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
36RCSID("$Id: fcache.c 22522 2008-01-24 11:56:25Z lha $");
37
38typedef struct krb5_fcache{
39    char *filename;
40    int version;
41}krb5_fcache;
42
43struct 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
61static const char*
62fcc_get_name(krb5_context context,
63	     krb5_ccache id)
64{
65    return FILENAME(id);
66}
67
68int
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
107int
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
137static krb5_error_code
138fcc_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
144static krb5_error_code
145fcc_unlock(krb5_context context, int fd)
146{
147    return _krb5_xunlock(context, fd);
148}
149
150static krb5_error_code
151fcc_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
175static int
176scrub_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
205static krb5_error_code
206erase_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
252static krb5_error_code
253fcc_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
286static void
287storage_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
311static krb5_error_code
312fcc_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
339static krb5_error_code
340fcc_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
391static krb5_error_code
392fcc_close(krb5_context context,
393	  krb5_ccache id)
394{
395    free (FILENAME(id));
396    krb5_data_free(&id->data);
397    return 0;
398}
399
400static krb5_error_code
401fcc_destroy(krb5_context context,
402	    krb5_ccache id)
403{
404    erase_file(FILENAME(id));
405    return 0;
406}
407
408static krb5_error_code
409fcc_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
442static krb5_error_code
443init_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
577static krb5_error_code
578fcc_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
598static krb5_error_code
599fcc_end_get (krb5_context context,
600	     krb5_ccache id,
601	     krb5_cc_cursor *cursor);
602
603static krb5_error_code
604fcc_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
636static krb5_error_code
637fcc_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
654static krb5_error_code
655fcc_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
666static krb5_error_code
667fcc_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
699static krb5_error_code
700fcc_set_flags(krb5_context context,
701	      krb5_ccache id,
702	      krb5_flags flags)
703{
704    return 0; /* XXX */
705}
706
707static krb5_error_code
708fcc_get_version(krb5_context context,
709		krb5_ccache id)
710{
711    return FCACHE(id)->version;
712}
713
714struct fcache_iter {
715    int first;
716};
717
718static krb5_error_code
719fcc_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
733static krb5_error_code
734fcc_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
762static krb5_error_code
763fcc_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
770static krb5_error_code
771fcc_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
845static krb5_error_code
846fcc_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
859const 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