8951bdb24e38b13d140a0a410cb5b4ef6d8e611b
[metze/samba/wip.git] / source / heimdal / lib / krb5 / fcache.c
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 23444 2008-07-27 12:07:47Z 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_message(context, ret, "timed out locking cache file %s", 
97                                filename);
98         break;
99     default:
100         krb5_set_error_message(context, ret, "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_message(context, ret,
131                                "Failed to unlock file: %s",
132                                strerror(ret));
133         break;
134     }
135     return ret;
136 }
137
138 static krb5_error_code
139 write_storage(krb5_context context, krb5_storage *sp, int fd)
140 {
141     krb5_error_code ret;
142     krb5_data data;
143     ssize_t sret;
144
145     ret = krb5_storage_to_data(sp, &data);
146     if (ret) {
147         krb5_set_error_message(context, ret, "malloc: out of memory");
148         return ret;
149     }
150     sret = write(fd, data.data, data.length);
151     ret = (sret != data.length);
152     krb5_data_free(&data);
153     if (ret) {
154         ret = errno;
155         krb5_set_error_message(context, ret,
156                                "Failed to write FILE credential data");
157         return ret;
158     }
159     return 0;
160 }
161
162
163 static krb5_error_code
164 fcc_lock(krb5_context context, krb5_ccache id,
165          int fd, krb5_boolean exclusive)
166 {
167     return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
168 }
169
170 static krb5_error_code
171 fcc_unlock(krb5_context context, int fd)
172 {
173     return _krb5_xunlock(context, fd);
174 }
175
176 static krb5_error_code
177 fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
178 {
179     krb5_fcache *f;
180     f = malloc(sizeof(*f));
181     if(f == NULL) {
182         krb5_set_error_message(context, KRB5_CC_NOMEM,
183                                "malloc: out of memory");
184         return KRB5_CC_NOMEM;
185     }
186     f->filename = strdup(res);
187     if(f->filename == NULL){
188         free(f);
189         krb5_set_error_message(context, KRB5_CC_NOMEM,
190                                "malloc: out of memory");
191         return KRB5_CC_NOMEM;
192     }
193     f->version = 0;
194     (*id)->data.data = f;
195     (*id)->data.length = sizeof(*f);
196     return 0;
197 }
198
199 /*
200  * Try to scrub the contents of `filename' safely.
201  */
202
203 static int
204 scrub_file (int fd)
205 {
206     off_t pos;
207     char buf[128];
208
209     pos = lseek(fd, 0, SEEK_END);
210     if (pos < 0)
211         return errno;
212     if (lseek(fd, 0, SEEK_SET) < 0)
213         return errno;
214     memset(buf, 0, sizeof(buf));
215     while(pos > 0) {
216         ssize_t tmp = write(fd, buf, min(sizeof(buf), pos));
217
218         if (tmp < 0)
219             return errno;
220         pos -= tmp;
221     }
222     fsync (fd);
223     return 0;
224 }
225
226 /*
227  * Erase `filename' if it exists, trying to remove the contents if
228  * it's `safe'.  We always try to remove the file, it it exists.  It's
229  * only overwritten if it's a regular file (not a symlink and not a
230  * hardlink)
231  */
232
233 static krb5_error_code
234 erase_file(krb5_context context, const char *filename)
235 {
236     int fd;
237     struct stat sb1, sb2;
238     int ret;
239
240     ret = lstat (filename, &sb1);
241     if (ret < 0)
242         return errno;
243
244     fd = open(filename, O_RDWR | O_BINARY);
245     if(fd < 0) {
246         if(errno == ENOENT)
247             return 0;
248         else
249             return errno;
250     }
251     rk_cloexec(fd);
252     ret = _krb5_xlock(context, fd, 1, filename);
253     if (ret) {
254         close(fd);
255         return ret;
256     }
257     if (unlink(filename) < 0) {
258         _krb5_xunlock(context, fd);
259         close (fd);
260         return errno;
261     }
262     ret = fstat (fd, &sb2);
263     if (ret < 0) {
264         _krb5_xunlock(context, fd);
265         close (fd);
266         return errno;
267     }
268
269     /* check if someone was playing with symlinks */
270
271     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
272         _krb5_xunlock(context, fd);
273         close (fd);
274         return EPERM;
275     }
276
277     /* there are still hard links to this file */
278
279     if (sb2.st_nlink != 0) {
280         _krb5_xunlock(context, fd);
281         close (fd);
282         return 0;
283     }
284
285     ret = scrub_file (fd);
286     if (ret) {
287         _krb5_xunlock(context, fd);
288         close(fd);
289         return ret;
290     }
291     ret = _krb5_xunlock(context, fd);
292     close (fd);
293     return ret;
294 }
295
296 static krb5_error_code
297 fcc_gen_new(krb5_context context, krb5_ccache *id)
298 {
299     krb5_fcache *f;
300     int fd;
301     char *file;
302
303     f = malloc(sizeof(*f));
304     if(f == NULL) {
305         krb5_set_error_message(context, KRB5_CC_NOMEM,
306                                "malloc: out of memory");
307         return KRB5_CC_NOMEM;
308     }
309     asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
310     if(file == NULL) {
311         free(f);
312         krb5_set_error_message(context, KRB5_CC_NOMEM,
313                                "malloc: out of memory");
314         return KRB5_CC_NOMEM;
315     }
316     fd = mkstemp(file);
317     if(fd < 0) {
318         int ret = errno;
319         krb5_set_error_message(context, ret, "mkstemp %s", file);
320         free(f);
321         free(file);
322         return ret;
323     }
324     close(fd);
325     f->filename = file;
326     f->version = 0;
327     (*id)->data.data = f;
328     (*id)->data.length = sizeof(*f);
329     return 0;
330 }
331
332 static void
333 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
334 {
335     int flags = 0;
336     switch(vno) {
337     case KRB5_FCC_FVNO_1:
338         flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
339         flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
340         flags |= KRB5_STORAGE_HOST_BYTEORDER;
341         break;
342     case KRB5_FCC_FVNO_2:
343         flags |= KRB5_STORAGE_HOST_BYTEORDER;
344         break;
345     case KRB5_FCC_FVNO_3:
346         flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
347         break;
348     case KRB5_FCC_FVNO_4:
349         break;
350     default:
351         krb5_abortx(context, 
352                     "storage_set_flags called with bad vno (%x)", vno);
353     }
354     krb5_storage_set_flags(sp, flags);
355 }
356
357 static krb5_error_code
358 fcc_open(krb5_context context,
359          krb5_ccache id,
360          int *fd_ret,
361          int flags,
362          mode_t mode)
363 {
364     krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
365                               (flags | O_RDWR) == flags);
366     krb5_error_code ret;
367     const char *filename = FILENAME(id);
368     int fd;
369     fd = open(filename, flags, mode);
370     if(fd < 0) {
371         ret = errno;
372         krb5_set_error_message(context, ret, "open(%s): %s", filename,
373                                strerror(ret));
374         return ret;
375     }
376     rk_cloexec(fd);
377     
378     if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
379         close(fd);
380         return ret;
381     }
382     *fd_ret = fd;
383     return 0;
384 }
385
386 static krb5_error_code
387 fcc_initialize(krb5_context context,
388                krb5_ccache id,
389                krb5_principal primary_principal)
390 {
391     krb5_fcache *f = FCACHE(id);
392     int ret = 0;
393     int fd;
394     char *filename = f->filename;
395
396     unlink (filename);
397   
398     ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
399     if(ret)
400         return ret;
401     {
402         krb5_storage *sp;    
403         sp = krb5_storage_emem();
404         krb5_storage_set_eof_code(sp, KRB5_CC_END);
405         if(context->fcache_vno != 0)
406             f->version = context->fcache_vno;
407         else
408             f->version = KRB5_FCC_FVNO_4;
409         ret |= krb5_store_int8(sp, 5);
410         ret |= krb5_store_int8(sp, f->version);
411         storage_set_flags(context, sp, f->version);
412         if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
413             /* V4 stuff */
414             if (context->kdc_sec_offset) {
415                 ret |= krb5_store_int16 (sp, 12); /* length */
416                 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
417                 ret |= krb5_store_int16 (sp, 8); /* length of data */
418                 ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
419                 ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
420             } else {
421                 ret |= krb5_store_int16 (sp, 0);
422             }
423         }
424         ret |= krb5_store_principal(sp, primary_principal);
425         
426         ret |= write_storage(context, sp, fd);
427
428         krb5_storage_free(sp);
429     }
430     fcc_unlock(context, fd);
431     if (close(fd) < 0)
432         if (ret == 0) {
433             ret = errno;
434             krb5_set_error_message (context, ret, "close %s: %s", 
435                                     FILENAME(id), strerror(ret));
436         }
437     return ret;
438 }
439
440 static krb5_error_code
441 fcc_close(krb5_context context,
442           krb5_ccache id)
443 {
444     free (FILENAME(id));
445     krb5_data_free(&id->data);
446     return 0;
447 }
448
449 static krb5_error_code
450 fcc_destroy(krb5_context context,
451             krb5_ccache id)
452 {
453     erase_file(context, FILENAME(id));
454     return 0;
455 }
456
457 static krb5_error_code
458 fcc_store_cred(krb5_context context,
459                krb5_ccache id,
460                krb5_creds *creds)
461 {
462     int ret;
463     int fd;
464
465     ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY, 0);
466     if(ret)
467         return ret;
468     {
469         krb5_storage *sp;
470
471         sp = krb5_storage_emem();
472         krb5_storage_set_eof_code(sp, KRB5_CC_END);
473         storage_set_flags(context, sp, FCACHE(id)->version);
474         if (!krb5_config_get_bool_default(context, NULL, TRUE,
475                                           "libdefaults",
476                                           "fcc-mit-ticketflags",
477                                           NULL))
478             krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
479         ret = krb5_store_creds(sp, creds);
480         if (ret == 0)
481             ret = write_storage(context, sp, fd);
482         krb5_storage_free(sp);
483     }
484     fcc_unlock(context, fd);
485     if (close(fd) < 0) {
486         if (ret == 0) {
487             ret = errno;
488             krb5_set_error_message (context, ret, "close %s: %s", 
489                                     FILENAME(id), strerror(ret));
490         }
491     }
492     return ret;
493 }
494
495 static krb5_error_code
496 init_fcc (krb5_context context,
497           krb5_ccache id,
498           krb5_storage **ret_sp,
499           int *ret_fd)
500 {
501     int fd;
502     int8_t pvno, tag;
503     krb5_storage *sp;
504     krb5_error_code ret;
505
506     ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY, 0);
507     if(ret)
508         return ret;
509     
510     sp = krb5_storage_from_fd(fd);
511     if(sp == NULL) {
512         krb5_clear_error_string(context);
513         ret = ENOMEM;
514         goto out;
515     }
516     krb5_storage_set_eof_code(sp, KRB5_CC_END);
517     ret = krb5_ret_int8(sp, &pvno);
518     if(ret != 0) {
519         if(ret == KRB5_CC_END) {
520             ret = ENOENT;
521             krb5_set_error_message(context, ret, 
522                                    "Empty credential cache file: %s",
523                                    FILENAME(id));
524         } else
525             krb5_set_error_message(context, ret,  "Error reading pvno in "
526                                    "cache file: %s", FILENAME(id));
527         goto out;
528     }
529     if(pvno != 5) {
530         ret = KRB5_CCACHE_BADVNO;
531         krb5_set_error_message(context, ret, "Bad version number in "
532                                "credential cache file: %s",
533                                FILENAME(id));
534         goto out;
535     }
536     ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
537     if(ret != 0) {
538         ret = KRB5_CC_FORMAT;
539         krb5_set_error_message(context, ret, "Error reading tag in "
540                               "cache file: %s", FILENAME(id));
541         goto out;
542     }
543     FCACHE(id)->version = tag;
544     storage_set_flags(context, sp, FCACHE(id)->version);
545     switch (tag) {
546     case KRB5_FCC_FVNO_4: {
547         int16_t length;
548
549         ret = krb5_ret_int16 (sp, &length);
550         if(ret) {
551             ret = KRB5_CC_FORMAT;
552             krb5_set_error_message(context, ret, 
553                                    "Error reading tag length in "
554                                    "cache file: %s", FILENAME(id));
555             goto out;
556         }
557         while(length > 0) {
558             int16_t dtag, data_len;
559             int i;
560             int8_t dummy;
561
562             ret = krb5_ret_int16 (sp, &dtag);
563             if(ret) {
564                 ret = KRB5_CC_FORMAT;
565                 krb5_set_error_message(context, ret, "Error reading dtag in "
566                                        "cache file: %s", FILENAME(id));
567                 goto out;
568             }
569             ret = krb5_ret_int16 (sp, &data_len);
570             if(ret) {
571                 ret = KRB5_CC_FORMAT;
572                 krb5_set_error_message(context, ret, "Error reading dlength in "
573                                        "cache file: %s", FILENAME(id));
574                 goto out;
575             }
576             switch (dtag) {
577             case FCC_TAG_DELTATIME :
578                 ret = krb5_ret_int32 (sp, &context->kdc_sec_offset);
579                 if(ret) {
580                     ret = KRB5_CC_FORMAT;
581                     krb5_set_error_message(context, ret, "Error reading kdc_sec in "
582                                            "cache file: %s", FILENAME(id));
583                     goto out;
584                 }
585                 ret = krb5_ret_int32 (sp, &context->kdc_usec_offset);
586                 if(ret) {
587                     ret = KRB5_CC_FORMAT;
588                     krb5_set_error_message(context, ret, "Error reading kdc_usec in "
589                                            "cache file: %s", FILENAME(id));
590                     goto out;
591                 }
592                 break;
593             default :
594                 for (i = 0; i < data_len; ++i) {
595                     ret = krb5_ret_int8 (sp, &dummy);
596                     if(ret) {
597                         ret = KRB5_CC_FORMAT;
598                         krb5_set_error_message(context, ret,
599                                                "Error reading unknown "
600                                                "tag in cache file: %s", 
601                                                FILENAME(id));
602                         goto out;
603                     }
604                 }
605                 break;
606             }
607             length -= 4 + data_len;
608         }
609         break;
610     }
611     case KRB5_FCC_FVNO_3:
612     case KRB5_FCC_FVNO_2:
613     case KRB5_FCC_FVNO_1:
614         break;
615     default :
616         ret = KRB5_CCACHE_BADVNO;
617         krb5_set_error_message(context, ret, "Unknown version number (%d) in "
618                                "credential cache file: %s",
619                                (int)tag, FILENAME(id));
620         goto out;
621     }
622     *ret_sp = sp;
623     *ret_fd = fd;
624     
625     return 0;
626   out:
627     if(sp != NULL)
628         krb5_storage_free(sp);
629     fcc_unlock(context, fd);
630     close(fd);
631     return ret;
632 }
633
634 static krb5_error_code
635 fcc_get_principal(krb5_context context,
636                   krb5_ccache id,
637                   krb5_principal *principal)
638 {
639     krb5_error_code ret;
640     int fd;
641     krb5_storage *sp;
642
643     ret = init_fcc (context, id, &sp, &fd);
644     if (ret)
645         return ret;
646     ret = krb5_ret_principal(sp, principal);
647     if (ret)
648         krb5_clear_error_string(context);
649     krb5_storage_free(sp);
650     fcc_unlock(context, fd);
651     close(fd);
652     return ret;
653 }
654
655 static krb5_error_code
656 fcc_end_get (krb5_context context,
657              krb5_ccache id,
658              krb5_cc_cursor *cursor);
659
660 static krb5_error_code
661 fcc_get_first (krb5_context context,
662                krb5_ccache id,
663                krb5_cc_cursor *cursor)
664 {
665     krb5_error_code ret;
666     krb5_principal principal;
667
668     *cursor = malloc(sizeof(struct fcc_cursor));
669     if (*cursor == NULL) {
670         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
671         return ENOMEM;
672     }
673     memset(*cursor, 0, sizeof(struct fcc_cursor));
674
675     ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp, 
676                     &FCC_CURSOR(*cursor)->fd);
677     if (ret) {
678         free(*cursor);
679         *cursor = NULL;
680         return ret;
681     }
682     ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
683     if(ret) {
684         krb5_clear_error_string(context);
685         fcc_end_get(context, id, cursor);
686         return ret;
687     }
688     krb5_free_principal (context, principal);
689     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
690     return 0;
691 }
692
693 static krb5_error_code
694 fcc_get_next (krb5_context context,
695               krb5_ccache id,
696               krb5_cc_cursor *cursor,
697               krb5_creds *creds)
698 {
699     krb5_error_code ret;
700     if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
701         return ret;
702
703     ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
704     if (ret)
705         krb5_clear_error_string(context);
706
707     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
708     return ret;
709 }
710
711 static krb5_error_code
712 fcc_end_get (krb5_context context,
713              krb5_ccache id,
714              krb5_cc_cursor *cursor)
715 {
716     krb5_storage_free(FCC_CURSOR(*cursor)->sp);
717     close (FCC_CURSOR(*cursor)->fd);
718     free(*cursor);
719     *cursor = NULL;
720     return 0;
721 }
722
723 static krb5_error_code
724 fcc_remove_cred(krb5_context context,
725                  krb5_ccache id,
726                  krb5_flags which,
727                  krb5_creds *cred)
728 {
729     krb5_error_code ret;
730     krb5_ccache copy, newfile;
731
732     ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &copy);
733     if (ret)
734         return ret;
735
736     ret = krb5_cc_copy_cache(context, id, copy);
737     if (ret) {
738         krb5_cc_destroy(context, copy);
739         return ret;
740     }
741
742     ret = krb5_cc_remove_cred(context, copy, which, cred);
743     if (ret) {
744         krb5_cc_destroy(context, copy);
745         return ret;
746     }
747
748     ret = krb5_cc_gen_new(context, &krb5_fcc_ops, &newfile);
749     if (ret) {
750         krb5_cc_destroy(context, copy);
751         return ret;
752     }
753
754     ret = krb5_cc_copy_cache(context, copy, newfile);
755     krb5_cc_destroy(context, copy);
756     if (ret) {
757         krb5_cc_destroy(context, newfile);
758         return ret;
759     }
760
761     return krb5_cc_move(context, newfile, id);
762 }
763
764 static krb5_error_code
765 fcc_set_flags(krb5_context context,
766               krb5_ccache id,
767               krb5_flags flags)
768 {
769     return 0; /* XXX */
770 }
771
772 static int
773 fcc_get_version(krb5_context context,
774                 krb5_ccache id)
775 {
776     return FCACHE(id)->version;
777 }
778                     
779 struct fcache_iter {
780     int first;
781 };
782
783 static krb5_error_code
784 fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
785 {
786     struct fcache_iter *iter;
787
788     iter = calloc(1, sizeof(*iter));
789     if (iter == NULL) {
790         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
791         return ENOMEM;
792     }    
793     iter->first = 1;
794     *cursor = iter;
795     return 0;
796 }
797
798 static krb5_error_code
799 fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
800 {
801     struct fcache_iter *iter = cursor;
802     krb5_error_code ret;
803     const char *fn;
804     char *expandedfn = NULL;
805
806     if (!iter->first) {
807         krb5_clear_error_string(context);
808         return KRB5_CC_END;
809     }
810     iter->first = 0;
811
812     fn = krb5_cc_default_name(context);
813     if (strncasecmp(fn, "FILE:", 5) != 0) {
814         ret = _krb5_expand_default_cc_name(context, 
815                                            KRB5_DEFAULT_CCNAME_FILE,
816                                            &expandedfn);
817         if (ret)
818             return ret;
819     }
820     ret = krb5_cc_resolve(context, fn, id);
821     if (expandedfn)
822         free(expandedfn);
823     
824     return ret;
825 }
826
827 static krb5_error_code
828 fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
829 {
830     struct fcache_iter *iter = cursor;
831     free(iter);
832     return 0;
833 }
834
835 static krb5_error_code
836 fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
837 {
838     krb5_error_code ret = 0;
839
840     ret = rename(FILENAME(from), FILENAME(to));
841     if (ret && errno != EXDEV) {
842         ret = errno;
843         krb5_set_error_message(context, ret,
844                                "Rename of file from %s to %s failed: %s", 
845                                FILENAME(from), FILENAME(to),
846                                strerror(ret));
847         return ret;
848     } else if (ret && errno == EXDEV) {
849         /* make a copy and delete the orignal */
850         krb5_ssize_t sz1, sz2;
851         int fd1, fd2;
852         char buf[BUFSIZ];
853
854         ret = fcc_open(context, from, &fd1, O_RDONLY | O_BINARY, 0);
855         if(ret)
856             return ret;
857
858         unlink(FILENAME(to));
859
860         ret = fcc_open(context, to, &fd2, 
861                        O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0600);
862         if(ret)
863             goto out1;
864
865         while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
866             sz2 = write(fd2, buf, sz1);
867             if (sz1 != sz2) {
868                 ret = EIO;
869                 krb5_set_error_message(context, ret,
870                                        "Failed to write data from one file "
871                                        "credential cache to the other");
872                 goto out2;
873             }
874         }
875         if (sz1 < 0) {
876             ret = EIO;
877             krb5_set_error_message(context, ret,
878                                    "Failed to read data from one file "
879                                    "credential cache to the other");
880             goto out2;
881         }
882     out2:
883         fcc_unlock(context, fd2);
884         close(fd2);
885
886     out1:
887         fcc_unlock(context, fd1);
888         close(fd1);
889
890         erase_file(context, FILENAME(from));
891
892         if (ret) {
893             erase_file(context, FILENAME(to));
894             return ret;
895         }
896     }
897
898     /* make sure ->version is uptodate */
899     {
900         krb5_storage *sp;
901         int fd;
902         ret = init_fcc (context, to, &sp, &fd);
903         krb5_storage_free(sp);
904         fcc_unlock(context, fd);
905         close(fd);
906     }    
907     return ret;
908 }
909
910 static krb5_error_code
911 fcc_default_name(krb5_context context, char **str)
912 {
913     return _krb5_expand_default_cc_name(context, 
914                                         KRB5_DEFAULT_CCNAME_FILE,
915                                         str);
916 }
917
918 /**
919  * Variable containing the FILE based credential cache implemention.
920  *
921  * @ingroup krb5_ccache
922  */
923
924 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
925     KRB5_CC_OPS_VERSION,
926     "FILE",
927     fcc_get_name,
928     fcc_resolve,
929     fcc_gen_new,
930     fcc_initialize,
931     fcc_destroy,
932     fcc_close,
933     fcc_store_cred,
934     NULL, /* fcc_retrieve */
935     fcc_get_principal,
936     fcc_get_first,
937     fcc_get_next,
938     fcc_end_get,
939     fcc_remove_cred,
940     fcc_set_flags,
941     fcc_get_version,
942     fcc_get_cache_first,
943     fcc_get_cache_next,
944     fcc_end_cache_get,
945     fcc_move,
946     fcc_default_name
947 };