Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / lib / krb5 / ccache / cc_file.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/cc_file.c - File-based credential cache */
3 /*
4  * Copyright 1990,1991,1992,1993,1994,2000,2004,2007 Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Original stdio support copyright 1995 by Cygnus Support.
8  *
9  * Export of this software from the United States of America may
10  *   require a specific license from the United States Government.
11  *   It is the responsibility of any person or organization contemplating
12  *   export to obtain such a license before exporting.
13  *
14  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
15  * distribute this software and its documentation for any purpose and
16  * without fee is hereby granted, provided that the above copyright
17  * notice appear in all copies and that both that copyright notice and
18  * this permission notice appear in supporting documentation, and that
19  * the name of M.I.T. not be used in advertising or publicity pertaining
20  * to distribution of the software without specific, written prior
21  * permission.  Furthermore if you modify this software you must label
22  * your software as modified software and not distribute it in such a
23  * fashion that it might be confused with the original M.I.T. software.
24  * M.I.T. makes no representations about the suitability of
25  * this software for any purpose.  It is provided "as is" without express
26  * or implied warranty.
27  */
28
29 /*
30  * A psuedo-BNF grammar for the FILE credential cache format is:
31  *
32  * file ::=
33  *   version (2 bytes; 05 01 for version 1 through 05 04 for version 4)
34  *   header [not present before version 4]
35  *   principal
36  *   credential1
37  *   credential2
38  *   ...
39  *
40  * header ::=
41  *   headerlen (16 bits)
42  *   header1tag (16 bits)
43  *   header1len (16 bits)
44  *   header1val (header1len bytes)
45  *
46  * See ccmarshal.c for the principal and credential formats.  Although versions
47  * 1 and 2 of the FILE format use native byte order for integer representations
48  * within principals and credentials, the integer fields in the grammar above
49  * are always in big-endian byte order.
50  *
51  * Only one header tag is currently defined.  The tag value is 1
52  * (FCC_TAG_DELTATIME), and its contents are two 32-bit integers giving the
53  * seconds and microseconds of the time offset of the KDC relative to the
54  * client.
55  *
56  * Each of the file ccache functions opens and closes the file whenever it
57  * needs to access it.
58  *
59  * This module depends on UNIX-like file descriptors, and UNIX-like behavior
60  * from the functions: open, close, read, write, lseek.
61  */
62
63 #include "k5-int.h"
64 #include "cc-int.h"
65
66 #include <stdio.h>
67 #include <errno.h>
68
69 #if HAVE_UNISTD_H
70 #include <unistd.h>
71 #endif
72
73 #ifndef O_CLOEXEC
74 #define O_CLOEXEC 0
75 #endif
76
77 extern const krb5_cc_ops krb5_cc_file_ops;
78
79 krb5_error_code krb5_change_cache(void);
80
81 static krb5_error_code interpret_errno(krb5_context, int);
82
83 /* The cache format version is a positive integer, represented in the cache
84  * file as a two-byte big endian number with 0x0500 added to it. */
85 #define FVNO_BASE 0x0500
86
87 #define FCC_TAG_DELTATIME       1
88
89 #ifndef TKT_ROOT
90 #ifdef MSDOS_FILESYSTEM
91 #define TKT_ROOT "\\tkt"
92 #else
93 #define TKT_ROOT "/tmp/tkt"
94 #endif
95 #endif
96
97 typedef struct fcc_data_st {
98     k5_cc_mutex lock;
99     char *filename;
100 } fcc_data;
101
102 /* Iterator over file caches.  */
103 struct krb5_fcc_ptcursor_data {
104     krb5_boolean first;
105 };
106
107 /* Iterator over a cache. */
108 typedef struct _krb5_fcc_cursor {
109     FILE *fp;
110     int version;
111 } krb5_fcc_cursor;
112
113 k5_cc_mutex krb5int_cc_file_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
114
115 /* Add fname to the standard error message for ret. */
116 static krb5_error_code
117 set_errmsg_filename(krb5_context context, krb5_error_code ret,
118                     const char *fname)
119 {
120     if (!ret)
121         return 0;
122     k5_setmsg(context, ret, "%s (filename: %s)", error_message(ret), fname);
123     return ret;
124 }
125
126 /* Get the size of the cache file as a size_t, or SIZE_MAX if it is too
127  * large to be represented as a size_t. */
128 static krb5_error_code
129 get_size(krb5_context context, FILE *fp, size_t *size_out)
130 {
131     struct stat sb;
132
133     *size_out = 0;
134     if (fstat(fileno(fp), &sb) == -1)
135         return interpret_errno(context, errno);
136     if (sizeof(off_t) > sizeof(size_t) && sb.st_size > (off_t)SIZE_MAX)
137         *size_out = SIZE_MAX;
138     else
139         *size_out = sb.st_size;
140     return 0;
141 }
142
143 /* Read len bytes from fp, storing them in buf.  Return KRB5_CC_END
144  * if not enough bytes are present. */
145 static krb5_error_code
146 read_bytes(krb5_context context, FILE *fp, void *buf, size_t len)
147 {
148     size_t nread;
149
150     nread = fread(buf, 1, len, fp);
151     if (nread < len)
152         return ferror(fp) ? errno : KRB5_CC_END;
153     return 0;
154 }
155
156 /* Load four bytes from the cache file.  Add them to buf (if set) and return
157  * their value as a 32-bit unsigned integer according to the file format. */
158 static krb5_error_code
159 read32(krb5_context context, FILE *fp, int version, struct k5buf *buf,
160        uint32_t *out)
161 {
162     krb5_error_code ret;
163     char bytes[4];
164
165     ret = read_bytes(context, fp, bytes, 4);
166     if (ret)
167         return ret;
168     if (buf != NULL)
169         k5_buf_add_len(buf, bytes, 4);
170     *out = (version < 3) ? load_32_n(bytes) : load_32_be(bytes);
171     return 0;
172 }
173
174 /* Load two bytes from the cache file and return their value as a 16-bit
175  * unsigned integer according to the file format. */
176 static krb5_error_code
177 read16(krb5_context context, FILE *fp, int version, uint16_t *out)
178 {
179     krb5_error_code ret;
180     char bytes[2];
181
182     ret = read_bytes(context, fp, bytes, 2);
183     if (ret)
184         return ret;
185     *out = (version < 3) ? load_16_n(bytes) : load_16_be(bytes);
186     return 0;
187 }
188
189 /* Read len bytes from the cache file and add them to buf. */
190 static krb5_error_code
191 load_bytes(krb5_context context, FILE *fp, size_t len, struct k5buf *buf)
192 {
193     void *ptr;
194
195     ptr = k5_buf_get_space(buf, len);
196     return (ptr == NULL) ? KRB5_CC_NOMEM : read_bytes(context, fp, ptr, len);
197 }
198
199 /* Load a 32-bit length and data from the cache file into buf, but not more
200  * than maxsize bytes. */
201 static krb5_error_code
202 load_data(krb5_context context, FILE *fp, int version, size_t maxsize,
203           struct k5buf *buf)
204 {
205     krb5_error_code ret;
206     uint32_t count;
207
208     ret = read32(context, fp, version, buf, &count);
209     if (ret)
210         return ret;
211     if (count > maxsize)
212         return KRB5_CC_FORMAT;
213     return load_bytes(context, fp, count, buf);
214 }
215
216 /* Load a marshalled principal from the cache file into buf, without
217  * unmarshalling it. */
218 static krb5_error_code
219 load_principal(krb5_context context, FILE *fp, int version, size_t maxsize,
220                struct k5buf *buf)
221 {
222     krb5_error_code ret;
223     uint32_t count;
224
225     if (version > 1) {
226         ret = load_bytes(context, fp, 4, buf);
227         if (ret)
228             return ret;
229     }
230     ret = read32(context, fp, version, buf, &count);
231     if (ret)
232         return ret;
233     /* Add one for the realm (except in version 1 which already counts it). */
234     if (version != 1)
235         count++;
236     while (count-- > 0) {
237         ret = load_data(context, fp, version, maxsize, buf);
238         if (ret)
239             return ret;
240     }
241     return 0;
242 }
243
244 /* Load a marshalled credential from the cache file into buf, without
245  * unmarshalling it. */
246 static krb5_error_code
247 load_cred(krb5_context context, FILE *fp, int version, size_t maxsize,
248           struct k5buf *buf)
249 {
250     krb5_error_code ret;
251     uint32_t count, i;
252
253     /* client and server */
254     ret = load_principal(context, fp, version, maxsize, buf);
255     if (ret)
256         return ret;
257     ret = load_principal(context, fp, version, maxsize, buf);
258     if (ret)
259         return ret;
260
261     /* keyblock (enctype, enctype again for version 3, length, value) */
262     ret = load_bytes(context, fp, (version == 3) ? 4 : 2, buf);
263     if (ret)
264         return ret;
265     ret = load_data(context, fp, version, maxsize, buf);
266     if (ret)
267         return ret;
268
269     /* times (4*4 bytes), is_skey (1 byte), ticket flags (4 bytes) */
270     ret = load_bytes(context, fp, 4 * 4 + 1 + 4, buf);
271     if (ret)
272         return ret;
273
274     /* addresses and authdata, both lists of {type, length, data} */
275     for (i = 0; i < 2; i++) {
276         ret = read32(context, fp, version, buf, &count);
277         if (ret)
278             return ret;
279         while (count-- > 0) {
280             ret = load_bytes(context, fp, 2, buf);
281             if (ret)
282                 return ret;
283             ret = load_data(context, fp, version, maxsize, buf);
284             if (ret)
285                 return ret;
286         }
287     }
288
289     /* ticket and second_ticket */
290     ret = load_data(context, fp, version, maxsize, buf);
291     if (ret)
292         return ret;
293     return load_data(context, fp, version, maxsize, buf);
294 }
295
296 static krb5_error_code
297 read_principal(krb5_context context, FILE *fp, int version,
298                krb5_principal *princ)
299 {
300     krb5_error_code ret;
301     struct k5buf buf;
302     size_t maxsize;
303
304     *princ = NULL;
305     k5_buf_init_dynamic(&buf);
306
307     /* Read the principal representation into memory. */
308     ret = get_size(context, fp, &maxsize);
309     if (ret)
310         goto cleanup;
311     ret = load_principal(context, fp, version, maxsize, &buf);
312     if (ret)
313         goto cleanup;
314     ret = k5_buf_status(&buf);
315     if (ret)
316         goto cleanup;
317
318     /* Unmarshal it from buf into princ. */
319     ret = k5_unmarshal_princ(buf.data, buf.len, version, princ);
320
321 cleanup:
322     k5_buf_free(&buf);
323     return ret;
324 }
325
326 /*
327  * Open and lock an existing cache file.  If writable is true, open it for
328  * writing (with O_APPEND) and get an exclusive lock; otherwise open it for
329  * reading and get a shared lock.
330  */
331 static krb5_error_code
332 open_cache_file(krb5_context context, const char *filename,
333                 krb5_boolean writable, FILE **fp_out)
334 {
335     krb5_error_code ret;
336     int fd, flags, lockmode;
337     FILE *fp;
338
339     *fp_out = NULL;
340
341     flags = writable ? (O_RDWR | O_APPEND) : O_RDONLY;
342     fd = open(filename, flags | O_BINARY | O_CLOEXEC, 0600);
343     if (fd == -1)
344         return interpret_errno(context, errno);
345     set_cloexec_fd(fd);
346
347     lockmode = writable ? KRB5_LOCKMODE_EXCLUSIVE : KRB5_LOCKMODE_SHARED;
348     ret = krb5_lock_file(context, fd, lockmode);
349     if (ret) {
350         (void)close(fd);
351         return ret;
352     }
353
354     fp = fdopen(fd, writable ? "r+b" : "rb");
355     if (fp == NULL) {
356         (void)krb5_unlock_file(context, fd);
357         (void)close(fd);
358         return KRB5_CC_NOMEM;
359     }
360
361     *fp_out = fp;
362     return 0;
363 }
364
365 /* Unlock and close the cache file.  Do nothing if fp is NULL. */
366 static krb5_error_code
367 close_cache_file(krb5_context context, FILE *fp)
368 {
369     int st;
370     krb5_error_code ret;
371
372     if (fp == NULL)
373         return 0;
374     ret = krb5_unlock_file(context, fileno(fp));
375     st = fclose(fp);
376     if (ret)
377         return ret;
378     return st ? interpret_errno(context, errno) : 0;
379 }
380
381 /* Read the cache file header.  Set time offsets in context from the header if
382  * appropriate.  Set *version_out to the cache file format version. */
383 static krb5_error_code
384 read_header(krb5_context context, FILE *fp, int *version_out)
385 {
386     krb5_error_code ret;
387     krb5_os_context os_ctx = &context->os_context;
388     uint16_t fields_len, tag, flen;
389     uint32_t time_offset, usec_offset;
390     char i16buf[2];
391     int version;
392
393     *version_out = 0;
394
395     /* Get the file format version. */
396     ret = read_bytes(context, fp, i16buf, 2);
397     if (ret)
398         return KRB5_CC_FORMAT;
399     version = load_16_be(i16buf) - FVNO_BASE;
400     if (version < 1 || version > 4)
401         return KRB5_CCACHE_BADVNO;
402     *version_out = version;
403
404     /* Tagged header fields begin with version 4. */
405     if (version < 4)
406         return 0;
407
408     if (read16(context, fp, version, &fields_len))
409         return KRB5_CC_FORMAT;
410     while (fields_len) {
411         if (fields_len < 4 || read16(context, fp, version, &tag) ||
412             read16(context, fp, version, &flen) || flen > fields_len - 4)
413             return KRB5_CC_FORMAT;
414
415         switch (tag) {
416         case FCC_TAG_DELTATIME:
417             if (flen != 8 ||
418                 read32(context, fp, version, NULL, &time_offset) ||
419                 read32(context, fp, version, NULL, &usec_offset))
420                 return KRB5_CC_FORMAT;
421
422             if (!(context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) ||
423                 (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID))
424                 break;
425
426             os_ctx->time_offset = time_offset;
427             os_ctx->usec_offset = usec_offset;
428             os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) |
429                                 KRB5_OS_TOFFSET_VALID);
430             break;
431
432         default:
433             if (flen && fseek(fp, flen, SEEK_CUR) != 0)
434                 return KRB5_CC_FORMAT;
435             break;
436         }
437         fields_len -= (4 + flen);
438     }
439     return 0;
440 }
441
442 /* Create or overwrite the cache file with a header and default principal. */
443 static krb5_error_code KRB5_CALLCONV
444 fcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
445 {
446     krb5_error_code ret;
447     krb5_os_context os_ctx = &context->os_context;
448     fcc_data *data = id->data;
449     char i16buf[2], i32buf[4];
450     uint16_t fields_len;
451     ssize_t nwritten;
452     int st, flags, version, fd = -1;
453     struct k5buf buf = EMPTY_K5BUF;
454     krb5_boolean file_locked = FALSE;
455
456     k5_cc_mutex_lock(context, &data->lock);
457
458     unlink(data->filename);
459     flags = O_CREAT | O_EXCL | O_RDWR | O_BINARY | O_CLOEXEC;
460     fd = open(data->filename, flags, 0600);
461     if (fd == -1) {
462         ret = interpret_errno(context, errno);
463         goto cleanup;
464     }
465     set_cloexec_fd(fd);
466
467 #if defined(HAVE_FCHMOD) || defined(HAVE_CHMOD)
468 #ifdef HAVE_FCHMOD
469     st = fchmod(fd, S_IRUSR | S_IWUSR);
470 #else
471     st = chmod(data->filename, S_IRUSR | S_IWUSR);
472 #endif
473     if (st == -1) {
474         ret = interpret_errno(context, errno);
475         goto cleanup;
476     }
477 #endif
478
479     ret = krb5_lock_file(context, fd, KRB5_LOCKMODE_EXCLUSIVE);
480     if (ret)
481         goto cleanup;
482     file_locked = TRUE;
483
484     /* Prepare the header and principal in buf. */
485     k5_buf_init_dynamic(&buf);
486     version = context->fcc_default_format - FVNO_BASE;
487     store_16_be(FVNO_BASE + version, i16buf);
488     k5_buf_add_len(&buf, i16buf, 2);
489     if (version >= 4) {
490         /* Add tagged header fields. */
491         fields_len = 0;
492         if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)
493             fields_len += 12;
494         store_16_be(fields_len, i16buf);
495         k5_buf_add_len(&buf, i16buf, 2);
496         if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
497             /* Add time offset tag. */
498             store_16_be(FCC_TAG_DELTATIME, i16buf);
499             k5_buf_add_len(&buf, i16buf, 2);
500             store_16_be(8, i16buf);
501             k5_buf_add_len(&buf, i16buf, 2);
502             store_32_be(os_ctx->time_offset, i32buf);
503             k5_buf_add_len(&buf, i32buf, 4);
504             store_32_be(os_ctx->usec_offset, i32buf);
505             k5_buf_add_len(&buf, i32buf, 4);
506         }
507     }
508     k5_marshal_princ(&buf, version, princ);
509     ret = k5_buf_status(&buf);
510     if (ret)
511         goto cleanup;
512
513     /* Write the header and principal. */
514     nwritten = write(fd, buf.data, buf.len);
515     if (nwritten == -1)
516         ret = interpret_errno(context, errno);
517     if ((size_t)nwritten != buf.len)
518         ret = KRB5_CC_IO;
519
520 cleanup:
521     k5_buf_free(&buf);
522     if (file_locked)
523         krb5_unlock_file(context, fd);
524     if (fd != -1)
525         close(fd);
526     k5_cc_mutex_unlock(context, &data->lock);
527     krb5_change_cache();
528     return set_errmsg_filename(context, ret, data->filename);
529 }
530
531 /* Release an fcc_data object. */
532 static void
533 free_fccdata(krb5_context context, fcc_data *data)
534 {
535     k5_cc_mutex_assert_unlocked(context, &data->lock);
536     free(data->filename);
537     k5_cc_mutex_destroy(&data->lock);
538     free(data);
539 }
540
541 /* Release the ccache handle. */
542 static krb5_error_code KRB5_CALLCONV
543 fcc_close(krb5_context context, krb5_ccache id)
544 {
545     free_fccdata(context, id->data);
546     free(id);
547     return 0;
548 }
549
550 /* Destroy the cache file and release the handle. */
551 static krb5_error_code KRB5_CALLCONV
552 fcc_destroy(krb5_context context, krb5_ccache id)
553 {
554     krb5_error_code ret = 0;
555     fcc_data *data = id->data;
556     int st, fd;
557     struct stat buf;
558     unsigned long i, size;
559     unsigned int wlen;
560     char zeros[BUFSIZ];
561
562     k5_cc_mutex_lock(context, &data->lock);
563
564     fd = open(data->filename, O_RDWR | O_BINARY | O_CLOEXEC, 0);
565     if (fd < 0) {
566         ret = interpret_errno(context, errno);
567         goto cleanup;
568     }
569     set_cloexec_fd(fd);
570
571 #ifdef MSDOS_FILESYSTEM
572     /*
573      * "Disgusting bit of UNIX trivia" - that's how the writers of NFS describe
574      * the ability of UNIX to still write to a file which has been unlinked.
575      * Naturally, the PC can't do this.  As a result, we have to delete the
576      * file after we wipe it clean, but that throws off all the error handling
577      * code.  So we have do the work ourselves.
578      */
579     st = fstat(fd, &buf);
580     if (st == -1) {
581         ret = interpret_errno(context, errno);
582         size = 0;               /* Nothing to wipe clean */
583     } else {
584         size = (unsigned long)buf.st_size;
585     }
586
587     memset(zeros, 0, BUFSIZ);
588     while (size > 0) {
589         wlen = (int)((size > BUFSIZ) ? BUFSIZ : size); /* How much to write */
590         i = write(fd, zeros, wlen);
591         if (i < 0) {
592             ret = interpret_errno(context, errno);
593             /* Don't jump to cleanup--we still want to delete the file. */
594             break;
595         }
596         size -= i;
597     }
598
599     (void)close(fd);
600
601     st = unlink(data->filename);
602     if (st < 0) {
603         ret = interpret_errno(context, errno);
604         goto cleanup;
605     }
606
607 #else /* MSDOS_FILESYSTEM */
608
609     st = unlink(data->filename);
610     if (st < 0) {
611         ret = interpret_errno(context, errno);
612         (void)close(fd);
613         goto cleanup;
614     }
615
616     st = fstat(fd, &buf);
617     if (st < 0) {
618         ret = interpret_errno(context, errno);
619         (void)close(fd);
620         goto cleanup;
621     }
622
623     /* XXX This may not be legal XXX */
624     size = (unsigned long)buf.st_size;
625     memset(zeros, 0, BUFSIZ);
626     for (i = 0; i < size / BUFSIZ; i++) {
627         if (write(fd, zeros, BUFSIZ) < 0) {
628             ret = interpret_errno(context, errno);
629             (void)close(fd);
630             goto cleanup;
631         }
632     }
633
634     wlen = size % BUFSIZ;
635     if (write(fd, zeros, wlen) < 0) {
636         ret = interpret_errno(context, errno);
637         (void)close(fd);
638         goto cleanup;
639     }
640
641     st = close(fd);
642
643     if (st)
644         ret = interpret_errno(context, errno);
645
646 #endif /* MSDOS_FILESYSTEM */
647
648 cleanup:
649     (void)set_errmsg_filename(context, ret, data->filename);
650     k5_cc_mutex_unlock(context, &data->lock);
651     free_fccdata(context, data);
652     free(id);
653
654     krb5_change_cache();
655     return ret;
656 }
657
658 extern const krb5_cc_ops krb5_fcc_ops;
659
660 /* Create a file ccache handle for the pathname given by residual. */
661 static krb5_error_code KRB5_CALLCONV
662 fcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
663 {
664     krb5_ccache lid;
665     krb5_error_code ret;
666     fcc_data *data;
667
668     data = malloc(sizeof(fcc_data));
669     if (data == NULL)
670         return KRB5_CC_NOMEM;
671     data->filename = strdup(residual);
672     if (data->filename == NULL) {
673         free(data);
674         return KRB5_CC_NOMEM;
675     }
676     ret = k5_cc_mutex_init(&data->lock);
677     if (ret) {
678         free(data->filename);
679         free(data);
680         return ret;
681     }
682
683     lid = malloc(sizeof(struct _krb5_ccache));
684     if (lid == NULL) {
685         free_fccdata(context, data);
686         return KRB5_CC_NOMEM;
687     }
688
689     lid->ops = &krb5_fcc_ops;
690     lid->data = data;
691     lid->magic = KV5M_CCACHE;
692
693     /* Other routines will get errors on open, and callers must expect them, if
694      * cache is non-existent/unusable. */
695     *id = lid;
696     return 0;
697 }
698
699 /* Prepare for a sequential iteration over the cache file. */
700 static krb5_error_code KRB5_CALLCONV
701 fcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
702 {
703     krb5_fcc_cursor *fcursor = NULL;
704     krb5_error_code ret;
705     krb5_principal princ = NULL;
706     fcc_data *data = id->data;
707     FILE *fp = NULL;
708     int version;
709
710     k5_cc_mutex_lock(context, &data->lock);
711
712     fcursor = malloc(sizeof(krb5_fcc_cursor));
713     if (fcursor == NULL) {
714         ret = KRB5_CC_NOMEM;
715         goto cleanup;
716     }
717
718     /* Open the cache file and read the header. */
719     ret = open_cache_file(context, data->filename, FALSE, &fp);
720     if (ret)
721         goto cleanup;
722     ret = read_header(context, fp, &version);
723     if (ret)
724         goto cleanup;
725
726     /* Read past the default client principal name. */
727     ret = read_principal(context, fp, version, &princ);
728     if (ret)
729         goto cleanup;
730
731     /* Drop the shared file lock but retain the file handle. */
732     (void)krb5_unlock_file(context, fileno(fp));
733     fcursor->fp = fp;
734     fp = NULL;
735     fcursor->version = version;
736     *cursor = (krb5_cc_cursor)fcursor;
737     fcursor = NULL;
738
739 cleanup:
740     (void)close_cache_file(context, fp);
741     free(fcursor);
742     krb5_free_principal(context, princ);
743     k5_cc_mutex_unlock(context, &data->lock);
744     return set_errmsg_filename(context, ret, data->filename);
745 }
746
747 /* Get the next credential from the cache file. */
748 static krb5_error_code KRB5_CALLCONV
749 fcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
750               krb5_creds *creds)
751 {
752     krb5_error_code ret;
753     krb5_fcc_cursor *fcursor = *cursor;
754     fcc_data *data = id->data;
755     struct k5buf buf;
756     size_t maxsize;
757     krb5_boolean file_locked = FALSE;
758
759     memset(creds, 0, sizeof(*creds));
760     k5_cc_mutex_lock(context, &data->lock);
761     k5_buf_init_dynamic(&buf);
762
763     ret = krb5_lock_file(context, fileno(fcursor->fp), KRB5_LOCKMODE_SHARED);
764     if (ret)
765         goto cleanup;
766     file_locked = TRUE;
767
768     /* Load a marshalled cred into memory. */
769     ret = get_size(context, fcursor->fp, &maxsize);
770     if (ret)
771         goto cleanup;
772     ret = load_cred(context, fcursor->fp, fcursor->version, maxsize, &buf);
773     if (ret)
774         goto cleanup;
775     ret = k5_buf_status(&buf);
776     if (ret)
777         goto cleanup;
778
779     /* Unmarshal it from buf into creds. */
780     ret = k5_unmarshal_cred(buf.data, buf.len, fcursor->version, creds);
781
782 cleanup:
783     if (file_locked)
784         (void)krb5_unlock_file(context, fileno(fcursor->fp));
785     k5_cc_mutex_unlock(context, &data->lock);
786     k5_buf_free(&buf);
787     return set_errmsg_filename(context, ret, data->filename);
788 }
789
790 /* Release an iteration cursor. */
791 static krb5_error_code KRB5_CALLCONV
792 fcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
793 {
794     krb5_fcc_cursor *fcursor = *cursor;
795
796     (void)fclose(fcursor->fp);
797     free(fcursor);
798     *cursor = NULL;
799     return 0;
800 }
801
802 /* Generate a unique file ccache using the given template (which will be
803  * modified to contain the actual name of the file). */
804 krb5_error_code
805 krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id)
806 {
807     krb5_ccache lid;
808     int fd;
809     krb5_error_code ret;
810     fcc_data *data;
811     char fcc_fvno[2];
812     int16_t fcc_flen = 0;
813     int errsave, cnt;
814
815     fd = mkstemp(template);
816     if (fd == -1)
817         return interpret_errno(context, errno);
818     set_cloexec_fd(fd);
819
820     /* Allocate memory */
821     data = malloc(sizeof(fcc_data));
822     if (data == NULL) {
823         close(fd);
824         unlink(template);
825         return KRB5_CC_NOMEM;
826     }
827
828     data->filename = strdup(template);
829     if (data->filename == NULL) {
830         free(data);
831         close(fd);
832         unlink(template);
833         return KRB5_CC_NOMEM;
834     }
835
836     ret = k5_cc_mutex_init(&data->lock);
837     if (ret) {
838         free(data->filename);
839         free(data);
840         close(fd);
841         unlink(template);
842         return ret;
843     }
844     k5_cc_mutex_lock(context, &data->lock);
845
846     /* Ignore user's umask, set mode = 0600 */
847 #ifndef HAVE_FCHMOD
848 #ifdef HAVE_CHMOD
849     chmod(data->filename, S_IRUSR | S_IWUSR);
850 #endif
851 #else
852     fchmod(fd, S_IRUSR | S_IWUSR);
853 #endif
854     store_16_be(context->fcc_default_format, fcc_fvno);
855     cnt = write(fd, &fcc_fvno, 2);
856     if (cnt != 2) {
857         errsave = errno;
858         (void)close(fd);
859         (void)unlink(data->filename);
860         ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO;
861         goto err_out;
862     }
863     /* For version 4 we save a length for the rest of the header */
864     if (context->fcc_default_format == FVNO_BASE + 4) {
865         cnt = write(fd, &fcc_flen, sizeof(fcc_flen));
866         if (cnt != sizeof(fcc_flen)) {
867             errsave = errno;
868             (void)close(fd);
869             (void)unlink(data->filename);
870             ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO;
871             goto err_out;
872         }
873     }
874     if (close(fd) == -1) {
875         errsave = errno;
876         (void)unlink(data->filename);
877         ret = interpret_errno(context, errsave);
878         goto err_out;
879     }
880
881     k5_cc_mutex_assert_locked(context, &data->lock);
882     k5_cc_mutex_unlock(context, &data->lock);
883     lid = malloc(sizeof(*lid));
884     if (lid == NULL) {
885         free_fccdata(context, data);
886         return KRB5_CC_NOMEM;
887     }
888
889     lid->ops = &krb5_fcc_ops;
890     lid->data = data;
891     lid->magic = KV5M_CCACHE;
892
893     *id = lid;
894
895     krb5_change_cache();
896     return 0;
897
898 err_out:
899     (void)set_errmsg_filename(context, ret, data->filename);
900     k5_cc_mutex_unlock(context, &data->lock);
901     k5_cc_mutex_destroy(&data->lock);
902     free(data->filename);
903     free(data);
904     return ret;
905 }
906
907 /*
908  * Create a new file cred cache whose name is guaranteed to be unique.  The
909  * name begins with the string TKT_ROOT (from fcc.h).  The cache file is not
910  * opened, but the new filename is reserved.
911  */
912 static krb5_error_code KRB5_CALLCONV
913 fcc_generate_new(krb5_context context, krb5_ccache *id)
914 {
915     char scratch[sizeof(TKT_ROOT) + 7]; /* Room for XXXXXX and terminator */
916
917     (void)snprintf(scratch, sizeof(scratch), "%sXXXXXX", TKT_ROOT);
918     return krb5int_fcc_new_unique(context, scratch, id);
919 }
920
921 /* Return an alias to the pathname of the cache file. */
922 static const char * KRB5_CALLCONV
923 fcc_get_name(krb5_context context, krb5_ccache id)
924 {
925     return ((fcc_data *)id->data)->filename;
926 }
927
928 /* Retrieve a copy of the default principal, if the cache is initialized. */
929 static krb5_error_code KRB5_CALLCONV
930 fcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
931 {
932     krb5_error_code ret;
933     fcc_data *data = id->data;
934     FILE *fp = NULL;
935     int version;
936
937     k5_cc_mutex_lock(context, &data->lock);
938     ret = open_cache_file(context, data->filename, FALSE, &fp);
939     if (ret)
940         goto cleanup;
941     ret = read_header(context, fp, &version);
942     if (ret)
943         goto cleanup;
944     ret = read_principal(context, fp, version, princ);
945
946 cleanup:
947     (void)close_cache_file(context, fp);
948     k5_cc_mutex_unlock(context, &data->lock);
949     return set_errmsg_filename(context, ret, data->filename);
950 }
951
952 /* Search for a credential within the cache file. */
953 static krb5_error_code KRB5_CALLCONV
954 fcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
955              krb5_creds *mcreds, krb5_creds *creds)
956 {
957     krb5_error_code ret;
958
959     ret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, creds);
960     return set_errmsg_filename(context, ret, ((fcc_data *)id->data)->filename);
961 }
962
963 /* Store a credential in the cache file. */
964 static krb5_error_code KRB5_CALLCONV
965 fcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
966 {
967     krb5_error_code ret, ret2;
968     fcc_data *data = id->data;
969     FILE *fp = NULL;
970     int version;
971     struct k5buf buf = EMPTY_K5BUF;
972     ssize_t nwritten;
973
974     k5_cc_mutex_lock(context, &data->lock);
975
976     /* Open the cache file for O_APPEND writing. */
977     ret = open_cache_file(context, data->filename, TRUE, &fp);
978     if (ret)
979         goto cleanup;
980     ret = read_header(context, fp, &version);
981     if (ret)
982         goto cleanup;
983
984     /* Marshal the cred and write it to the file with a single append write. */
985     k5_buf_init_dynamic(&buf);
986     k5_marshal_cred(&buf, version, creds);
987     ret = k5_buf_status(&buf);
988     if (ret)
989         goto cleanup;
990     nwritten = write(fileno(fp), buf.data, buf.len);
991     if (nwritten == -1)
992         ret = interpret_errno(context, errno);
993     if ((size_t)nwritten != buf.len)
994         ret = KRB5_CC_IO;
995
996     krb5_change_cache();
997
998 cleanup:
999     k5_buf_free(&buf);
1000     ret2 = close_cache_file(context, fp);
1001     k5_cc_mutex_unlock(context, &data->lock);
1002     return set_errmsg_filename(context, ret ? ret : ret2, data->filename);
1003 }
1004
1005 /* Non-functional stub for removing a cred from the cache file. */
1006 static krb5_error_code KRB5_CALLCONV
1007 fcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
1008                 krb5_creds *creds)
1009 {
1010     return KRB5_CC_NOSUPP;
1011 }
1012
1013 static krb5_error_code KRB5_CALLCONV
1014 fcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
1015 {
1016     return 0;
1017 }
1018
1019 static krb5_error_code KRB5_CALLCONV
1020 fcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
1021 {
1022     *flags = 0;
1023     return 0;
1024 }
1025
1026 /* Prepare to iterate over the caches in the per-type collection. */
1027 static krb5_error_code KRB5_CALLCONV
1028 fcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor)
1029 {
1030     krb5_cc_ptcursor n = NULL;
1031     struct krb5_fcc_ptcursor_data *cdata = NULL;
1032
1033     *cursor = NULL;
1034
1035     n = malloc(sizeof(*n));
1036     if (n == NULL)
1037         return ENOMEM;
1038     n->ops = &krb5_fcc_ops;
1039     cdata = malloc(sizeof(*cdata));
1040     if (cdata == NULL) {
1041         free(n);
1042         return ENOMEM;
1043     }
1044     cdata->first = TRUE;
1045     n->data = cdata;
1046     *cursor = n;
1047     return 0;
1048 }
1049
1050 /* Get the next cache in the per-type collection.  The FILE per-type collection
1051  * contains only the context's default cache if it is a file cache. */
1052 static krb5_error_code KRB5_CALLCONV
1053 fcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
1054                   krb5_ccache *cache_out)
1055 {
1056     krb5_error_code ret;
1057     struct krb5_fcc_ptcursor_data *cdata = cursor->data;
1058     const char *defname, *residual;
1059     krb5_ccache cache;
1060     struct stat sb;
1061
1062     *cache_out = NULL;
1063     if (!cdata->first)
1064         return 0;
1065     cdata->first = FALSE;
1066
1067     defname = krb5_cc_default_name(context);
1068     if (!defname)
1069         return 0;
1070
1071     /* Check if the default has type FILE or no type; find the residual. */
1072     if (strncmp(defname, "FILE:", 5) == 0)
1073         residual = defname + 5;
1074     else if (strchr(defname + 2, ':') == NULL)  /* Skip drive prefix if any. */
1075         residual = defname;
1076     else
1077         return 0;
1078
1079     /* Don't yield a nonexistent default file cache. */
1080     if (stat(residual, &sb) != 0)
1081         return 0;
1082
1083     ret = krb5_cc_resolve(context, defname, &cache);
1084     if (ret)
1085         return set_errmsg_filename(context, ret, defname);
1086     *cache_out = cache;
1087     return 0;
1088 }
1089
1090 /* Release a per-type collection iteration cursor. */
1091 static krb5_error_code KRB5_CALLCONV
1092 fcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
1093 {
1094     if (*cursor == NULL)
1095         return 0;
1096     free((*cursor)->data);
1097     free(*cursor);
1098     *cursor = NULL;
1099     return 0;
1100 }
1101
1102 /* Get the cache file's last modification time. */
1103 static krb5_error_code KRB5_CALLCONV
1104 fcc_last_change_time(krb5_context context, krb5_ccache id,
1105                      krb5_timestamp *change_time)
1106 {
1107     krb5_error_code ret = 0;
1108     fcc_data *data = id->data;
1109     struct stat buf;
1110
1111     *change_time = 0;
1112
1113     k5_cc_mutex_lock(context, &data->lock);
1114
1115     if (stat(data->filename, &buf) == -1)
1116         ret = interpret_errno(context, errno);
1117     else
1118         *change_time = (krb5_timestamp)buf.st_mtime;
1119
1120     k5_cc_mutex_unlock(context, &data->lock);
1121
1122     return set_errmsg_filename(context, ret, data->filename);
1123 }
1124
1125 /* Lock the cache handle against other threads.  (This does not lock the cache
1126  * file against other processes.) */
1127 static krb5_error_code KRB5_CALLCONV
1128 fcc_lock(krb5_context context, krb5_ccache id)
1129 {
1130     fcc_data *data = id->data;
1131     k5_cc_mutex_lock(context, &data->lock);
1132     return 0;
1133 }
1134
1135 /* Unlock the cache handle. */
1136 static krb5_error_code KRB5_CALLCONV
1137 fcc_unlock(krb5_context context, krb5_ccache id)
1138 {
1139     fcc_data *data = id->data;
1140     k5_cc_mutex_unlock(context, &data->lock);
1141     return 0;
1142 }
1143
1144 /* Translate a system errno value to a Kerberos com_err code. */
1145 static krb5_error_code
1146 interpret_errno(krb5_context context, int errnum)
1147 {
1148     krb5_error_code ret;
1149
1150     switch (errnum) {
1151     case ENOENT:
1152     case ENOTDIR:
1153 #ifdef ELOOP
1154     case ELOOP:
1155 #endif
1156 #ifdef ENAMETOOLONG
1157     case ENAMETOOLONG:
1158 #endif
1159         ret = KRB5_FCC_NOFILE;
1160         break;
1161     case EPERM:
1162     case EACCES:
1163 #ifdef EISDIR
1164     case EISDIR:                /* Mac doesn't have EISDIR */
1165 #endif
1166     case EROFS:
1167         ret = KRB5_FCC_PERM;
1168         break;
1169     case EINVAL:
1170     case EEXIST:
1171     case EFAULT:
1172     case EBADF:
1173 #ifdef EWOULDBLOCK
1174     case EWOULDBLOCK:
1175 #endif
1176         ret = KRB5_FCC_INTERNAL;
1177         break;
1178     /*
1179      * The rest all map to KRB5_CC_IO.  These errnos are listed to
1180      * document that they've been considered explicitly:
1181      *
1182      *  - EDQUOT
1183      *  - ENOSPC
1184      *  - EIO
1185      *  - ENFILE
1186      *  - EMFILE
1187      *  - ENXIO
1188      *  - EBUSY
1189      *  - ETXTBSY
1190      */
1191     default:
1192         ret = KRB5_CC_IO;
1193         break;
1194     }
1195     return ret;
1196 }
1197
1198 const krb5_cc_ops krb5_fcc_ops = {
1199     0,
1200     "FILE",
1201     fcc_get_name,
1202     fcc_resolve,
1203     fcc_generate_new,
1204     fcc_initialize,
1205     fcc_destroy,
1206     fcc_close,
1207     fcc_store,
1208     fcc_retrieve,
1209     fcc_get_principal,
1210     fcc_start_seq_get,
1211     fcc_next_cred,
1212     fcc_end_seq_get,
1213     fcc_remove_cred,
1214     fcc_set_flags,
1215     fcc_get_flags,
1216     fcc_ptcursor_new,
1217     fcc_ptcursor_next,
1218     fcc_ptcursor_free,
1219     NULL, /* move */
1220     fcc_last_change_time,
1221     NULL, /* wasdefault */
1222     fcc_lock,
1223     fcc_unlock,
1224     NULL, /* switch_to */
1225 };
1226
1227 #if defined(_WIN32)
1228 /*
1229  * krb5_change_cache should be called after the cache changes.
1230  * A notification message is is posted out to all top level
1231  * windows so that they may recheck the cache based on the
1232  * changes made.  We register a unique message type with which
1233  * we'll communicate to all other processes.
1234  */
1235
1236 krb5_error_code
1237 krb5_change_cache(void)
1238 {
1239     PostMessage(HWND_BROADCAST, krb5_get_notification_message(), 0, 0);
1240     return 0;
1241 }
1242
1243 unsigned int KRB5_CALLCONV
1244 krb5_get_notification_message(void)
1245 {
1246     static unsigned int message = 0;
1247
1248     if (message == 0)
1249         message = RegisterWindowMessage(WM_KERBEROS5_CHANGED);
1250
1251     return message;
1252 }
1253 #else /* _WIN32 */
1254
1255 krb5_error_code
1256 krb5_change_cache(void)
1257 {
1258     return 0;
1259 }
1260
1261 unsigned int
1262 krb5_get_notification_message(void)
1263 {
1264     return 0;
1265 }
1266
1267 #endif /* _WIN32 */
1268
1269 const krb5_cc_ops krb5_cc_file_ops = {
1270     0,
1271     "FILE",
1272     fcc_get_name,
1273     fcc_resolve,
1274     fcc_generate_new,
1275     fcc_initialize,
1276     fcc_destroy,
1277     fcc_close,
1278     fcc_store,
1279     fcc_retrieve,
1280     fcc_get_principal,
1281     fcc_start_seq_get,
1282     fcc_next_cred,
1283     fcc_end_seq_get,
1284     fcc_remove_cred,
1285     fcc_set_flags,
1286     fcc_get_flags,
1287     fcc_ptcursor_new,
1288     fcc_ptcursor_next,
1289     fcc_ptcursor_free,
1290     NULL, /* move */
1291     fcc_last_change_time,
1292     NULL, /* wasdefault */
1293     fcc_lock,
1294     fcc_unlock,
1295     NULL, /* switch_to */
1296 };