xref: /freebsd/crypto/heimdal/lib/krb5/fcache.c (revision 884a2a699669ec61e2366e3e358342dbc94be24a)
1 /*
2  * Copyright (c) 1997 - 2008 Kungliga Tekniska H�gskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * 3. Neither the name of the Institute nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include "krb5_locl.h"
35 
36 RCSID("$Id: fcache.c 22522 2008-01-24 11:56:25Z lha $");
37 
38 typedef struct krb5_fcache{
39     char *filename;
40     int version;
41 }krb5_fcache;
42 
43 struct fcc_cursor {
44     int fd;
45     krb5_storage *sp;
46 };
47 
48 #define KRB5_FCC_FVNO_1 1
49 #define KRB5_FCC_FVNO_2 2
50 #define KRB5_FCC_FVNO_3 3
51 #define KRB5_FCC_FVNO_4 4
52 
53 #define FCC_TAG_DELTATIME 1
54 
55 #define FCACHE(X) ((krb5_fcache*)(X)->data.data)
56 
57 #define FILENAME(X) (FCACHE(X)->filename)
58 
59 #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
60 
61 static const char*
62 fcc_get_name(krb5_context context,
63 	     krb5_ccache id)
64 {
65     return FILENAME(id);
66 }
67 
68 int
69 _krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
70 	    const char *filename)
71 {
72     int ret;
73 #ifdef HAVE_FCNTL
74     struct flock l;
75 
76     l.l_start = 0;
77     l.l_len = 0;
78     l.l_type = exclusive ? F_WRLCK : F_RDLCK;
79     l.l_whence = SEEK_SET;
80     ret = fcntl(fd, F_SETLKW, &l);
81 #else
82     ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
83 #endif
84     if(ret < 0)
85 	ret = errno;
86     if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
87 	ret = EAGAIN;
88 
89     switch (ret) {
90     case 0:
91 	break;
92     case EINVAL: /* filesystem doesn't support locking, let the user have it */
93 	ret = 0;
94 	break;
95     case EAGAIN:
96 	krb5_set_error_string(context, "timed out locking cache file %s",
97 			      filename);
98 	break;
99     default:
100 	krb5_set_error_string(context, "error locking cache file %s: %s",
101 			      filename, strerror(ret));
102 	break;
103     }
104     return ret;
105 }
106 
107 int
108 _krb5_xunlock(krb5_context context, int fd)
109 {
110     int ret;
111 #ifdef HAVE_FCNTL
112     struct flock l;
113     l.l_start = 0;
114     l.l_len = 0;
115     l.l_type = F_UNLCK;
116     l.l_whence = SEEK_SET;
117     ret = fcntl(fd, F_SETLKW, &l);
118 #else
119     ret = flock(fd, LOCK_UN);
120 #endif
121     if (ret < 0)
122 	ret = errno;
123     switch (ret) {
124     case 0:
125 	break;
126     case EINVAL: /* filesystem doesn't support locking, let the user have it */
127 	ret = 0;
128 	break;
129     default:
130 	krb5_set_error_string(context,
131 			      "Failed to unlock file: %s", strerror(ret));
132 	break;
133     }
134     return ret;
135 }
136 
137 static krb5_error_code
138 fcc_lock(krb5_context context, krb5_ccache id,
139 	 int fd, krb5_boolean exclusive)
140 {
141     return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
142 }
143 
144 static krb5_error_code
145 fcc_unlock(krb5_context context, int fd)
146 {
147     return _krb5_xunlock(context, fd);
148 }
149 
150 static krb5_error_code
151 fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
152 {
153     krb5_fcache *f;
154     f = malloc(sizeof(*f));
155     if(f == NULL) {
156 	krb5_set_error_string(context, "malloc: out of memory");
157 	return KRB5_CC_NOMEM;
158     }
159     f->filename = strdup(res);
160     if(f->filename == NULL){
161 	free(f);
162 	krb5_set_error_string(context, "malloc: out of memory");
163 	return KRB5_CC_NOMEM;
164     }
165     f->version = 0;
166     (*id)->data.data = f;
167     (*id)->data.length = sizeof(*f);
168     return 0;
169 }
170 
171 /*
172  * Try to scrub the contents of `filename' safely.
173  */
174 
175 static int
176 scrub_file (int fd)
177 {
178     off_t pos;
179     char buf[128];
180 
181     pos = lseek(fd, 0, SEEK_END);
182     if (pos < 0)
183         return errno;
184     if (lseek(fd, 0, SEEK_SET) < 0)
185         return errno;
186     memset(buf, 0, sizeof(buf));
187     while(pos > 0) {
188         ssize_t tmp = write(fd, buf, min(sizeof(buf), pos));
189 
190 	if (tmp < 0)
191 	    return errno;
192 	pos -= tmp;
193     }
194     fsync (fd);
195     return 0;
196 }
197 
198 /*
199  * Erase `filename' if it exists, trying to remove the contents if
200  * it's `safe'.  We always try to remove the file, it it exists.  It's
201  * only overwritten if it's a regular file (not a symlink and not a
202  * hardlink)
203  */
204 
205 static krb5_error_code
206 erase_file(const char *filename)
207 {
208     int fd;
209     struct stat sb1, sb2;
210     int ret;
211 
212     ret = lstat (filename, &sb1);
213     if (ret < 0)
214 	return errno;
215 
216     fd = open(filename, O_RDWR | O_BINARY);
217     if(fd < 0) {
218 	if(errno == ENOENT)
219 	    return 0;
220 	else
221 	    return errno;
222     }
223     if (unlink(filename) < 0) {
224         close (fd);
225         return errno;
226     }
227     ret = fstat (fd, &sb2);
228     if (ret < 0) {
229 	close (fd);
230 	return errno;
231     }
232 
233     /* check if someone was playing with symlinks */
234 
235     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
236 	close (fd);
237 	return EPERM;
238     }
239 
240     /* there are still hard links to this file */
241 
242     if (sb2.st_nlink != 0) {
243         close (fd);
244         return 0;
245     }
246 
247     ret = scrub_file (fd);
248     close (fd);
249     return ret;
250 }
251 
252 static krb5_error_code
253 fcc_gen_new(krb5_context context, krb5_ccache *id)
254 {
255     krb5_fcache *f;
256     int fd;
257     char *file;
258 
259     f = malloc(sizeof(*f));
260     if(f == NULL) {
261 	krb5_set_error_string(context, "malloc: out of memory");
262 	return KRB5_CC_NOMEM;
263     }
264     asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
265     if(file == NULL) {
266 	free(f);
267 	krb5_set_error_string(context, "malloc: out of memory");
268 	return KRB5_CC_NOMEM;
269     }
270     fd = mkstemp(file);
271     if(fd < 0) {
272 	int ret = errno;
273 	krb5_set_error_string(context, "mkstemp %s", file);
274 	free(f);
275 	free(file);
276 	return ret;
277     }
278     close(fd);
279     f->filename = file;
280     f->version = 0;
281     (*id)->data.data = f;
282     (*id)->data.length = sizeof(*f);
283     return 0;
284 }
285 
286 static void
287 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
288 {
289     int flags = 0;
290     switch(vno) {
291     case KRB5_FCC_FVNO_1:
292 	flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
293 	flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
294 	flags |= KRB5_STORAGE_HOST_BYTEORDER;
295 	break;
296     case KRB5_FCC_FVNO_2:
297 	flags |= KRB5_STORAGE_HOST_BYTEORDER;
298 	break;
299     case KRB5_FCC_FVNO_3:
300 	flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
301 	break;
302     case KRB5_FCC_FVNO_4:
303 	break;
304     default:
305 	krb5_abortx(context,
306 		    "storage_set_flags called with bad vno (%x)", vno);
307     }
308     krb5_storage_set_flags(sp, flags);
309 }
310 
311 static krb5_error_code
312 fcc_open(krb5_context context,
313 	 krb5_ccache id,
314 	 int *fd_ret,
315 	 int flags,
316 	 mode_t mode)
317 {
318     krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
319 			      (flags | O_RDWR) == flags);
320     krb5_error_code ret;
321     const char *filename = FILENAME(id);
322     int fd;
323     fd = open(filename, flags, mode);
324     if(fd < 0) {
325 	ret = errno;
326 	krb5_set_error_string(context, "open(%s): %s", filename,
327 			      strerror(ret));
328 	return ret;
329     }
330 
331     if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
332 	close(fd);
333 	return ret;
334     }
335     *fd_ret = fd;
336     return 0;
337 }
338 
339 static krb5_error_code
340 fcc_initialize(krb5_context context,
341 	       krb5_ccache id,
342 	       krb5_principal primary_principal)
343 {
344     krb5_fcache *f = FCACHE(id);
345     int ret = 0;
346     int fd;
347     char *filename = f->filename;
348 
349     unlink (filename);
350 
351     ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
352     if(ret)
353 	return ret;
354     {
355 	krb5_storage *sp;
356 	sp = krb5_storage_from_fd(fd);
357 	krb5_storage_set_eof_code(sp, KRB5_CC_END);
358 	if(context->fcache_vno != 0)
359 	    f->version = context->fcache_vno;
360 	else
361 	    f->version = KRB5_FCC_FVNO_4;
362 	ret |= krb5_store_int8(sp, 5);
363 	ret |= krb5_store_int8(sp, f->version);
364 	storage_set_flags(context, sp, f->version);
365 	if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
366 	    /* V4 stuff */
367 	    if (context->kdc_sec_offset) {
368 		ret |= krb5_store_int16 (sp, 12); /* length */
369 		ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
370 		ret |= krb5_store_int16 (sp, 8); /* length of data */
371 		ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
372 		ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
373 	    } else {
374 		ret |= krb5_store_int16 (sp, 0);
375 	    }
376 	}
377 	ret |= krb5_store_principal(sp, primary_principal);
378 
379 	krb5_storage_free(sp);
380     }
381     fcc_unlock(context, fd);
382     if (close(fd) < 0)
383 	if (ret == 0) {
384 	    ret = errno;
385 	    krb5_set_error_string (context, "close %s: %s",
386 				   FILENAME(id), strerror(ret));
387 	}
388     return ret;
389 }
390 
391 static krb5_error_code
392 fcc_close(krb5_context context,
393 	  krb5_ccache id)
394 {
395     free (FILENAME(id));
396     krb5_data_free(&id->data);
397     return 0;
398 }
399 
400 static krb5_error_code
401 fcc_destroy(krb5_context context,
402 	    krb5_ccache id)
403 {
404     erase_file(FILENAME(id));
405     return 0;
406 }
407 
408 static krb5_error_code
409 fcc_store_cred(krb5_context context,
410 	       krb5_ccache id,
411 	       krb5_creds *creds)
412 {
413     int ret;
414     int fd;
415 
416     ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY, 0);
417     if(ret)
418 	return ret;
419     {
420 	krb5_storage *sp;
421 	sp = krb5_storage_from_fd(fd);
422 	krb5_storage_set_eof_code(sp, KRB5_CC_END);
423 	storage_set_flags(context, sp, FCACHE(id)->version);
424 	if (!krb5_config_get_bool_default(context, NULL, TRUE,
425 					  "libdefaults",
426 					  "fcc-mit-ticketflags",
427 					  NULL))
428 	    krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
429 	ret = krb5_store_creds(sp, creds);
430 	krb5_storage_free(sp);
431     }
432     fcc_unlock(context, fd);
433     if (close(fd) < 0)
434 	if (ret == 0) {
435 	    ret = errno;
436 	    krb5_set_error_string (context, "close %s: %s",
437 				   FILENAME(id), strerror(ret));
438 	}
439     return ret;
440 }
441 
442 static krb5_error_code
443 init_fcc (krb5_context context,
444 	  krb5_ccache id,
445 	  krb5_storage **ret_sp,
446 	  int *ret_fd)
447 {
448     int fd;
449     int8_t pvno, tag;
450     krb5_storage *sp;
451     krb5_error_code ret;
452 
453     ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY, 0);
454     if(ret)
455 	return ret;
456 
457     sp = krb5_storage_from_fd(fd);
458     if(sp == NULL) {
459 	krb5_clear_error_string(context);
460 	ret = ENOMEM;
461 	goto out;
462     }
463     krb5_storage_set_eof_code(sp, KRB5_CC_END);
464     ret = krb5_ret_int8(sp, &pvno);
465     if(ret != 0) {
466 	if(ret == KRB5_CC_END) {
467 	    krb5_set_error_string(context, "Empty credential cache file: %s",
468 				  FILENAME(id));
469 	    ret = ENOENT;
470 	} else
471 	    krb5_set_error_string(context, "Error reading pvno in "
472 				  "cache file: %s", FILENAME(id));
473 	goto out;
474     }
475     if(pvno != 5) {
476 	krb5_set_error_string(context, "Bad version number in credential "
477 			      "cache file: %s", FILENAME(id));
478 	ret = KRB5_CCACHE_BADVNO;
479 	goto out;
480     }
481     ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
482     if(ret != 0) {
483 	krb5_set_error_string(context, "Error reading tag in "
484 			      "cache file: %s", FILENAME(id));
485 	ret = KRB5_CC_FORMAT;
486 	goto out;
487     }
488     FCACHE(id)->version = tag;
489     storage_set_flags(context, sp, FCACHE(id)->version);
490     switch (tag) {
491     case KRB5_FCC_FVNO_4: {
492 	int16_t length;
493 
494 	ret = krb5_ret_int16 (sp, &length);
495 	if(ret) {
496 	    ret = KRB5_CC_FORMAT;
497 	    krb5_set_error_string(context, "Error reading tag length in "
498 			      "cache file: %s", FILENAME(id));
499 	    goto out;
500 	}
501 	while(length > 0) {
502 	    int16_t dtag, data_len;
503 	    int i;
504 	    int8_t dummy;
505 
506 	    ret = krb5_ret_int16 (sp, &dtag);
507 	    if(ret) {
508 		krb5_set_error_string(context, "Error reading dtag in "
509 				      "cache file: %s", FILENAME(id));
510 		ret = KRB5_CC_FORMAT;
511 		goto out;
512 	    }
513 	    ret = krb5_ret_int16 (sp, &data_len);
514 	    if(ret) {
515 		krb5_set_error_string(context, "Error reading dlength in "
516 				      "cache file: %s", FILENAME(id));
517 		ret = KRB5_CC_FORMAT;
518 		goto out;
519 	    }
520 	    switch (dtag) {
521 	    case FCC_TAG_DELTATIME :
522 		ret = krb5_ret_int32 (sp, &context->kdc_sec_offset);
523 		if(ret) {
524 		    krb5_set_error_string(context, "Error reading kdc_sec in "
525 					  "cache file: %s", FILENAME(id));
526 		    ret = KRB5_CC_FORMAT;
527 		    goto out;
528 		}
529 		ret = krb5_ret_int32 (sp, &context->kdc_usec_offset);
530 		if(ret) {
531 		    krb5_set_error_string(context, "Error reading kdc_usec in "
532 					  "cache file: %s", FILENAME(id));
533 		    ret = KRB5_CC_FORMAT;
534 		    goto out;
535 		}
536 		break;
537 	    default :
538 		for (i = 0; i < data_len; ++i) {
539 		    ret = krb5_ret_int8 (sp, &dummy);
540 		    if(ret) {
541 			krb5_set_error_string(context, "Error reading unknown "
542 					      "tag in cache file: %s",
543 					      FILENAME(id));
544 			ret = KRB5_CC_FORMAT;
545 			goto out;
546 		    }
547 		}
548 		break;
549 	    }
550 	    length -= 4 + data_len;
551 	}
552 	break;
553     }
554     case KRB5_FCC_FVNO_3:
555     case KRB5_FCC_FVNO_2:
556     case KRB5_FCC_FVNO_1:
557 	break;
558     default :
559 	ret = KRB5_CCACHE_BADVNO;
560 	krb5_set_error_string(context, "Unknown version number (%d) in "
561 			      "credential cache file: %s",
562 			      (int)tag, FILENAME(id));
563 	goto out;
564     }
565     *ret_sp = sp;
566     *ret_fd = fd;
567 
568     return 0;
569   out:
570     if(sp != NULL)
571 	krb5_storage_free(sp);
572     fcc_unlock(context, fd);
573     close(fd);
574     return ret;
575 }
576 
577 static krb5_error_code
578 fcc_get_principal(krb5_context context,
579 		  krb5_ccache id,
580 		  krb5_principal *principal)
581 {
582     krb5_error_code ret;
583     int fd;
584     krb5_storage *sp;
585 
586     ret = init_fcc (context, id, &sp, &fd);
587     if (ret)
588 	return ret;
589     ret = krb5_ret_principal(sp, principal);
590     if (ret)
591 	krb5_clear_error_string(context);
592     krb5_storage_free(sp);
593     fcc_unlock(context, fd);
594     close(fd);
595     return ret;
596 }
597 
598 static krb5_error_code
599 fcc_end_get (krb5_context context,
600 	     krb5_ccache id,
601 	     krb5_cc_cursor *cursor);
602 
603 static krb5_error_code
604 fcc_get_first (krb5_context context,
605 	       krb5_ccache id,
606 	       krb5_cc_cursor *cursor)
607 {
608     krb5_error_code ret;
609     krb5_principal principal;
610 
611     *cursor = malloc(sizeof(struct fcc_cursor));
612     if (*cursor == NULL) {
613         krb5_set_error_string (context, "malloc: out of memory");
614 	return ENOMEM;
615     }
616     memset(*cursor, 0, sizeof(struct fcc_cursor));
617 
618     ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp,
619 		    &FCC_CURSOR(*cursor)->fd);
620     if (ret) {
621 	free(*cursor);
622 	*cursor = NULL;
623 	return ret;
624     }
625     ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
626     if(ret) {
627 	krb5_clear_error_string(context);
628 	fcc_end_get(context, id, cursor);
629 	return ret;
630     }
631     krb5_free_principal (context, principal);
632     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
633     return 0;
634 }
635 
636 static krb5_error_code
637 fcc_get_next (krb5_context context,
638 	      krb5_ccache id,
639 	      krb5_cc_cursor *cursor,
640 	      krb5_creds *creds)
641 {
642     krb5_error_code ret;
643     if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
644 	return ret;
645 
646     ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
647     if (ret)
648 	krb5_clear_error_string(context);
649 
650     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
651     return ret;
652 }
653 
654 static krb5_error_code
655 fcc_end_get (krb5_context context,
656 	     krb5_ccache id,
657 	     krb5_cc_cursor *cursor)
658 {
659     krb5_storage_free(FCC_CURSOR(*cursor)->sp);
660     close (FCC_CURSOR(*cursor)->fd);
661     free(*cursor);
662     *cursor = NULL;
663     return 0;
664 }
665 
666 static krb5_error_code
667 fcc_remove_cred(krb5_context context,
668 		 krb5_ccache id,
669 		 krb5_flags which,
670 		 krb5_creds *cred)
671 {
672     krb5_error_code ret;
673     krb5_ccache copy;
674 
675     ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &copy);
676     if (ret)
677 	return ret;
678 
679     ret = krb5_cc_copy_cache(context, id, copy);
680     if (ret) {
681 	krb5_cc_destroy(context, copy);
682 	return ret;
683     }
684 
685     ret = krb5_cc_remove_cred(context, copy, which, cred);
686     if (ret) {
687 	krb5_cc_destroy(context, copy);
688 	return ret;
689     }
690 
691     fcc_destroy(context, id);
692 
693     ret = krb5_cc_copy_cache(context, copy, id);
694     krb5_cc_destroy(context, copy);
695 
696     return ret;
697 }
698 
699 static krb5_error_code
700 fcc_set_flags(krb5_context context,
701 	      krb5_ccache id,
702 	      krb5_flags flags)
703 {
704     return 0; /* XXX */
705 }
706 
707 static krb5_error_code
708 fcc_get_version(krb5_context context,
709 		krb5_ccache id)
710 {
711     return FCACHE(id)->version;
712 }
713 
714 struct fcache_iter {
715     int first;
716 };
717 
718 static krb5_error_code
719 fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
720 {
721     struct fcache_iter *iter;
722 
723     iter = calloc(1, sizeof(*iter));
724     if (iter == NULL) {
725 	krb5_set_error_string(context, "malloc - out of memory");
726 	return ENOMEM;
727     }
728     iter->first = 1;
729     *cursor = iter;
730     return 0;
731 }
732 
733 static krb5_error_code
734 fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
735 {
736     struct fcache_iter *iter = cursor;
737     krb5_error_code ret;
738     const char *fn;
739     char *expandedfn = NULL;
740 
741     if (!iter->first) {
742 	krb5_clear_error_string(context);
743 	return KRB5_CC_END;
744     }
745     iter->first = 0;
746 
747     fn = krb5_cc_default_name(context);
748     if (strncasecmp(fn, "FILE:", 5) != 0) {
749 	ret = _krb5_expand_default_cc_name(context,
750 					   KRB5_DEFAULT_CCNAME_FILE,
751 					   &expandedfn);
752 	if (ret)
753 	    return ret;
754     }
755     ret = krb5_cc_resolve(context, fn, id);
756     if (expandedfn)
757 	free(expandedfn);
758 
759     return ret;
760 }
761 
762 static krb5_error_code
763 fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
764 {
765     struct fcache_iter *iter = cursor;
766     free(iter);
767     return 0;
768 }
769 
770 static krb5_error_code
771 fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
772 {
773     krb5_error_code ret = 0;
774 
775     ret = rename(FILENAME(from), FILENAME(to));
776     if (ret && errno != EXDEV) {
777 	ret = errno;
778 	krb5_set_error_string(context,
779 			      "Rename of file from %s to %s failed: %s",
780 			      FILENAME(from), FILENAME(to),
781 			      strerror(ret));
782 	return ret;
783     } else if (ret && errno == EXDEV) {
784 	/* make a copy and delete the orignal */
785 	krb5_ssize_t sz1, sz2;
786 	int fd1, fd2;
787 	char buf[BUFSIZ];
788 
789 	ret = fcc_open(context, from, &fd1, O_RDONLY | O_BINARY, 0);
790 	if(ret)
791 	    return ret;
792 
793 	unlink(FILENAME(to));
794 
795 	ret = fcc_open(context, to, &fd2,
796 		       O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0600);
797 	if(ret)
798 	    goto out1;
799 
800 	while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
801 	    sz2 = write(fd2, buf, sz1);
802 	    if (sz1 != sz2) {
803 		ret = EIO;
804 		krb5_set_error_string(context,
805 				      "Failed to write data from one file "
806 				      "credential cache to the other");
807 		goto out2;
808 	    }
809 	}
810 	if (sz1 < 0) {
811 	    ret = EIO;
812 	    krb5_set_error_string(context,
813 				  "Failed to read data from one file "
814 				  "credential cache to the other");
815 	    goto out2;
816 	}
817 	erase_file(FILENAME(from));
818 
819     out2:
820 	fcc_unlock(context, fd2);
821 	close(fd2);
822 
823     out1:
824 	fcc_unlock(context, fd1);
825 	close(fd1);
826 
827 	if (ret) {
828 	    erase_file(FILENAME(to));
829 	    return ret;
830 	}
831     }
832 
833     /* make sure ->version is uptodate */
834     {
835 	krb5_storage *sp;
836 	int fd;
837 	ret = init_fcc (context, to, &sp, &fd);
838 	krb5_storage_free(sp);
839 	fcc_unlock(context, fd);
840 	close(fd);
841     }
842     return ret;
843 }
844 
845 static krb5_error_code
846 fcc_default_name(krb5_context context, char **str)
847 {
848     return _krb5_expand_default_cc_name(context,
849 					KRB5_DEFAULT_CCNAME_FILE,
850 					str);
851 }
852 
853 /**
854  * Variable containing the FILE based credential cache implemention.
855  *
856  * @ingroup krb5_ccache
857  */
858 
859 const krb5_cc_ops krb5_fcc_ops = {
860     "FILE",
861     fcc_get_name,
862     fcc_resolve,
863     fcc_gen_new,
864     fcc_initialize,
865     fcc_destroy,
866     fcc_close,
867     fcc_store_cred,
868     NULL, /* fcc_retrieve */
869     fcc_get_principal,
870     fcc_get_first,
871     fcc_get_next,
872     fcc_end_get,
873     fcc_remove_cred,
874     fcc_set_flags,
875     fcc_get_version,
876     fcc_get_cache_first,
877     fcc_get_cache_next,
878     fcc_end_cache_get,
879     fcc_move,
880     fcc_default_name
881 };
882