1/*
2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include "krb5_locl.h"
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* KRB5_CALLCONV
62fcc_get_name(krb5_context context,
63	     krb5_ccache id)
64{
65    if (FCACHE(id) == NULL)
66        return NULL;
67
68    return FILENAME(id);
69}
70
71int
72_krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
73	    const char *filename)
74{
75    int ret;
76#ifdef HAVE_FCNTL
77    struct flock l;
78
79    l.l_start = 0;
80    l.l_len = 0;
81    l.l_type = exclusive ? F_WRLCK : F_RDLCK;
82    l.l_whence = SEEK_SET;
83    ret = fcntl(fd, F_SETLKW, &l);
84#else
85    ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
86#endif
87    if(ret < 0)
88	ret = errno;
89    if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
90	ret = EAGAIN;
91
92    switch (ret) {
93    case 0:
94	break;
95    case EINVAL: /* filesystem doesn't support locking, let the user have it */
96	ret = 0;
97	break;
98    case EAGAIN:
99	krb5_set_error_message(context, ret,
100			       N_("timed out locking cache file %s", "file"),
101			       filename);
102	break;
103    default: {
104	char buf[128];
105	rk_strerror_r(ret, buf, sizeof(buf));
106	krb5_set_error_message(context, ret,
107			       N_("error locking cache file %s: %s",
108				  "file, error"), filename, buf);
109	break;
110    }
111    }
112    return ret;
113}
114
115int
116_krb5_xunlock(krb5_context context, int fd)
117{
118    int ret;
119#ifdef HAVE_FCNTL
120    struct flock l;
121    l.l_start = 0;
122    l.l_len = 0;
123    l.l_type = F_UNLCK;
124    l.l_whence = SEEK_SET;
125    ret = fcntl(fd, F_SETLKW, &l);
126#else
127    ret = flock(fd, LOCK_UN);
128#endif
129    if (ret < 0)
130	ret = errno;
131    switch (ret) {
132    case 0:
133	break;
134    case EINVAL: /* filesystem doesn't support locking, let the user have it */
135	ret = 0;
136	break;
137    default: {
138	char buf[128];
139	rk_strerror_r(ret, buf, sizeof(buf));
140	krb5_set_error_message(context, ret,
141			       N_("Failed to unlock file: %s", ""), buf);
142	break;
143    }
144    }
145    return ret;
146}
147
148static krb5_error_code
149write_storage(krb5_context context, krb5_storage *sp, int fd)
150{
151    krb5_error_code ret;
152    krb5_data data;
153    ssize_t sret;
154
155    ret = krb5_storage_to_data(sp, &data);
156    if (ret) {
157	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
158	return ret;
159    }
160    sret = write(fd, data.data, data.length);
161    ret = (sret != (ssize_t)data.length);
162    krb5_data_free(&data);
163    if (ret) {
164	ret = errno;
165	krb5_set_error_message(context, ret,
166			       N_("Failed to write FILE credential data", ""));
167	return ret;
168    }
169    return 0;
170}
171
172
173static krb5_error_code KRB5_CALLCONV
174fcc_lock(krb5_context context, krb5_ccache id,
175	 int fd, krb5_boolean exclusive)
176{
177    return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
178}
179
180static krb5_error_code KRB5_CALLCONV
181fcc_unlock(krb5_context context, int fd)
182{
183    return _krb5_xunlock(context, fd);
184}
185
186static krb5_error_code KRB5_CALLCONV
187fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
188{
189    krb5_fcache *f;
190    f = malloc(sizeof(*f));
191    if(f == NULL) {
192	krb5_set_error_message(context, KRB5_CC_NOMEM,
193			       N_("malloc: out of memory", ""));
194	return KRB5_CC_NOMEM;
195    }
196    f->filename = strdup(res);
197    if(f->filename == NULL){
198	free(f);
199	krb5_set_error_message(context, KRB5_CC_NOMEM,
200			       N_("malloc: out of memory", ""));
201	return KRB5_CC_NOMEM;
202    }
203    f->version = 0;
204    (*id)->data.data = f;
205    (*id)->data.length = sizeof(*f);
206    return 0;
207}
208
209/*
210 * Try to scrub the contents of `filename' safely.
211 */
212
213static int
214scrub_file (int fd)
215{
216    off_t pos;
217    char buf[128];
218
219    pos = lseek(fd, 0, SEEK_END);
220    if (pos < 0)
221        return errno;
222    if (lseek(fd, 0, SEEK_SET) < 0)
223        return errno;
224    memset(buf, 0, sizeof(buf));
225    while(pos > 0) {
226        ssize_t tmp = write(fd, buf, min((off_t)sizeof(buf), pos));
227
228	if (tmp < 0)
229	    return errno;
230	pos -= tmp;
231    }
232#ifdef _MSC_VER
233    _commit (fd);
234#else
235    fsync (fd);
236#endif
237    return 0;
238}
239
240/*
241 * Erase `filename' if it exists, trying to remove the contents if
242 * it's `safe'.  We always try to remove the file, it it exists.  It's
243 * only overwritten if it's a regular file (not a symlink and not a
244 * hardlink)
245 */
246
247krb5_error_code
248_krb5_erase_file(krb5_context context, const char *filename)
249{
250    int fd;
251    struct stat sb1, sb2;
252    int ret;
253
254    ret = lstat (filename, &sb1);
255    if (ret < 0)
256	return errno;
257
258    fd = open(filename, O_RDWR | O_BINARY);
259    if(fd < 0) {
260	if(errno == ENOENT)
261	    return 0;
262	else
263	    return errno;
264    }
265    rk_cloexec(fd);
266    ret = _krb5_xlock(context, fd, 1, filename);
267    if (ret) {
268	close(fd);
269	return ret;
270    }
271    if (unlink(filename) < 0) {
272	_krb5_xunlock(context, fd);
273        close (fd);
274        return errno;
275    }
276    ret = fstat (fd, &sb2);
277    if (ret < 0) {
278	_krb5_xunlock(context, fd);
279	close (fd);
280	return errno;
281    }
282
283    /* check if someone was playing with symlinks */
284
285    if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
286	_krb5_xunlock(context, fd);
287	close (fd);
288	return EPERM;
289    }
290
291    /* there are still hard links to this file */
292
293    if (sb2.st_nlink != 0) {
294	_krb5_xunlock(context, fd);
295        close (fd);
296        return 0;
297    }
298
299    ret = scrub_file (fd);
300    if (ret) {
301	_krb5_xunlock(context, fd);
302	close(fd);
303	return ret;
304    }
305    ret = _krb5_xunlock(context, fd);
306    close (fd);
307    return ret;
308}
309
310static krb5_error_code KRB5_CALLCONV
311fcc_gen_new(krb5_context context, krb5_ccache *id)
312{
313    char *file = NULL, *exp_file = NULL;
314    krb5_error_code ret;
315    krb5_fcache *f;
316    int fd;
317
318    f = malloc(sizeof(*f));
319    if(f == NULL) {
320	krb5_set_error_message(context, KRB5_CC_NOMEM,
321			       N_("malloc: out of memory", ""));
322	return KRB5_CC_NOMEM;
323    }
324    ret = asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
325    if(ret < 0 || file == NULL) {
326	free(f);
327	krb5_set_error_message(context, KRB5_CC_NOMEM,
328			       N_("malloc: out of memory", ""));
329	return KRB5_CC_NOMEM;
330    }
331    ret = _krb5_expand_path_tokens(context, file, &exp_file);
332    free(file);
333    if (ret)
334	return ret;
335
336    file = exp_file;
337
338    fd = mkstemp(exp_file);
339    if(fd < 0) {
340	int xret = errno;
341	krb5_set_error_message(context, xret, N_("mkstemp %s failed", ""), exp_file);
342	free(f);
343	free(exp_file);
344	return xret;
345    }
346    close(fd);
347    f->filename = exp_file;
348    f->version = 0;
349    (*id)->data.data = f;
350    (*id)->data.length = sizeof(*f);
351    return 0;
352}
353
354static void
355storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
356{
357    int flags = 0;
358    switch(vno) {
359    case KRB5_FCC_FVNO_1:
360	flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
361	flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
362	flags |= KRB5_STORAGE_HOST_BYTEORDER;
363	break;
364    case KRB5_FCC_FVNO_2:
365	flags |= KRB5_STORAGE_HOST_BYTEORDER;
366	break;
367    case KRB5_FCC_FVNO_3:
368	flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
369	break;
370    case KRB5_FCC_FVNO_4:
371	break;
372    default:
373	krb5_abortx(context,
374		    "storage_set_flags called with bad vno (%x)", vno);
375    }
376    krb5_storage_set_flags(sp, flags);
377}
378
379static krb5_error_code KRB5_CALLCONV
380fcc_open(krb5_context context,
381	 krb5_ccache id,
382	 int *fd_ret,
383	 int flags,
384	 mode_t mode)
385{
386    krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
387			      (flags | O_RDWR) == flags);
388    krb5_error_code ret;
389    const char *filename;
390    int fd;
391
392    if (FCACHE(id) == NULL)
393        return krb5_einval(context, 2);
394
395    filename = FILENAME(id);
396
397    fd = open(filename, flags, mode);
398    if(fd < 0) {
399	char buf[128];
400	ret = errno;
401	rk_strerror_r(ret, buf, sizeof(buf));
402	krb5_set_error_message(context, ret, N_("open(%s): %s", "file, error"),
403			       filename, buf);
404	return ret;
405    }
406    rk_cloexec(fd);
407
408    if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
409	close(fd);
410	return ret;
411    }
412    *fd_ret = fd;
413    return 0;
414}
415
416static krb5_error_code KRB5_CALLCONV
417fcc_initialize(krb5_context context,
418	       krb5_ccache id,
419	       krb5_principal primary_principal)
420{
421    krb5_fcache *f = FCACHE(id);
422    int ret = 0;
423    int fd;
424
425    if (f == NULL)
426        return krb5_einval(context, 2);
427
428    unlink (f->filename);
429
430    ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, 0600);
431    if(ret)
432	return ret;
433    {
434	krb5_storage *sp;
435	sp = krb5_storage_emem();
436	krb5_storage_set_eof_code(sp, KRB5_CC_END);
437	if(context->fcache_vno != 0)
438	    f->version = context->fcache_vno;
439	else
440	    f->version = KRB5_FCC_FVNO_4;
441	ret |= krb5_store_int8(sp, 5);
442	ret |= krb5_store_int8(sp, f->version);
443	storage_set_flags(context, sp, f->version);
444	if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
445	    /* V4 stuff */
446	    if (context->kdc_sec_offset) {
447		ret |= krb5_store_int16 (sp, 12); /* length */
448		ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
449		ret |= krb5_store_int16 (sp, 8); /* length of data */
450		ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
451		ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
452	    } else {
453		ret |= krb5_store_int16 (sp, 0);
454	    }
455	}
456	ret |= krb5_store_principal(sp, primary_principal);
457
458	ret |= write_storage(context, sp, fd);
459
460	krb5_storage_free(sp);
461    }
462    fcc_unlock(context, fd);
463    if (close(fd) < 0)
464	if (ret == 0) {
465	    char buf[128];
466	    ret = errno;
467	    rk_strerror_r(ret, buf, sizeof(buf));
468	    krb5_set_error_message (context, ret, N_("close %s: %s", ""),
469				    FILENAME(id), buf);
470	}
471    return ret;
472}
473
474static krb5_error_code KRB5_CALLCONV
475fcc_close(krb5_context context,
476	  krb5_ccache id)
477{
478    if (FCACHE(id) == NULL)
479        return krb5_einval(context, 2);
480
481    free (FILENAME(id));
482    krb5_data_free(&id->data);
483    return 0;
484}
485
486static krb5_error_code KRB5_CALLCONV
487fcc_destroy(krb5_context context,
488	    krb5_ccache id)
489{
490    if (FCACHE(id) == NULL)
491        return krb5_einval(context, 2);
492
493    _krb5_erase_file(context, FILENAME(id));
494    return 0;
495}
496
497static krb5_error_code KRB5_CALLCONV
498fcc_store_cred(krb5_context context,
499	       krb5_ccache id,
500	       krb5_creds *creds)
501{
502    int ret;
503    int fd;
504
505    ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY | O_CLOEXEC, 0);
506    if(ret)
507	return ret;
508    {
509	krb5_storage *sp;
510
511	sp = krb5_storage_emem();
512	krb5_storage_set_eof_code(sp, KRB5_CC_END);
513	storage_set_flags(context, sp, FCACHE(id)->version);
514	if (!krb5_config_get_bool_default(context, NULL, TRUE,
515					  "libdefaults",
516					  "fcc-mit-ticketflags",
517					  NULL))
518	    krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
519	ret = krb5_store_creds(sp, creds);
520	if (ret == 0)
521	    ret = write_storage(context, sp, fd);
522	krb5_storage_free(sp);
523    }
524    fcc_unlock(context, fd);
525    if (close(fd) < 0) {
526	if (ret == 0) {
527	    char buf[128];
528	    rk_strerror_r(ret, buf, sizeof(buf));
529	    ret = errno;
530	    krb5_set_error_message (context, ret, N_("close %s: %s", ""),
531				    FILENAME(id), buf);
532	}
533    }
534    return ret;
535}
536
537static krb5_error_code
538init_fcc (krb5_context context,
539	  krb5_ccache id,
540	  krb5_storage **ret_sp,
541	  int *ret_fd,
542	  krb5_deltat *kdc_offset)
543{
544    int fd;
545    int8_t pvno, tag;
546    krb5_storage *sp;
547    krb5_error_code ret;
548
549    if (kdc_offset)
550	*kdc_offset = 0;
551
552    ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
553    if(ret)
554	return ret;
555
556    sp = krb5_storage_from_fd(fd);
557    if(sp == NULL) {
558	krb5_clear_error_message(context);
559	ret = ENOMEM;
560	goto out;
561    }
562    krb5_storage_set_eof_code(sp, KRB5_CC_END);
563    ret = krb5_ret_int8(sp, &pvno);
564    if(ret != 0) {
565	if(ret == KRB5_CC_END) {
566	    ret = ENOENT;
567	    krb5_set_error_message(context, ret,
568				   N_("Empty credential cache file: %s", ""),
569				   FILENAME(id));
570	} else
571	    krb5_set_error_message(context, ret, N_("Error reading pvno "
572						    "in cache file: %s", ""),
573				   FILENAME(id));
574	goto out;
575    }
576    if(pvno != 5) {
577	ret = KRB5_CCACHE_BADVNO;
578	krb5_set_error_message(context, ret, N_("Bad version number in credential "
579						"cache file: %s", ""),
580			       FILENAME(id));
581	goto out;
582    }
583    ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
584    if(ret != 0) {
585	ret = KRB5_CC_FORMAT;
586	krb5_set_error_message(context, ret, "Error reading tag in "
587			      "cache file: %s", FILENAME(id));
588	goto out;
589    }
590    FCACHE(id)->version = tag;
591    storage_set_flags(context, sp, FCACHE(id)->version);
592    switch (tag) {
593    case KRB5_FCC_FVNO_4: {
594	int16_t length;
595
596	ret = krb5_ret_int16 (sp, &length);
597	if(ret) {
598	    ret = KRB5_CC_FORMAT;
599	    krb5_set_error_message(context, ret,
600				   N_("Error reading tag length in "
601				      "cache file: %s", ""), FILENAME(id));
602	    goto out;
603	}
604	while(length > 0) {
605	    int16_t dtag, data_len;
606	    int i;
607	    int8_t dummy;
608
609	    ret = krb5_ret_int16 (sp, &dtag);
610	    if(ret) {
611		ret = KRB5_CC_FORMAT;
612		krb5_set_error_message(context, ret, N_("Error reading dtag in "
613							"cache file: %s", ""),
614				       FILENAME(id));
615		goto out;
616	    }
617	    ret = krb5_ret_int16 (sp, &data_len);
618	    if(ret) {
619		ret = KRB5_CC_FORMAT;
620		krb5_set_error_message(context, ret,
621				       N_("Error reading dlength "
622					  "in cache file: %s",""),
623				       FILENAME(id));
624		goto out;
625	    }
626	    switch (dtag) {
627	    case FCC_TAG_DELTATIME : {
628		int32_t offset;
629
630		ret = krb5_ret_int32 (sp, &offset);
631		ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
632		if(ret) {
633		    ret = KRB5_CC_FORMAT;
634		    krb5_set_error_message(context, ret,
635					   N_("Error reading kdc_sec in "
636					      "cache file: %s", ""),
637					   FILENAME(id));
638		    goto out;
639		}
640		context->kdc_sec_offset = offset;
641		if (kdc_offset)
642		    *kdc_offset = offset;
643		break;
644	    }
645	    default :
646		for (i = 0; i < data_len; ++i) {
647		    ret = krb5_ret_int8 (sp, &dummy);
648		    if(ret) {
649			ret = KRB5_CC_FORMAT;
650			krb5_set_error_message(context, ret,
651					       N_("Error reading unknown "
652						  "tag in cache file: %s", ""),
653					       FILENAME(id));
654			goto out;
655		    }
656		}
657		break;
658	    }
659	    length -= 4 + data_len;
660	}
661	break;
662    }
663    case KRB5_FCC_FVNO_3:
664    case KRB5_FCC_FVNO_2:
665    case KRB5_FCC_FVNO_1:
666	break;
667    default :
668	ret = KRB5_CCACHE_BADVNO;
669	krb5_set_error_message(context, ret,
670			       N_("Unknown version number (%d) in "
671				  "credential cache file: %s", ""),
672			       (int)tag, FILENAME(id));
673	goto out;
674    }
675    *ret_sp = sp;
676    *ret_fd = fd;
677
678    return 0;
679  out:
680    if(sp != NULL)
681	krb5_storage_free(sp);
682    fcc_unlock(context, fd);
683    close(fd);
684    return ret;
685}
686
687static krb5_error_code KRB5_CALLCONV
688fcc_get_principal(krb5_context context,
689		  krb5_ccache id,
690		  krb5_principal *principal)
691{
692    krb5_error_code ret;
693    int fd;
694    krb5_storage *sp;
695
696    ret = init_fcc (context, id, &sp, &fd, NULL);
697    if (ret)
698	return ret;
699    ret = krb5_ret_principal(sp, principal);
700    if (ret)
701	krb5_clear_error_message(context);
702    krb5_storage_free(sp);
703    fcc_unlock(context, fd);
704    close(fd);
705    return ret;
706}
707
708static krb5_error_code KRB5_CALLCONV
709fcc_end_get (krb5_context context,
710	     krb5_ccache id,
711	     krb5_cc_cursor *cursor);
712
713static krb5_error_code KRB5_CALLCONV
714fcc_get_first (krb5_context context,
715	       krb5_ccache id,
716	       krb5_cc_cursor *cursor)
717{
718    krb5_error_code ret;
719    krb5_principal principal;
720
721    if (FCACHE(id) == NULL)
722        return krb5_einval(context, 2);
723
724    *cursor = malloc(sizeof(struct fcc_cursor));
725    if (*cursor == NULL) {
726        krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
727	return ENOMEM;
728    }
729    memset(*cursor, 0, sizeof(struct fcc_cursor));
730
731    ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp,
732		    &FCC_CURSOR(*cursor)->fd, NULL);
733    if (ret) {
734	free(*cursor);
735	*cursor = NULL;
736	return ret;
737    }
738    ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
739    if(ret) {
740	krb5_clear_error_message(context);
741	fcc_end_get(context, id, cursor);
742	return ret;
743    }
744    krb5_free_principal (context, principal);
745    fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
746    return 0;
747}
748
749static krb5_error_code KRB5_CALLCONV
750fcc_get_next (krb5_context context,
751	      krb5_ccache id,
752	      krb5_cc_cursor *cursor,
753	      krb5_creds *creds)
754{
755    krb5_error_code ret;
756
757    if (FCACHE(id) == NULL)
758        return krb5_einval(context, 2);
759
760    if (FCC_CURSOR(*cursor) == NULL)
761        return krb5_einval(context, 3);
762
763    if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
764	return ret;
765
766    ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
767    if (ret)
768	krb5_clear_error_message(context);
769
770    fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
771    return ret;
772}
773
774static krb5_error_code KRB5_CALLCONV
775fcc_end_get (krb5_context context,
776	     krb5_ccache id,
777	     krb5_cc_cursor *cursor)
778{
779
780    if (FCACHE(id) == NULL)
781        return krb5_einval(context, 2);
782
783    if (FCC_CURSOR(*cursor) == NULL)
784        return krb5_einval(context, 3);
785
786    krb5_storage_free(FCC_CURSOR(*cursor)->sp);
787    close (FCC_CURSOR(*cursor)->fd);
788    free(*cursor);
789    *cursor = NULL;
790    return 0;
791}
792
793static krb5_error_code KRB5_CALLCONV
794fcc_remove_cred(krb5_context context,
795		 krb5_ccache id,
796		 krb5_flags which,
797		 krb5_creds *cred)
798{
799    krb5_error_code ret;
800    krb5_ccache copy, newfile;
801    char *newname = NULL;
802    int fd;
803
804    if (FCACHE(id) == NULL)
805        return krb5_einval(context, 2);
806
807    ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &copy);
808    if (ret)
809	return ret;
810
811    ret = krb5_cc_copy_cache(context, id, copy);
812    if (ret) {
813	krb5_cc_destroy(context, copy);
814	return ret;
815    }
816
817    ret = krb5_cc_remove_cred(context, copy, which, cred);
818    if (ret) {
819	krb5_cc_destroy(context, copy);
820	return ret;
821    }
822
823    ret = asprintf(&newname, "FILE:%s.XXXXXX", FILENAME(id));
824    if (ret < 0 || newname == NULL) {
825	krb5_cc_destroy(context, copy);
826	return ENOMEM;
827    }
828
829    fd = mkstemp(&newname[5]);
830    if (fd < 0) {
831	ret = errno;
832	krb5_cc_destroy(context, copy);
833	return ret;
834    }
835    close(fd);
836
837    ret = krb5_cc_resolve(context, newname, &newfile);
838    if (ret) {
839	unlink(&newname[5]);
840	free(newname);
841	krb5_cc_destroy(context, copy);
842	return ret;
843    }
844
845    ret = krb5_cc_copy_cache(context, copy, newfile);
846    krb5_cc_destroy(context, copy);
847    if (ret) {
848	free(newname);
849	krb5_cc_destroy(context, newfile);
850	return ret;
851    }
852
853    ret = rk_rename(&newname[5], FILENAME(id));
854    if (ret)
855	ret = errno;
856    free(newname);
857    krb5_cc_close(context, newfile);
858
859    return ret;
860}
861
862static krb5_error_code KRB5_CALLCONV
863fcc_set_flags(krb5_context context,
864	      krb5_ccache id,
865	      krb5_flags flags)
866{
867    if (FCACHE(id) == NULL)
868        return krb5_einval(context, 2);
869
870    return 0; /* XXX */
871}
872
873static int KRB5_CALLCONV
874fcc_get_version(krb5_context context,
875		krb5_ccache id)
876{
877    if (FCACHE(id) == NULL)
878        return -1;
879
880    return FCACHE(id)->version;
881}
882
883struct fcache_iter {
884    int first;
885};
886
887static krb5_error_code KRB5_CALLCONV
888fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
889{
890    struct fcache_iter *iter;
891
892    iter = calloc(1, sizeof(*iter));
893    if (iter == NULL) {
894	krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
895	return ENOMEM;
896    }
897    iter->first = 1;
898    *cursor = iter;
899    return 0;
900}
901
902static krb5_error_code KRB5_CALLCONV
903fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
904{
905    struct fcache_iter *iter = cursor;
906    krb5_error_code ret;
907    const char *fn;
908    char *expandedfn = NULL;
909
910    if (iter == NULL)
911        return krb5_einval(context, 2);
912
913    if (!iter->first) {
914	krb5_clear_error_message(context);
915	return KRB5_CC_END;
916    }
917    iter->first = 0;
918
919    fn = krb5_cc_default_name(context);
920    if (fn == NULL || strncasecmp(fn, "FILE:", 5) != 0) {
921	ret = _krb5_expand_default_cc_name(context,
922					   KRB5_DEFAULT_CCNAME_FILE,
923					   &expandedfn);
924	if (ret)
925	    return ret;
926	fn = expandedfn;
927    }
928    /* check if file exists, don't return a non existant "next" */
929    if (strncasecmp(fn, "FILE:", 5) == 0) {
930	struct stat sb;
931	ret = stat(fn + 5, &sb);
932	if (ret) {
933	    ret = KRB5_CC_END;
934	    goto out;
935	}
936    }
937    ret = krb5_cc_resolve(context, fn, id);
938 out:
939    if (expandedfn)
940	free(expandedfn);
941
942    return ret;
943}
944
945static krb5_error_code KRB5_CALLCONV
946fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
947{
948    struct fcache_iter *iter = cursor;
949
950    if (iter == NULL)
951        return krb5_einval(context, 2);
952
953    free(iter);
954    return 0;
955}
956
957static krb5_error_code KRB5_CALLCONV
958fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
959{
960    krb5_error_code ret = 0;
961
962    ret = rk_rename(FILENAME(from), FILENAME(to));
963
964    if (ret && errno != EXDEV) {
965	char buf[128];
966	ret = errno;
967	rk_strerror_r(ret, buf, sizeof(buf));
968	krb5_set_error_message(context, ret,
969			       N_("Rename of file from %s "
970				  "to %s failed: %s", ""),
971			       FILENAME(from), FILENAME(to), buf);
972	return ret;
973    } else if (ret && errno == EXDEV) {
974	/* make a copy and delete the orignal */
975	krb5_ssize_t sz1, sz2;
976	int fd1, fd2;
977	char buf[BUFSIZ];
978
979	ret = fcc_open(context, from, &fd1, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
980	if(ret)
981	    return ret;
982
983	unlink(FILENAME(to));
984
985	ret = fcc_open(context, to, &fd2,
986		       O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, 0600);
987	if(ret)
988	    goto out1;
989
990	while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
991	    sz2 = write(fd2, buf, sz1);
992	    if (sz1 != sz2) {
993		ret = EIO;
994		krb5_set_error_message(context, ret,
995				       N_("Failed to write data from one file "
996					  "credential cache to the other", ""));
997		goto out2;
998	    }
999	}
1000	if (sz1 < 0) {
1001	    ret = EIO;
1002	    krb5_set_error_message(context, ret,
1003				   N_("Failed to read data from one file "
1004				      "credential cache to the other", ""));
1005	    goto out2;
1006	}
1007    out2:
1008	fcc_unlock(context, fd2);
1009	close(fd2);
1010
1011    out1:
1012	fcc_unlock(context, fd1);
1013	close(fd1);
1014
1015	_krb5_erase_file(context, FILENAME(from));
1016
1017	if (ret) {
1018	    _krb5_erase_file(context, FILENAME(to));
1019	    return ret;
1020	}
1021    }
1022
1023    /* make sure ->version is uptodate */
1024    {
1025	krb5_storage *sp;
1026	int fd;
1027	if ((ret = init_fcc (context, to, &sp, &fd, NULL)) == 0) {
1028	    if (sp)
1029		krb5_storage_free(sp);
1030	    fcc_unlock(context, fd);
1031	    close(fd);
1032	}
1033    }
1034
1035    fcc_close(context, from);
1036
1037    return ret;
1038}
1039
1040static krb5_error_code KRB5_CALLCONV
1041fcc_get_default_name(krb5_context context, char **str)
1042{
1043    return _krb5_expand_default_cc_name(context,
1044					KRB5_DEFAULT_CCNAME_FILE,
1045					str);
1046}
1047
1048static krb5_error_code KRB5_CALLCONV
1049fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1050{
1051    krb5_error_code ret;
1052    struct stat sb;
1053    int fd;
1054
1055    ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
1056    if(ret)
1057	return ret;
1058    ret = fstat(fd, &sb);
1059    close(fd);
1060    if (ret) {
1061	ret = errno;
1062	krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1063	return ret;
1064    }
1065    *mtime = sb.st_mtime;
1066    return 0;
1067}
1068
1069static krb5_error_code KRB5_CALLCONV
1070fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1071{
1072    return 0;
1073}
1074
1075static krb5_error_code KRB5_CALLCONV
1076fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1077{
1078    krb5_error_code ret;
1079    krb5_storage *sp = NULL;
1080    int fd;
1081    ret = init_fcc(context, id, &sp, &fd, kdc_offset);
1082    if (sp)
1083	krb5_storage_free(sp);
1084    fcc_unlock(context, fd);
1085    close(fd);
1086
1087    return ret;
1088}
1089
1090
1091/**
1092 * Variable containing the FILE based credential cache implemention.
1093 *
1094 * @ingroup krb5_ccache
1095 */
1096
1097KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1098    KRB5_CC_OPS_VERSION,
1099    "FILE",
1100    fcc_get_name,
1101    fcc_resolve,
1102    fcc_gen_new,
1103    fcc_initialize,
1104    fcc_destroy,
1105    fcc_close,
1106    fcc_store_cred,
1107    NULL, /* fcc_retrieve */
1108    fcc_get_principal,
1109    fcc_get_first,
1110    fcc_get_next,
1111    fcc_end_get,
1112    fcc_remove_cred,
1113    fcc_set_flags,
1114    fcc_get_version,
1115    fcc_get_cache_first,
1116    fcc_get_cache_next,
1117    fcc_end_cache_get,
1118    fcc_move,
1119    fcc_get_default_name,
1120    NULL,
1121    fcc_lastchange,
1122    fcc_set_kdc_offset,
1123    fcc_get_kdc_offset
1124};
1125