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;
227	size_t wr = sizeof(buf);
228	if (wr > pos)
229	    wr = (size_t)pos;
230        tmp = write(fd, buf, wr);
231
232	if (tmp < 0)
233	    return errno;
234	pos -= tmp;
235    }
236#ifdef _MSC_VER
237    _commit (fd);
238#else
239    fsync (fd);
240#endif
241    return 0;
242}
243
244/*
245 * Erase `filename' if it exists, trying to remove the contents if
246 * it's `safe'.  We always try to remove the file, it it exists.  It's
247 * only overwritten if it's a regular file (not a symlink and not a
248 * hardlink)
249 */
250
251krb5_error_code
252_krb5_erase_file(krb5_context context, const char *filename)
253{
254    int fd;
255    struct stat sb1, sb2;
256    int ret;
257
258    ret = lstat (filename, &sb1);
259    if (ret < 0)
260	return errno;
261
262    fd = open(filename, O_RDWR | O_BINARY);
263    if(fd < 0) {
264	if(errno == ENOENT)
265	    return 0;
266	else
267	    return errno;
268    }
269    rk_cloexec(fd);
270    ret = _krb5_xlock(context, fd, 1, filename);
271    if (ret) {
272	close(fd);
273	return ret;
274    }
275    if (unlink(filename) < 0) {
276	_krb5_xunlock(context, fd);
277        close (fd);
278        return errno;
279    }
280    ret = fstat (fd, &sb2);
281    if (ret < 0) {
282	_krb5_xunlock(context, fd);
283	close (fd);
284	return errno;
285    }
286
287    /* check if someone was playing with symlinks */
288
289    if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
290	_krb5_xunlock(context, fd);
291	close (fd);
292	return EPERM;
293    }
294
295    /* there are still hard links to this file */
296
297    if (sb2.st_nlink != 0) {
298	_krb5_xunlock(context, fd);
299        close (fd);
300        return 0;
301    }
302
303    ret = scrub_file (fd);
304    if (ret) {
305	_krb5_xunlock(context, fd);
306	close(fd);
307	return ret;
308    }
309    ret = _krb5_xunlock(context, fd);
310    close (fd);
311    return ret;
312}
313
314static krb5_error_code KRB5_CALLCONV
315fcc_gen_new(krb5_context context, krb5_ccache *id)
316{
317    char *file = NULL, *exp_file = NULL;
318    krb5_error_code ret;
319    krb5_fcache *f;
320    int fd;
321
322    f = malloc(sizeof(*f));
323    if(f == NULL) {
324	krb5_set_error_message(context, KRB5_CC_NOMEM,
325			       N_("malloc: out of memory", ""));
326	return KRB5_CC_NOMEM;
327    }
328    ret = asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
329    if(ret < 0 || file == NULL) {
330	free(f);
331	krb5_set_error_message(context, KRB5_CC_NOMEM,
332			       N_("malloc: out of memory", ""));
333	return KRB5_CC_NOMEM;
334    }
335    ret = _krb5_expand_path_tokens(context, file, &exp_file);
336    free(file);
337    if (ret) {
338	free(f);
339	return ret;
340    }
341
342    file = exp_file;
343
344    fd = mkstemp(exp_file);
345    if(fd < 0) {
346	ret = (krb5_error_code)errno;
347	krb5_set_error_message(context, ret, N_("mkstemp %s failed", ""), exp_file);
348	free(f);
349	free(exp_file);
350	return ret;
351    }
352    close(fd);
353    f->filename = exp_file;
354    f->version = 0;
355    (*id)->data.data = f;
356    (*id)->data.length = sizeof(*f);
357    return 0;
358}
359
360static void
361storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
362{
363    int flags = 0;
364    switch(vno) {
365    case KRB5_FCC_FVNO_1:
366	flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
367	flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
368	flags |= KRB5_STORAGE_HOST_BYTEORDER;
369	break;
370    case KRB5_FCC_FVNO_2:
371	flags |= KRB5_STORAGE_HOST_BYTEORDER;
372	break;
373    case KRB5_FCC_FVNO_3:
374	flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
375	break;
376    case KRB5_FCC_FVNO_4:
377	break;
378    default:
379	krb5_abortx(context,
380		    "storage_set_flags called with bad vno (%x)", vno);
381    }
382    krb5_storage_set_flags(sp, flags);
383}
384
385static krb5_error_code KRB5_CALLCONV
386fcc_open(krb5_context context,
387	 krb5_ccache id,
388	 int *fd_ret,
389	 int flags,
390	 mode_t mode)
391{
392    krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
393			      (flags | O_RDWR) == flags);
394    krb5_error_code ret;
395    const char *filename;
396    int fd;
397
398    if (FCACHE(id) == NULL)
399        return krb5_einval(context, 2);
400
401    filename = FILENAME(id);
402
403    fd = open(filename, flags, mode);
404    if(fd < 0) {
405	char buf[128];
406	ret = errno;
407	rk_strerror_r(ret, buf, sizeof(buf));
408	krb5_set_error_message(context, ret, N_("open(%s): %s", "file, error"),
409			       filename, buf);
410	return ret;
411    }
412    rk_cloexec(fd);
413
414    if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
415	close(fd);
416	return ret;
417    }
418    *fd_ret = fd;
419    return 0;
420}
421
422static krb5_error_code KRB5_CALLCONV
423fcc_initialize(krb5_context context,
424	       krb5_ccache id,
425	       krb5_principal primary_principal)
426{
427    krb5_fcache *f = FCACHE(id);
428    int ret = 0;
429    int fd;
430
431    if (f == NULL)
432        return krb5_einval(context, 2);
433
434    unlink (f->filename);
435
436    ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, 0600);
437    if(ret)
438	return ret;
439    {
440	krb5_storage *sp;
441	sp = krb5_storage_emem();
442	krb5_storage_set_eof_code(sp, KRB5_CC_END);
443	if(context->fcache_vno != 0)
444	    f->version = context->fcache_vno;
445	else
446	    f->version = KRB5_FCC_FVNO_4;
447	ret |= krb5_store_int8(sp, 5);
448	ret |= krb5_store_int8(sp, f->version);
449	storage_set_flags(context, sp, f->version);
450	if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
451	    /* V4 stuff */
452	    if (context->kdc_sec_offset) {
453		ret |= krb5_store_int16 (sp, 12); /* length */
454		ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
455		ret |= krb5_store_int16 (sp, 8); /* length of data */
456		ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
457		ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
458	    } else {
459		ret |= krb5_store_int16 (sp, 0);
460	    }
461	}
462	ret |= krb5_store_principal(sp, primary_principal);
463
464	ret |= write_storage(context, sp, fd);
465
466	krb5_storage_free(sp);
467    }
468    fcc_unlock(context, fd);
469    if (close(fd) < 0)
470	if (ret == 0) {
471	    char buf[128];
472	    ret = errno;
473	    rk_strerror_r(ret, buf, sizeof(buf));
474	    krb5_set_error_message (context, ret, N_("close %s: %s", ""),
475				    FILENAME(id), buf);
476	}
477    return ret;
478}
479
480static krb5_error_code KRB5_CALLCONV
481fcc_close(krb5_context context,
482	  krb5_ccache id)
483{
484    if (FCACHE(id) == NULL)
485        return krb5_einval(context, 2);
486
487    free (FILENAME(id));
488    krb5_data_free(&id->data);
489    return 0;
490}
491
492static krb5_error_code KRB5_CALLCONV
493fcc_destroy(krb5_context context,
494	    krb5_ccache id)
495{
496    if (FCACHE(id) == NULL)
497        return krb5_einval(context, 2);
498
499    _krb5_erase_file(context, FILENAME(id));
500    return 0;
501}
502
503static krb5_error_code KRB5_CALLCONV
504fcc_store_cred(krb5_context context,
505	       krb5_ccache id,
506	       krb5_creds *creds)
507{
508    int ret;
509    int fd;
510
511    ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY | O_CLOEXEC, 0);
512    if(ret)
513	return ret;
514    {
515	krb5_storage *sp;
516
517	sp = krb5_storage_emem();
518	krb5_storage_set_eof_code(sp, KRB5_CC_END);
519	storage_set_flags(context, sp, FCACHE(id)->version);
520	if (!krb5_config_get_bool_default(context, NULL, TRUE,
521					  "libdefaults",
522					  "fcc-mit-ticketflags",
523					  NULL))
524	    krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
525	ret = krb5_store_creds(sp, creds);
526	if (ret == 0)
527	    ret = write_storage(context, sp, fd);
528	krb5_storage_free(sp);
529    }
530    fcc_unlock(context, fd);
531    if (close(fd) < 0) {
532	if (ret == 0) {
533	    char buf[128];
534	    rk_strerror_r(ret, buf, sizeof(buf));
535	    ret = errno;
536	    krb5_set_error_message (context, ret, N_("close %s: %s", ""),
537				    FILENAME(id), buf);
538	}
539    }
540    return ret;
541}
542
543static krb5_error_code
544init_fcc (krb5_context context,
545	  krb5_ccache id,
546	  krb5_storage **ret_sp,
547	  int *ret_fd,
548	  krb5_deltat *kdc_offset)
549{
550    int fd;
551    int8_t pvno, tag;
552    krb5_storage *sp;
553    krb5_error_code ret;
554
555    if (kdc_offset)
556	*kdc_offset = 0;
557
558    ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
559    if(ret)
560	return ret;
561
562    sp = krb5_storage_from_fd(fd);
563    if(sp == NULL) {
564	krb5_clear_error_message(context);
565	ret = ENOMEM;
566	goto out;
567    }
568    krb5_storage_set_eof_code(sp, KRB5_CC_END);
569    ret = krb5_ret_int8(sp, &pvno);
570    if(ret != 0) {
571	if(ret == KRB5_CC_END) {
572	    ret = ENOENT;
573	    krb5_set_error_message(context, ret,
574				   N_("Empty credential cache file: %s", ""),
575				   FILENAME(id));
576	} else
577	    krb5_set_error_message(context, ret, N_("Error reading pvno "
578						    "in cache file: %s", ""),
579				   FILENAME(id));
580	goto out;
581    }
582    if(pvno != 5) {
583	ret = KRB5_CCACHE_BADVNO;
584	krb5_set_error_message(context, ret, N_("Bad version number in credential "
585						"cache file: %s", ""),
586			       FILENAME(id));
587	goto out;
588    }
589    ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
590    if(ret != 0) {
591	ret = KRB5_CC_FORMAT;
592	krb5_set_error_message(context, ret, "Error reading tag in "
593			      "cache file: %s", FILENAME(id));
594	goto out;
595    }
596    FCACHE(id)->version = tag;
597    storage_set_flags(context, sp, FCACHE(id)->version);
598    switch (tag) {
599    case KRB5_FCC_FVNO_4: {
600	int16_t length;
601
602	ret = krb5_ret_int16 (sp, &length);
603	if(ret) {
604	    ret = KRB5_CC_FORMAT;
605	    krb5_set_error_message(context, ret,
606				   N_("Error reading tag length in "
607				      "cache file: %s", ""), FILENAME(id));
608	    goto out;
609	}
610	while(length > 0) {
611	    int16_t dtag, data_len;
612	    int i;
613	    int8_t dummy;
614
615	    ret = krb5_ret_int16 (sp, &dtag);
616	    if(ret) {
617		ret = KRB5_CC_FORMAT;
618		krb5_set_error_message(context, ret, N_("Error reading dtag in "
619							"cache file: %s", ""),
620				       FILENAME(id));
621		goto out;
622	    }
623	    ret = krb5_ret_int16 (sp, &data_len);
624	    if(ret) {
625		ret = KRB5_CC_FORMAT;
626		krb5_set_error_message(context, ret,
627				       N_("Error reading dlength "
628					  "in cache file: %s",""),
629				       FILENAME(id));
630		goto out;
631	    }
632	    switch (dtag) {
633	    case FCC_TAG_DELTATIME : {
634		int32_t offset;
635
636		ret = krb5_ret_int32 (sp, &offset);
637		ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
638		if(ret) {
639		    ret = KRB5_CC_FORMAT;
640		    krb5_set_error_message(context, ret,
641					   N_("Error reading kdc_sec in "
642					      "cache file: %s", ""),
643					   FILENAME(id));
644		    goto out;
645		}
646		context->kdc_sec_offset = offset;
647		if (kdc_offset)
648		    *kdc_offset = offset;
649		break;
650	    }
651	    default :
652		for (i = 0; i < data_len; ++i) {
653		    ret = krb5_ret_int8 (sp, &dummy);
654		    if(ret) {
655			ret = KRB5_CC_FORMAT;
656			krb5_set_error_message(context, ret,
657					       N_("Error reading unknown "
658						  "tag in cache file: %s", ""),
659					       FILENAME(id));
660			goto out;
661		    }
662		}
663		break;
664	    }
665	    length -= 4 + data_len;
666	}
667	break;
668    }
669    case KRB5_FCC_FVNO_3:
670    case KRB5_FCC_FVNO_2:
671    case KRB5_FCC_FVNO_1:
672	break;
673    default :
674	ret = KRB5_CCACHE_BADVNO;
675	krb5_set_error_message(context, ret,
676			       N_("Unknown version number (%d) in "
677				  "credential cache file: %s", ""),
678			       (int)tag, FILENAME(id));
679	goto out;
680    }
681    *ret_sp = sp;
682    *ret_fd = fd;
683
684    return 0;
685  out:
686    if(sp != NULL)
687	krb5_storage_free(sp);
688    fcc_unlock(context, fd);
689    close(fd);
690    return ret;
691}
692
693static krb5_error_code KRB5_CALLCONV
694fcc_get_principal(krb5_context context,
695		  krb5_ccache id,
696		  krb5_principal *principal)
697{
698    krb5_error_code ret;
699    int fd;
700    krb5_storage *sp;
701
702    ret = init_fcc (context, id, &sp, &fd, NULL);
703    if (ret)
704	return ret;
705    ret = krb5_ret_principal(sp, principal);
706    if (ret)
707	krb5_clear_error_message(context);
708    krb5_storage_free(sp);
709    fcc_unlock(context, fd);
710    close(fd);
711    return ret;
712}
713
714static krb5_error_code KRB5_CALLCONV
715fcc_end_get (krb5_context context,
716	     krb5_ccache id,
717	     krb5_cc_cursor *cursor);
718
719static krb5_error_code KRB5_CALLCONV
720fcc_get_first (krb5_context context,
721	       krb5_ccache id,
722	       krb5_cc_cursor *cursor)
723{
724    krb5_error_code ret;
725    krb5_principal principal;
726
727    if (FCACHE(id) == NULL)
728        return krb5_einval(context, 2);
729
730    *cursor = malloc(sizeof(struct fcc_cursor));
731    if (*cursor == NULL) {
732        krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
733	return ENOMEM;
734    }
735    memset(*cursor, 0, sizeof(struct fcc_cursor));
736
737    ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp,
738		    &FCC_CURSOR(*cursor)->fd, NULL);
739    if (ret) {
740	free(*cursor);
741	*cursor = NULL;
742	return ret;
743    }
744    ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
745    if(ret) {
746	krb5_clear_error_message(context);
747	fcc_end_get(context, id, cursor);
748	return ret;
749    }
750    krb5_free_principal (context, principal);
751    fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
752    return 0;
753}
754
755static krb5_error_code KRB5_CALLCONV
756fcc_get_next (krb5_context context,
757	      krb5_ccache id,
758	      krb5_cc_cursor *cursor,
759	      krb5_creds *creds)
760{
761    krb5_error_code ret;
762
763    if (FCACHE(id) == NULL)
764        return krb5_einval(context, 2);
765
766    if (FCC_CURSOR(*cursor) == NULL)
767        return krb5_einval(context, 3);
768
769    if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
770	return ret;
771
772    ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
773    if (ret)
774	krb5_clear_error_message(context);
775
776    fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
777    return ret;
778}
779
780static krb5_error_code KRB5_CALLCONV
781fcc_end_get (krb5_context context,
782	     krb5_ccache id,
783	     krb5_cc_cursor *cursor)
784{
785
786    if (FCACHE(id) == NULL)
787        return krb5_einval(context, 2);
788
789    if (FCC_CURSOR(*cursor) == NULL)
790        return krb5_einval(context, 3);
791
792    krb5_storage_free(FCC_CURSOR(*cursor)->sp);
793    close (FCC_CURSOR(*cursor)->fd);
794    free(*cursor);
795    *cursor = NULL;
796    return 0;
797}
798
799static krb5_error_code KRB5_CALLCONV
800fcc_remove_cred(krb5_context context,
801		 krb5_ccache id,
802		 krb5_flags which,
803		 krb5_creds *cred)
804{
805    krb5_error_code ret;
806    krb5_ccache copy, newfile;
807    char *newname = NULL;
808    int fd;
809
810    if (FCACHE(id) == NULL)
811        return krb5_einval(context, 2);
812
813    ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &copy);
814    if (ret)
815	return ret;
816
817    ret = krb5_cc_copy_cache(context, id, copy);
818    if (ret) {
819	krb5_cc_destroy(context, copy);
820	return ret;
821    }
822
823    ret = krb5_cc_remove_cred(context, copy, which, cred);
824    if (ret) {
825	krb5_cc_destroy(context, copy);
826	return ret;
827    }
828
829    ret = asprintf(&newname, "FILE:%s.XXXXXX", FILENAME(id));
830    if (ret < 0 || newname == NULL) {
831	krb5_cc_destroy(context, copy);
832	return ENOMEM;
833    }
834
835    fd = mkstemp(&newname[5]);
836    if (fd < 0) {
837	ret = errno;
838	krb5_cc_destroy(context, copy);
839	return ret;
840    }
841    close(fd);
842
843    ret = krb5_cc_resolve(context, newname, &newfile);
844    if (ret) {
845	unlink(&newname[5]);
846	free(newname);
847	krb5_cc_destroy(context, copy);
848	return ret;
849    }
850
851    ret = krb5_cc_copy_cache(context, copy, newfile);
852    krb5_cc_destroy(context, copy);
853    if (ret) {
854	free(newname);
855	krb5_cc_destroy(context, newfile);
856	return ret;
857    }
858
859    ret = rk_rename(&newname[5], FILENAME(id));
860    if (ret)
861	ret = errno;
862    free(newname);
863    krb5_cc_close(context, newfile);
864
865    return ret;
866}
867
868static krb5_error_code KRB5_CALLCONV
869fcc_set_flags(krb5_context context,
870	      krb5_ccache id,
871	      krb5_flags flags)
872{
873    if (FCACHE(id) == NULL)
874        return krb5_einval(context, 2);
875
876    return 0; /* XXX */
877}
878
879static int KRB5_CALLCONV
880fcc_get_version(krb5_context context,
881		krb5_ccache id)
882{
883    if (FCACHE(id) == NULL)
884        return -1;
885
886    return FCACHE(id)->version;
887}
888
889struct fcache_iter {
890    int first;
891};
892
893static krb5_error_code KRB5_CALLCONV
894fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
895{
896    struct fcache_iter *iter;
897
898    iter = calloc(1, sizeof(*iter));
899    if (iter == NULL) {
900	krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
901	return ENOMEM;
902    }
903    iter->first = 1;
904    *cursor = iter;
905    return 0;
906}
907
908static krb5_error_code KRB5_CALLCONV
909fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
910{
911    struct fcache_iter *iter = cursor;
912    krb5_error_code ret;
913    const char *fn;
914    char *expandedfn = NULL;
915
916    if (iter == NULL)
917        return krb5_einval(context, 2);
918
919    if (!iter->first) {
920	krb5_clear_error_message(context);
921	return KRB5_CC_END;
922    }
923    iter->first = 0;
924
925    /*
926     * Can't call krb5_cc_default_name here since it refers back to
927     * krb5_cc_cache_match() which will call back into this function.
928     *
929     * Just use the default value if its set, otherwise, use the
930     * default hardcoded value.
931     */
932    fn = context->default_cc_name;
933    if (fn == NULL || strncasecmp(fn, "FILE:", 5) != 0) {
934	ret = _krb5_expand_default_cc_name(context,
935					   KRB5_DEFAULT_CCNAME_FILE,
936					   &expandedfn);
937	if (ret)
938	    return ret;
939	fn = expandedfn;
940    }
941    /* check if file exists, don't return a non existant "next" */
942    if (strncasecmp(fn, "FILE:", 5) == 0) {
943	struct stat sb;
944	ret = stat(fn + 5, &sb);
945	if (ret) {
946	    ret = KRB5_CC_END;
947	    goto out;
948	}
949    }
950    ret = krb5_cc_resolve(context, fn, id);
951 out:
952    if (expandedfn)
953	free(expandedfn);
954
955    return ret;
956}
957
958static krb5_error_code KRB5_CALLCONV
959fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
960{
961    struct fcache_iter *iter = cursor;
962
963    if (iter == NULL)
964        return krb5_einval(context, 2);
965
966    free(iter);
967    return 0;
968}
969
970static krb5_error_code KRB5_CALLCONV
971fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
972{
973    krb5_error_code ret = 0;
974
975    ret = rk_rename(FILENAME(from), FILENAME(to));
976
977    if (ret && errno != EXDEV) {
978	char buf[128];
979	ret = errno;
980	rk_strerror_r(ret, buf, sizeof(buf));
981	krb5_set_error_message(context, ret,
982			       N_("Rename of file from %s "
983				  "to %s failed: %s", ""),
984			       FILENAME(from), FILENAME(to), buf);
985	return ret;
986    } else if (ret && errno == EXDEV) {
987	/* make a copy and delete the orignal */
988	krb5_ssize_t sz1, sz2;
989	int fd1, fd2;
990	char buf[BUFSIZ];
991
992	ret = fcc_open(context, from, &fd1, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
993	if(ret)
994	    return ret;
995
996	unlink(FILENAME(to));
997
998	ret = fcc_open(context, to, &fd2,
999		       O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, 0600);
1000	if(ret)
1001	    goto out1;
1002
1003	while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
1004	    sz2 = write(fd2, buf, sz1);
1005	    if (sz1 != sz2) {
1006		ret = EIO;
1007		krb5_set_error_message(context, ret,
1008				       N_("Failed to write data from one file "
1009					  "credential cache to the other", ""));
1010		goto out2;
1011	    }
1012	}
1013	if (sz1 < 0) {
1014	    ret = EIO;
1015	    krb5_set_error_message(context, ret,
1016				   N_("Failed to read data from one file "
1017				      "credential cache to the other", ""));
1018	    goto out2;
1019	}
1020    out2:
1021	fcc_unlock(context, fd2);
1022	close(fd2);
1023
1024    out1:
1025	fcc_unlock(context, fd1);
1026	close(fd1);
1027
1028	_krb5_erase_file(context, FILENAME(from));
1029
1030	if (ret) {
1031	    _krb5_erase_file(context, FILENAME(to));
1032	    return ret;
1033	}
1034    }
1035
1036    /* make sure ->version is uptodate */
1037    {
1038	krb5_storage *sp;
1039	int fd;
1040	if ((ret = init_fcc (context, to, &sp, &fd, NULL)) == 0) {
1041	    if (sp)
1042		krb5_storage_free(sp);
1043	    fcc_unlock(context, fd);
1044	    close(fd);
1045	}
1046    }
1047
1048    fcc_close(context, from);
1049
1050    return ret;
1051}
1052
1053static krb5_error_code KRB5_CALLCONV
1054fcc_get_default_name(krb5_context context, char **str)
1055{
1056    return _krb5_expand_default_cc_name(context,
1057					KRB5_DEFAULT_CCNAME_FILE,
1058					str);
1059}
1060
1061static krb5_error_code KRB5_CALLCONV
1062fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1063{
1064    krb5_error_code ret;
1065    struct stat sb;
1066    int fd;
1067
1068    ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
1069    if(ret)
1070	return ret;
1071    ret = fstat(fd, &sb);
1072    close(fd);
1073    if (ret) {
1074	ret = errno;
1075	krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1076	return ret;
1077    }
1078    *mtime = sb.st_mtime;
1079    return 0;
1080}
1081
1082static krb5_error_code KRB5_CALLCONV
1083fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1084{
1085    return 0;
1086}
1087
1088static krb5_error_code KRB5_CALLCONV
1089fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1090{
1091    krb5_error_code ret;
1092    krb5_storage *sp = NULL;
1093    int fd;
1094    ret = init_fcc(context, id, &sp, &fd, kdc_offset);
1095    if (sp)
1096	krb5_storage_free(sp);
1097    fcc_unlock(context, fd);
1098    close(fd);
1099
1100    return ret;
1101}
1102
1103
1104/**
1105 * Variable containing the FILE based credential cache implemention.
1106 *
1107 * @ingroup krb5_ccache
1108 */
1109
1110KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1111    KRB5_CC_OPS_VERSION,
1112    "FILE",
1113    fcc_get_name,
1114    fcc_resolve,
1115    fcc_gen_new,
1116    fcc_initialize,
1117    fcc_destroy,
1118    fcc_close,
1119    fcc_store_cred,
1120    NULL, /* fcc_retrieve */
1121    fcc_get_principal,
1122    fcc_get_first,
1123    fcc_get_next,
1124    fcc_end_get,
1125    fcc_remove_cred,
1126    fcc_set_flags,
1127    fcc_get_version,
1128    fcc_get_cache_first,
1129    fcc_get_cache_next,
1130    fcc_end_cache_get,
1131    fcc_move,
1132    fcc_get_default_name,
1133    NULL,
1134    fcc_lastchange,
1135    fcc_set_kdc_offset,
1136    fcc_get_kdc_offset
1137};
1138