1 /*	$NetBSD: dcache.c,v 1.2 2017/01/28 21:31:49 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * All rights reserved.
7  *
8  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  *
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * 3. Neither the name of the Institute nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 
38 #include "krb5_locl.h"
39 
40 typedef struct krb5_dcache{
41     krb5_ccache fcache;
42     char *dir;
43     char *name;
44 } krb5_dcache;
45 
46 #define DCACHE(X) ((krb5_dcache*)(X)->data.data)
47 #define D2FCACHE(X) ((X)->fcache)
48 
49 static krb5_error_code KRB5_CALLCONV dcc_close(krb5_context, krb5_ccache);
50 static krb5_error_code KRB5_CALLCONV dcc_get_default_name(krb5_context, char **);
51 
52 
53 static char *
primary_create(krb5_dcache * dc)54 primary_create(krb5_dcache *dc)
55 {
56     char *primary = NULL;
57 
58     asprintf(&primary, "%s/primary", dc->dir);
59     if (primary == NULL)
60 	return NULL;
61 
62     return primary;
63 }
64 
65 static int
is_filename_cacheish(const char * name)66 is_filename_cacheish(const char *name)
67 {
68     return strncmp(name, "tkt", 3) == 0;
69 
70 }
71 
72 static krb5_error_code
set_default_cache(krb5_context context,krb5_dcache * dc,const char * residual)73 set_default_cache(krb5_context context, krb5_dcache *dc, const char *residual)
74 {
75     char *path = NULL, *primary = NULL;
76     krb5_error_code ret;
77     struct iovec iov[2];
78     size_t len;
79     int fd = -1;
80 
81     if (!is_filename_cacheish(residual)) {
82 	krb5_set_error_message(context, KRB5_CC_FORMAT,
83 			       "name %s is not a cache (doesn't start with tkt)", residual);
84 	return KRB5_CC_FORMAT;
85     }
86 
87     asprintf(&path, "%s/primary-XXXXXX", dc->dir);
88     if (path == NULL)
89 	return krb5_enomem(context);
90 
91     fd = mkstemp(path);
92     if (fd < 0) {
93 	ret = errno;
94 	goto out;
95     }
96     rk_cloexec(fd);
97 #ifndef _WIN32
98     if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
99 	ret = errno;
100 	goto out;
101     }
102 #endif
103     len = strlen(residual);
104 
105     iov[0].iov_base = rk_UNCONST(residual);
106     iov[0].iov_len = len;
107     iov[1].iov_base = "\n";
108     iov[1].iov_len = 1;
109 
110     if (writev(fd, iov, sizeof(iov)/sizeof(iov[0])) != len + 1) {
111 	ret = errno;
112 	goto out;
113     }
114 
115     primary = primary_create(dc);
116     if (primary == NULL) {
117 	ret = krb5_enomem(context);
118 	goto out;
119     }
120 
121     if (rename(path, primary) < 0) {
122 	ret = errno;
123 	goto out;
124     }
125 
126     close(fd);
127     fd = -1;
128 
129     ret = 0;
130  out:
131     if (fd >= 0) {
132 	(void)unlink(path);
133 	close(fd);
134     }
135     if (path)
136 	free(path);
137     if (primary)
138 	free(primary);
139 
140     return ret;
141 }
142 
143 static krb5_error_code
get_default_cache(krb5_context context,krb5_dcache * dc,char ** residual)144 get_default_cache(krb5_context context, krb5_dcache *dc, char **residual)
145 {
146     krb5_error_code ret;
147     char buf[MAXPATHLEN];
148     char *primary;
149     FILE *f;
150 
151     *residual = NULL;
152     primary = primary_create(dc);
153     if (primary == NULL)
154 	return krb5_enomem(context);
155 
156     f = fopen(primary, "r");
157     if (f == NULL) {
158 	if (errno == ENOENT) {
159 	    free(primary);
160 	    *residual = strdup("tkt");
161 	    if (*residual == NULL)
162 		return krb5_enomem(context);
163 	    return 0;
164 	}
165 	ret = errno;
166 	krb5_set_error_message(context, ret, "failed to open %s", primary);
167 	free(primary);
168 	return ret;
169     }
170 
171     if (fgets(buf, sizeof(buf), f) == NULL) {
172 	ret = ferror(f);
173 	fclose(f);
174 	krb5_set_error_message(context, ret, "read file %s", primary);
175 	free(primary);
176 	return ret;
177     }
178     fclose(f);
179 
180     buf[strcspn(buf, "\r\n")] = '\0';
181 
182     if (!is_filename_cacheish(buf)) {
183 	krb5_set_error_message(context, KRB5_CC_FORMAT,
184 			       "name in %s is not a cache (doesn't start with tkt)", primary);
185 	free(primary);
186         return KRB5_CC_FORMAT;
187     }
188 
189     free(primary);
190 
191     *residual = strdup(buf);
192     if (*residual == NULL)
193 	return krb5_enomem(context);
194 
195     return 0;
196 }
197 
198 
199 
200 static const char* KRB5_CALLCONV
dcc_get_name(krb5_context context,krb5_ccache id)201 dcc_get_name(krb5_context context,
202 	     krb5_ccache id)
203 {
204     krb5_dcache *dc = DCACHE(id);
205     return dc->name;
206 }
207 
208 
209 static krb5_error_code
verify_directory(krb5_context context,const char * path)210 verify_directory(krb5_context context, const char *path)
211 {
212     struct stat sb;
213 
214     if (stat(path, &sb) != 0) {
215 	if (errno == ENOENT) {
216 	    /* XXX should use mkdirx_np()  */
217 	    if (rk_mkdir(path, S_IRWXU) == 0)
218 		return 0;
219 
220 	    krb5_set_error_message(context, ENOENT,
221 				   N_("DIR directory %s doesn't exists", ""), path);
222 	    return ENOENT;
223 	} else {
224 	    int ret = errno;
225 	    krb5_set_error_message(context, ret,
226 				   N_("DIR directory %s is bad: %s", ""), path, strerror(ret));
227 	    return errno;
228 	}
229     }
230     if (!S_ISDIR(sb.st_mode)) {
231 	krb5_set_error_message(context, KRB5_CC_BADNAME,
232 			       N_("DIR directory %s is not a directory", ""), path);
233 	return KRB5_CC_BADNAME;
234     }
235 
236     return 0;
237 }
238 
239 static void
dcc_release(krb5_context context,krb5_dcache * dc)240 dcc_release(krb5_context context, krb5_dcache *dc)
241 {
242     if (dc->fcache)
243 	krb5_cc_close(context, dc->fcache);
244     if (dc->dir)
245 	free(dc->dir);
246     if (dc->name)
247 	free(dc->name);
248     memset(dc, 0, sizeof(*dc));
249     free(dc);
250 }
251 
252 static krb5_error_code KRB5_CALLCONV
dcc_resolve(krb5_context context,krb5_ccache * id,const char * res)253 dcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
254 {
255     char *filename = NULL;
256     krb5_error_code ret;
257     krb5_dcache *dc;
258     const char *p;
259 
260     p = res;
261     do {
262 	p = strstr(p, "..");
263 	if (p && (p == res || ISPATHSEP(p[-1])) && (ISPATHSEP(p[2]) || p[2] == '\0')) {
264 	    krb5_set_error_message(context, KRB5_CC_FORMAT,
265 				   N_("Path contains a .. component", ""));
266 	    return KRB5_CC_FORMAT;
267 	}
268 	if (p)
269 	    p += 3;
270     } while (p);
271 
272     dc = calloc(1, sizeof(*dc));
273     if (dc == NULL) {
274 	krb5_set_error_message(context, KRB5_CC_NOMEM,
275 			       N_("malloc: out of memory", ""));
276 	return KRB5_CC_NOMEM;
277     }
278 
279     /* check for explicit component */
280     if (res[0] == ':') {
281 	char *q;
282 
283 	dc->dir = strdup(&res[1]);
284 #ifdef _WIN32
285 	q = strrchr(dc->dir, '\\');
286 	if (q == NULL)
287 #endif
288 	q = strrchr(dc->dir, '/');
289 	if (q) {
290 	    *q++ = '\0';
291 	} else {
292 	    krb5_set_error_message(context, KRB5_CC_FORMAT, N_("Cache not an absolute path: %s", ""), dc->dir);
293 	    dcc_release(context, dc);
294 	    return KRB5_CC_FORMAT;
295 	}
296 
297 	if (!is_filename_cacheish(q)) {
298 	    krb5_set_error_message(context, KRB5_CC_FORMAT,
299 				   N_("Name %s is not a cache (doesn't start with tkt)", ""), q);
300 	    dcc_release(context, dc);
301 	    return KRB5_CC_FORMAT;
302 	}
303 
304 	ret = verify_directory(context, dc->dir);
305 	if (ret) {
306 	    dcc_release(context, dc);
307 	    return ret;
308 	}
309 
310 	dc->name = strdup(res);
311 	if (dc->name == NULL) {
312 	    dcc_release(context, dc);
313 	    return krb5_enomem(context);
314 	}
315 
316     } else {
317 	char *residual;
318 	size_t len;
319 
320 	dc->dir = strdup(res);
321 	if (dc->dir == NULL) {
322 	    dcc_release(context, dc);
323 	    return krb5_enomem(context);
324 	}
325 
326 	len = strlen(dc->dir);
327 
328 	if (ISPATHSEP(dc->dir[len - 1]))
329 	    dc->dir[len - 1] = '\0';
330 
331 	ret = verify_directory(context, dc->dir);
332 	if (ret) {
333 	    dcc_release(context, dc);
334 	    return ret;
335 	}
336 
337 	ret = get_default_cache(context, dc, &residual);
338 	if (ret) {
339 	    dcc_release(context, dc);
340 	    return ret;
341 	}
342 	asprintf(&dc->name, ":%s/%s", dc->dir, residual);
343 	free(residual);
344 	if (dc->name == NULL) {
345 	    dcc_release(context, dc);
346 	    return krb5_enomem(context);
347 	}
348     }
349 
350     asprintf(&filename, "FILE%s", dc->name);
351     if (filename == NULL) {
352 	dcc_release(context, dc);
353 	return krb5_enomem(context);
354     }
355 
356     ret = krb5_cc_resolve(context, filename, &dc->fcache);
357     free(filename);
358     if (ret) {
359 	dcc_release(context, dc);
360 	return ret;
361     }
362 
363 
364     (*id)->data.data = dc;
365     (*id)->data.length = sizeof(*dc);
366     return 0;
367 }
368 
369 static char *
copy_default_dcc_cache(krb5_context context)370 copy_default_dcc_cache(krb5_context context)
371 {
372     const char *defname;
373     krb5_error_code ret;
374     char *name = NULL;
375     size_t len;
376 
377     len = strlen(krb5_dcc_ops.prefix);
378 
379     defname = krb5_cc_default_name(context);
380     if (defname == NULL ||
381 	strncmp(defname, krb5_dcc_ops.prefix, len) != 0 ||
382 	defname[len] != ':')
383     {
384 	ret = dcc_get_default_name(context, &name);
385 	if (ret)
386 	    return NULL;
387 
388 	return name;
389     } else {
390 	return strdup(&defname[len + 1]);
391     }
392 }
393 
394 
395 static krb5_error_code KRB5_CALLCONV
dcc_gen_new(krb5_context context,krb5_ccache * id)396 dcc_gen_new(krb5_context context, krb5_ccache *id)
397 {
398     krb5_error_code ret;
399     char *name = NULL;
400     krb5_dcache *dc;
401     int fd;
402     size_t len;
403 
404     name = copy_default_dcc_cache(context);
405     if (name == NULL) {
406 	krb5_set_error_message(context, KRB5_CC_FORMAT,
407 			       N_("Can't generate DIR caches unless its the default type", ""));
408 	return KRB5_CC_FORMAT;
409     }
410 
411     len = strlen(krb5_dcc_ops.prefix);
412     if (strncmp(name, krb5_dcc_ops.prefix, len) == 0 && name[len] == ':')
413 	++len;
414     else
415 	len = 0;
416 
417     ret = dcc_resolve(context, id, name + len);
418     free(name);
419     name = NULL;
420     if (ret)
421 	return ret;
422 
423     dc = DCACHE((*id));
424 
425     asprintf(&name, ":%s/tktXXXXXX", dc->dir);
426     if (name == NULL) {
427 	dcc_close(context, *id);
428 	return krb5_enomem(context);
429     }
430 
431     fd = mkstemp(&name[1]);
432     if (fd < 0) {
433 	dcc_close(context, *id);
434 	return krb5_enomem(context);
435     }
436     close(fd);
437 
438     free(dc->name);
439     dc->name = name;
440 
441     return 0;
442 }
443 
444 static krb5_error_code KRB5_CALLCONV
dcc_initialize(krb5_context context,krb5_ccache id,krb5_principal primary_principal)445 dcc_initialize(krb5_context context,
446 	       krb5_ccache id,
447 	       krb5_principal primary_principal)
448 {
449     krb5_dcache *dc = DCACHE(id);
450     return krb5_cc_initialize(context, D2FCACHE(dc), primary_principal);
451 }
452 
453 static krb5_error_code KRB5_CALLCONV
dcc_close(krb5_context context,krb5_ccache id)454 dcc_close(krb5_context context,
455 	  krb5_ccache id)
456 {
457     dcc_release(context, DCACHE(id));
458     return 0;
459 }
460 
461 static krb5_error_code KRB5_CALLCONV
dcc_destroy(krb5_context context,krb5_ccache id)462 dcc_destroy(krb5_context context,
463 	    krb5_ccache id)
464 {
465     krb5_dcache *dc = DCACHE(id);
466     krb5_ccache fcache = D2FCACHE(dc);
467     dc->fcache = NULL;
468     return krb5_cc_destroy(context, fcache);
469 }
470 
471 static krb5_error_code KRB5_CALLCONV
dcc_store_cred(krb5_context context,krb5_ccache id,krb5_creds * creds)472 dcc_store_cred(krb5_context context,
473 	       krb5_ccache id,
474 	       krb5_creds *creds)
475 {
476     krb5_dcache *dc = DCACHE(id);
477     return krb5_cc_store_cred(context, D2FCACHE(dc), creds);
478 }
479 
480 static krb5_error_code KRB5_CALLCONV
dcc_get_principal(krb5_context context,krb5_ccache id,krb5_principal * principal)481 dcc_get_principal(krb5_context context,
482 		  krb5_ccache id,
483 		  krb5_principal *principal)
484 {
485     krb5_dcache *dc = DCACHE(id);
486     return krb5_cc_get_principal(context, D2FCACHE(dc), principal);
487 }
488 
489 static krb5_error_code KRB5_CALLCONV
dcc_get_first(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)490 dcc_get_first (krb5_context context,
491 	       krb5_ccache id,
492 	       krb5_cc_cursor *cursor)
493 {
494     krb5_dcache *dc = DCACHE(id);
495     return krb5_cc_start_seq_get(context, D2FCACHE(dc), cursor);
496 }
497 
498 static krb5_error_code KRB5_CALLCONV
dcc_get_next(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor,krb5_creds * creds)499 dcc_get_next (krb5_context context,
500 	      krb5_ccache id,
501 	      krb5_cc_cursor *cursor,
502 	      krb5_creds *creds)
503 {
504     krb5_dcache *dc = DCACHE(id);
505     return krb5_cc_next_cred(context, D2FCACHE(dc), cursor, creds);
506 }
507 
508 static krb5_error_code KRB5_CALLCONV
dcc_end_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)509 dcc_end_get (krb5_context context,
510 	     krb5_ccache id,
511 	     krb5_cc_cursor *cursor)
512 {
513     krb5_dcache *dc = DCACHE(id);
514     return krb5_cc_end_seq_get(context, D2FCACHE(dc), cursor);
515 }
516 
517 static krb5_error_code KRB5_CALLCONV
dcc_remove_cred(krb5_context context,krb5_ccache id,krb5_flags which,krb5_creds * cred)518 dcc_remove_cred(krb5_context context,
519 		 krb5_ccache id,
520 		 krb5_flags which,
521 		 krb5_creds *cred)
522 {
523     krb5_dcache *dc = DCACHE(id);
524     return krb5_cc_remove_cred(context, D2FCACHE(dc), which, cred);
525 }
526 
527 static krb5_error_code KRB5_CALLCONV
dcc_set_flags(krb5_context context,krb5_ccache id,krb5_flags flags)528 dcc_set_flags(krb5_context context,
529 	      krb5_ccache id,
530 	      krb5_flags flags)
531 {
532     krb5_dcache *dc = DCACHE(id);
533     return krb5_cc_set_flags(context, D2FCACHE(dc), flags);
534 }
535 
536 static int KRB5_CALLCONV
dcc_get_version(krb5_context context,krb5_ccache id)537 dcc_get_version(krb5_context context,
538 		krb5_ccache id)
539 {
540     krb5_dcache *dc = DCACHE(id);
541     return krb5_cc_get_version(context, D2FCACHE(dc));
542 }
543 
544 struct dcache_iter {
545     int first;
546     krb5_dcache *dc;
547 };
548 
549 static krb5_error_code KRB5_CALLCONV
dcc_get_cache_first(krb5_context context,krb5_cc_cursor * cursor)550 dcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
551 {
552     struct dcache_iter *iter;
553     krb5_error_code ret;
554     char *name;
555 
556     *cursor = NULL;
557     iter = calloc(1, sizeof(*iter));
558     if (iter == NULL)
559 	return krb5_enomem(context);
560     iter->first = 1;
561 
562     name = copy_default_dcc_cache(context);
563     if (name == NULL) {
564         free(iter);
565 	krb5_set_error_message(context, KRB5_CC_FORMAT,
566 			       N_("Can't generate DIR caches unless its the default type", ""));
567 	return KRB5_CC_FORMAT;
568     }
569 
570     ret = dcc_resolve(context, NULL, name);
571     free(name);
572     if (ret) {
573         free(iter);
574         return ret;
575     }
576 
577     /* XXX We need to opendir() here */
578 
579     *cursor = iter;
580     return 0;
581 }
582 
583 static krb5_error_code KRB5_CALLCONV
dcc_get_cache_next(krb5_context context,krb5_cc_cursor cursor,krb5_ccache * id)584 dcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
585 {
586     struct dcache_iter *iter = cursor;
587 
588     if (iter == NULL)
589         return krb5_einval(context, 2);
590 
591     if (!iter->first) {
592 	krb5_clear_error_message(context);
593 	return KRB5_CC_END;
594     }
595 
596     /* XXX We need to readdir() here */
597     iter->first = 0;
598 
599     return KRB5_CC_END;
600 }
601 
602 static krb5_error_code KRB5_CALLCONV
dcc_end_cache_get(krb5_context context,krb5_cc_cursor cursor)603 dcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
604 {
605     struct dcache_iter *iter = cursor;
606 
607     if (iter == NULL)
608         return krb5_einval(context, 2);
609 
610     /* XXX We need to closedir() here */
611     if (iter->dc)
612 	dcc_release(context, iter->dc);
613     free(iter);
614     return 0;
615 }
616 
617 static krb5_error_code KRB5_CALLCONV
dcc_move(krb5_context context,krb5_ccache from,krb5_ccache to)618 dcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
619 {
620     krb5_dcache *dcfrom = DCACHE(from);
621     krb5_dcache *dcto = DCACHE(to);
622     return krb5_cc_move(context, D2FCACHE(dcfrom), D2FCACHE(dcto));
623 }
624 
625 static krb5_error_code KRB5_CALLCONV
dcc_get_default_name(krb5_context context,char ** str)626 dcc_get_default_name(krb5_context context, char **str)
627 {
628     return _krb5_expand_default_cc_name(context,
629 					KRB5_DEFAULT_CCNAME_DIR,
630 					str);
631 }
632 
633 static krb5_error_code KRB5_CALLCONV
dcc_set_default(krb5_context context,krb5_ccache id)634 dcc_set_default(krb5_context context, krb5_ccache id)
635 {
636     krb5_dcache *dc = DCACHE(id);
637     const char *name;
638 
639     name = krb5_cc_get_name(context, D2FCACHE(dc));
640     if (name == NULL)
641 	return ENOENT;
642 
643     return set_default_cache(context, dc, name);
644 }
645 
646 static krb5_error_code KRB5_CALLCONV
dcc_lastchange(krb5_context context,krb5_ccache id,krb5_timestamp * mtime)647 dcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
648 {
649     krb5_dcache *dc = DCACHE(id);
650     return krb5_cc_last_change_time(context, D2FCACHE(dc), mtime);
651 }
652 
653 static krb5_error_code KRB5_CALLCONV
dcc_set_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat kdc_offset)654 dcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
655 {
656     krb5_dcache *dc = DCACHE(id);
657     return krb5_cc_set_kdc_offset(context, D2FCACHE(dc), kdc_offset);
658 }
659 
660 static krb5_error_code KRB5_CALLCONV
dcc_get_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat * kdc_offset)661 dcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
662 {
663     krb5_dcache *dc = DCACHE(id);
664     return krb5_cc_get_kdc_offset(context, D2FCACHE(dc), kdc_offset);
665 }
666 
667 
668 /**
669  * Variable containing the DIR based credential cache implemention.
670  *
671  * @ingroup krb5_ccache
672  */
673 
674 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_dcc_ops = {
675     KRB5_CC_OPS_VERSION,
676     "DIR",
677     dcc_get_name,
678     dcc_resolve,
679     dcc_gen_new,
680     dcc_initialize,
681     dcc_destroy,
682     dcc_close,
683     dcc_store_cred,
684     NULL, /* dcc_retrieve */
685     dcc_get_principal,
686     dcc_get_first,
687     dcc_get_next,
688     dcc_end_get,
689     dcc_remove_cred,
690     dcc_set_flags,
691     dcc_get_version,
692     dcc_get_cache_first,
693     dcc_get_cache_next,
694     dcc_end_cache_get,
695     dcc_move,
696     dcc_get_default_name,
697     dcc_set_default,
698     dcc_lastchange,
699     dcc_set_kdc_offset,
700     dcc_get_kdc_offset
701 };
702