xref: /freebsd/crypto/krb5/src/lib/krb5/ccache/cc_file.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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
set_errmsg_filename(krb5_context context,krb5_error_code ret,const char * fname)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
get_size(krb5_context context,FILE * fp,size_t * size_out)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
read_bytes(krb5_context context,FILE * fp,void * buf,size_t len)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
read32(krb5_context context,FILE * fp,int version,struct k5buf * buf,uint32_t * out)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
read16(krb5_context context,FILE * fp,int version,uint16_t * out)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
load_bytes(krb5_context context,FILE * fp,size_t len,struct k5buf * buf)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
load_data(krb5_context context,FILE * fp,int version,size_t maxsize,struct k5buf * buf)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
load_principal(krb5_context context,FILE * fp,int version,size_t maxsize,struct k5buf * buf)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
load_cred(krb5_context context,FILE * fp,int version,size_t maxsize,struct k5buf * buf)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
read_principal(krb5_context context,FILE * fp,int version,krb5_principal * princ)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
open_cache_file(krb5_context context,const char * filename,krb5_boolean writable,FILE ** fp_out)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
close_cache_file(krb5_context context,FILE * fp)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
read_header(krb5_context context,FILE * fp,int * version_out)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 static void
marshal_header(krb5_context context,struct k5buf * buf,krb5_principal princ)443 marshal_header(krb5_context context, struct k5buf *buf, krb5_principal princ)
444 {
445     krb5_os_context os_ctx = &context->os_context;
446     int version = context->fcc_default_format - FVNO_BASE;
447     uint16_t fields_len;
448 
449     version = context->fcc_default_format - FVNO_BASE;
450     k5_buf_add_uint16_be(buf, FVNO_BASE + version);
451     if (version >= 4) {
452         /* Add tagged header fields. */
453         fields_len = 0;
454         if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)
455             fields_len += 12;
456         k5_buf_add_uint16_be(buf, fields_len);
457         if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
458             /* Add time offset tag. */
459             k5_buf_add_uint16_be(buf, FCC_TAG_DELTATIME);
460             k5_buf_add_uint16_be(buf, 8);
461             k5_buf_add_uint32_be(buf, os_ctx->time_offset);
462             k5_buf_add_uint32_be(buf, os_ctx->usec_offset);
463         }
464     }
465     k5_marshal_princ(buf, version, princ);
466 }
467 
468 /* Create or overwrite the cache file with a header and default principal. */
469 static krb5_error_code KRB5_CALLCONV
fcc_initialize(krb5_context context,krb5_ccache id,krb5_principal princ)470 fcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
471 {
472     krb5_error_code ret;
473     fcc_data *data = id->data;
474     ssize_t nwritten;
475     int st, flags, fd = -1;
476     struct k5buf buf = EMPTY_K5BUF;
477     krb5_boolean file_locked = FALSE;
478 
479     k5_cc_mutex_lock(context, &data->lock);
480 
481     unlink(data->filename);
482     flags = O_CREAT | O_EXCL | O_RDWR | O_BINARY | O_CLOEXEC;
483     fd = open(data->filename, flags, 0600);
484     if (fd == -1) {
485         ret = interpret_errno(context, errno);
486         goto cleanup;
487     }
488     set_cloexec_fd(fd);
489 
490 #if defined(HAVE_FCHMOD) || defined(HAVE_CHMOD)
491 #ifdef HAVE_FCHMOD
492     st = fchmod(fd, S_IRUSR | S_IWUSR);
493 #else
494     st = chmod(data->filename, S_IRUSR | S_IWUSR);
495 #endif
496     if (st == -1) {
497         ret = interpret_errno(context, errno);
498         goto cleanup;
499     }
500 #endif
501 
502     ret = krb5_lock_file(context, fd, KRB5_LOCKMODE_EXCLUSIVE);
503     if (ret)
504         goto cleanup;
505     file_locked = TRUE;
506 
507     /* Prepare the header and principal in buf. */
508     k5_buf_init_dynamic(&buf);
509     marshal_header(context, &buf, princ);
510     ret = k5_buf_status(&buf);
511     if (ret)
512         goto cleanup;
513 
514     /* Write the header and principal. */
515     nwritten = write(fd, buf.data, buf.len);
516     if (nwritten == -1)
517         ret = interpret_errno(context, errno);
518     if ((size_t)nwritten != buf.len)
519         ret = KRB5_CC_IO;
520 
521 cleanup:
522     k5_buf_free(&buf);
523     if (file_locked)
524         krb5_unlock_file(context, fd);
525     if (fd != -1)
526         close(fd);
527     k5_cc_mutex_unlock(context, &data->lock);
528     krb5_change_cache();
529     return set_errmsg_filename(context, ret, data->filename);
530 }
531 
532 /* Release an fcc_data object. */
533 static void
free_fccdata(krb5_context context,fcc_data * data)534 free_fccdata(krb5_context context, fcc_data *data)
535 {
536     k5_cc_mutex_assert_unlocked(context, &data->lock);
537     free(data->filename);
538     k5_cc_mutex_destroy(&data->lock);
539     free(data);
540 }
541 
542 /* Release the ccache handle. */
543 static krb5_error_code KRB5_CALLCONV
fcc_close(krb5_context context,krb5_ccache id)544 fcc_close(krb5_context context, krb5_ccache id)
545 {
546     free_fccdata(context, id->data);
547     free(id);
548     return 0;
549 }
550 
551 /* Destroy the cache file and release the handle. */
552 static krb5_error_code KRB5_CALLCONV
fcc_destroy(krb5_context context,krb5_ccache id)553 fcc_destroy(krb5_context context, krb5_ccache id)
554 {
555     krb5_error_code ret = 0;
556     fcc_data *data = id->data;
557     int st, fd;
558     struct stat buf;
559     unsigned long i, size;
560     unsigned int wlen;
561     char zeros[BUFSIZ];
562 
563     k5_cc_mutex_lock(context, &data->lock);
564 
565     fd = open(data->filename, O_RDWR | O_BINARY | O_CLOEXEC, 0);
566     if (fd < 0) {
567         ret = interpret_errno(context, errno);
568         goto cleanup;
569     }
570     set_cloexec_fd(fd);
571 
572 #ifdef MSDOS_FILESYSTEM
573     /*
574      * "Disgusting bit of UNIX trivia" - that's how the writers of NFS describe
575      * the ability of UNIX to still write to a file which has been unlinked.
576      * Naturally, the PC can't do this.  As a result, we have to delete the
577      * file after we wipe it clean, but that throws off all the error handling
578      * code.  So we have do the work ourselves.
579      */
580     st = fstat(fd, &buf);
581     if (st == -1) {
582         ret = interpret_errno(context, errno);
583         size = 0;               /* Nothing to wipe clean */
584     } else {
585         size = (unsigned long)buf.st_size;
586     }
587 
588     memset(zeros, 0, BUFSIZ);
589     while (size > 0) {
590         wlen = (int)((size > BUFSIZ) ? BUFSIZ : size); /* How much to write */
591         i = write(fd, zeros, wlen);
592         if (i < 0) {
593             ret = interpret_errno(context, errno);
594             /* Don't jump to cleanup--we still want to delete the file. */
595             break;
596         }
597         size -= i;
598     }
599 
600     (void)close(fd);
601 
602     st = unlink(data->filename);
603     if (st < 0) {
604         ret = interpret_errno(context, errno);
605         goto cleanup;
606     }
607 
608 #else /* MSDOS_FILESYSTEM */
609 
610     st = unlink(data->filename);
611     if (st < 0) {
612         ret = interpret_errno(context, errno);
613         (void)close(fd);
614         goto cleanup;
615     }
616 
617     st = fstat(fd, &buf);
618     if (st < 0) {
619         ret = interpret_errno(context, errno);
620         (void)close(fd);
621         goto cleanup;
622     }
623 
624     /* XXX This may not be legal XXX */
625     size = (unsigned long)buf.st_size;
626     memset(zeros, 0, BUFSIZ);
627     for (i = 0; i < size / BUFSIZ; i++) {
628         if (write(fd, zeros, BUFSIZ) < 0) {
629             ret = interpret_errno(context, errno);
630             (void)close(fd);
631             goto cleanup;
632         }
633     }
634 
635     wlen = size % BUFSIZ;
636     if (write(fd, zeros, wlen) < 0) {
637         ret = interpret_errno(context, errno);
638         (void)close(fd);
639         goto cleanup;
640     }
641 
642     st = close(fd);
643 
644     if (st)
645         ret = interpret_errno(context, errno);
646 
647 #endif /* MSDOS_FILESYSTEM */
648 
649 cleanup:
650     (void)set_errmsg_filename(context, ret, data->filename);
651     k5_cc_mutex_unlock(context, &data->lock);
652     free_fccdata(context, data);
653     free(id);
654 
655     krb5_change_cache();
656     return ret;
657 }
658 
659 extern const krb5_cc_ops krb5_fcc_ops;
660 
661 /* Create a file ccache handle for the pathname given by residual. */
662 static krb5_error_code KRB5_CALLCONV
fcc_resolve(krb5_context context,krb5_ccache * id,const char * residual)663 fcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
664 {
665     krb5_ccache lid;
666     krb5_error_code ret;
667     fcc_data *data;
668 
669     data = malloc(sizeof(fcc_data));
670     if (data == NULL)
671         return KRB5_CC_NOMEM;
672     data->filename = strdup(residual);
673     if (data->filename == NULL) {
674         free(data);
675         return KRB5_CC_NOMEM;
676     }
677     ret = k5_cc_mutex_init(&data->lock);
678     if (ret) {
679         free(data->filename);
680         free(data);
681         return ret;
682     }
683 
684     lid = malloc(sizeof(struct _krb5_ccache));
685     if (lid == NULL) {
686         free_fccdata(context, data);
687         return KRB5_CC_NOMEM;
688     }
689 
690     lid->ops = &krb5_fcc_ops;
691     lid->data = data;
692     lid->magic = KV5M_CCACHE;
693 
694     /* Other routines will get errors on open, and callers must expect them, if
695      * cache is non-existent/unusable. */
696     *id = lid;
697     return 0;
698 }
699 
700 /* Prepare for a sequential iteration over the cache file. */
701 static krb5_error_code KRB5_CALLCONV
fcc_start_seq_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)702 fcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
703 {
704     krb5_fcc_cursor *fcursor = NULL;
705     krb5_error_code ret;
706     krb5_principal princ = NULL;
707     fcc_data *data = id->data;
708     FILE *fp = NULL;
709     int version;
710 
711     k5_cc_mutex_lock(context, &data->lock);
712 
713     fcursor = malloc(sizeof(krb5_fcc_cursor));
714     if (fcursor == NULL) {
715         ret = KRB5_CC_NOMEM;
716         goto cleanup;
717     }
718 
719     /* Open the cache file and read the header. */
720     ret = open_cache_file(context, data->filename, FALSE, &fp);
721     if (ret)
722         goto cleanup;
723     ret = read_header(context, fp, &version);
724     if (ret)
725         goto cleanup;
726 
727     /* Read past the default client principal name. */
728     ret = read_principal(context, fp, version, &princ);
729     if (ret)
730         goto cleanup;
731 
732     /* Drop the shared file lock but retain the file handle. */
733     (void)krb5_unlock_file(context, fileno(fp));
734     fcursor->fp = fp;
735     fp = NULL;
736     fcursor->version = version;
737     *cursor = (krb5_cc_cursor)fcursor;
738     fcursor = NULL;
739 
740 cleanup:
741     (void)close_cache_file(context, fp);
742     free(fcursor);
743     krb5_free_principal(context, princ);
744     k5_cc_mutex_unlock(context, &data->lock);
745     return set_errmsg_filename(context, ret, data->filename);
746 }
747 
748 /*
749  * Return true if cred is a removed entry.  We assume that any active entry
750  * with endtime=0 (such as a config entry or gssproxy encrypted credential)
751  * will also have authtime=0.
752  */
753 static inline krb5_boolean
cred_removed(krb5_creds * c)754 cred_removed(krb5_creds *c)
755 {
756     return c->times.endtime == 0 && c->times.authtime != 0;
757 }
758 
759 /* Get the next credential from the cache file. */
760 static krb5_error_code KRB5_CALLCONV
fcc_next_cred(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor,krb5_creds * creds)761 fcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
762               krb5_creds *creds)
763 {
764     krb5_error_code ret;
765     krb5_fcc_cursor *fcursor = *cursor;
766     fcc_data *data = id->data;
767     struct k5buf buf;
768     size_t maxsize;
769     krb5_boolean file_locked = FALSE;
770 
771     memset(creds, 0, sizeof(*creds));
772     k5_cc_mutex_lock(context, &data->lock);
773     k5_buf_init_dynamic_zap(&buf);
774 
775     ret = krb5_lock_file(context, fileno(fcursor->fp), KRB5_LOCKMODE_SHARED);
776     if (ret)
777         goto cleanup;
778     file_locked = TRUE;
779 
780     for (;;) {
781         /* Load a marshalled cred into memory. */
782         ret = get_size(context, fcursor->fp, &maxsize);
783         if (ret)
784             goto cleanup;
785         ret = load_cred(context, fcursor->fp, fcursor->version, maxsize, &buf);
786         if (ret)
787             goto cleanup;
788         ret = k5_buf_status(&buf);
789         if (ret)
790             goto cleanup;
791 
792         /* Unmarshal it from buf into creds. */
793         ret = k5_unmarshal_cred(buf.data, buf.len, fcursor->version, creds);
794         if (ret)
795             goto cleanup;
796 
797         /* Keep going if this entry has been removed; otherwise stop. */
798         if (!cred_removed(creds))
799             break;
800 
801         k5_buf_truncate(&buf, 0);
802         krb5_free_cred_contents(context, creds);
803     }
804 
805 cleanup:
806     if (file_locked)
807         (void)krb5_unlock_file(context, fileno(fcursor->fp));
808     k5_cc_mutex_unlock(context, &data->lock);
809     k5_buf_free(&buf);
810     return set_errmsg_filename(context, ret, data->filename);
811 }
812 
813 /* Release an iteration cursor. */
814 static krb5_error_code KRB5_CALLCONV
fcc_end_seq_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)815 fcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
816 {
817     krb5_fcc_cursor *fcursor = *cursor;
818 
819     (void)fclose(fcursor->fp);
820     free(fcursor);
821     *cursor = NULL;
822     return 0;
823 }
824 
825 /* Generate a unique file ccache using the given template (which will be
826  * modified to contain the actual name of the file). */
827 krb5_error_code
krb5int_fcc_new_unique(krb5_context context,char * template,krb5_ccache * id)828 krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id)
829 {
830     krb5_ccache lid;
831     int fd;
832     krb5_error_code ret;
833     fcc_data *data;
834     char fcc_fvno[2];
835     int16_t fcc_flen = 0;
836     int errsave, cnt;
837 
838     fd = mkstemp(template);
839     if (fd == -1)
840         return interpret_errno(context, errno);
841     set_cloexec_fd(fd);
842 
843     /* Allocate memory */
844     data = malloc(sizeof(fcc_data));
845     if (data == NULL) {
846         close(fd);
847         unlink(template);
848         return KRB5_CC_NOMEM;
849     }
850 
851     data->filename = strdup(template);
852     if (data->filename == NULL) {
853         free(data);
854         close(fd);
855         unlink(template);
856         return KRB5_CC_NOMEM;
857     }
858 
859     ret = k5_cc_mutex_init(&data->lock);
860     if (ret) {
861         free(data->filename);
862         free(data);
863         close(fd);
864         unlink(template);
865         return ret;
866     }
867     k5_cc_mutex_lock(context, &data->lock);
868 
869     /* Ignore user's umask, set mode = 0600 */
870 #ifndef HAVE_FCHMOD
871 #ifdef HAVE_CHMOD
872     chmod(data->filename, S_IRUSR | S_IWUSR);
873 #endif
874 #else
875     fchmod(fd, S_IRUSR | S_IWUSR);
876 #endif
877     store_16_be(context->fcc_default_format, fcc_fvno);
878     cnt = write(fd, &fcc_fvno, 2);
879     if (cnt != 2) {
880         errsave = errno;
881         (void)close(fd);
882         (void)unlink(data->filename);
883         ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO;
884         goto err_out;
885     }
886     /* For version 4 we save a length for the rest of the header */
887     if (context->fcc_default_format == FVNO_BASE + 4) {
888         cnt = write(fd, &fcc_flen, sizeof(fcc_flen));
889         if (cnt != sizeof(fcc_flen)) {
890             errsave = errno;
891             (void)close(fd);
892             (void)unlink(data->filename);
893             ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO;
894             goto err_out;
895         }
896     }
897     if (close(fd) == -1) {
898         errsave = errno;
899         (void)unlink(data->filename);
900         ret = interpret_errno(context, errsave);
901         goto err_out;
902     }
903 
904     k5_cc_mutex_assert_locked(context, &data->lock);
905     k5_cc_mutex_unlock(context, &data->lock);
906     lid = malloc(sizeof(*lid));
907     if (lid == NULL) {
908         free_fccdata(context, data);
909         return KRB5_CC_NOMEM;
910     }
911 
912     lid->ops = &krb5_fcc_ops;
913     lid->data = data;
914     lid->magic = KV5M_CCACHE;
915 
916     *id = lid;
917 
918     krb5_change_cache();
919     return 0;
920 
921 err_out:
922     (void)set_errmsg_filename(context, ret, data->filename);
923     k5_cc_mutex_unlock(context, &data->lock);
924     k5_cc_mutex_destroy(&data->lock);
925     free(data->filename);
926     free(data);
927     return ret;
928 }
929 
930 /*
931  * Create a new file cred cache whose name is guaranteed to be unique.  The
932  * name begins with the string TKT_ROOT (from fcc.h).  The cache file is not
933  * opened, but the new filename is reserved.
934  */
935 static krb5_error_code KRB5_CALLCONV
fcc_generate_new(krb5_context context,krb5_ccache * id)936 fcc_generate_new(krb5_context context, krb5_ccache *id)
937 {
938     char scratch[sizeof(TKT_ROOT) + 7]; /* Room for XXXXXX and terminator */
939 
940     (void)snprintf(scratch, sizeof(scratch), "%sXXXXXX", TKT_ROOT);
941     return krb5int_fcc_new_unique(context, scratch, id);
942 }
943 
944 /* Return an alias to the pathname of the cache file. */
945 static const char * KRB5_CALLCONV
fcc_get_name(krb5_context context,krb5_ccache id)946 fcc_get_name(krb5_context context, krb5_ccache id)
947 {
948     return ((fcc_data *)id->data)->filename;
949 }
950 
951 /* Retrieve a copy of the default principal, if the cache is initialized. */
952 static krb5_error_code KRB5_CALLCONV
fcc_get_principal(krb5_context context,krb5_ccache id,krb5_principal * princ)953 fcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
954 {
955     krb5_error_code ret;
956     fcc_data *data = id->data;
957     FILE *fp = NULL;
958     int version;
959 
960     k5_cc_mutex_lock(context, &data->lock);
961     ret = open_cache_file(context, data->filename, FALSE, &fp);
962     if (ret)
963         goto cleanup;
964     ret = read_header(context, fp, &version);
965     if (ret)
966         goto cleanup;
967     ret = read_principal(context, fp, version, princ);
968 
969 cleanup:
970     (void)close_cache_file(context, fp);
971     k5_cc_mutex_unlock(context, &data->lock);
972     return set_errmsg_filename(context, ret, data->filename);
973 }
974 
975 /* Search for a credential within the cache file. */
976 static krb5_error_code KRB5_CALLCONV
fcc_retrieve(krb5_context context,krb5_ccache id,krb5_flags whichfields,krb5_creds * mcreds,krb5_creds * creds)977 fcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
978              krb5_creds *mcreds, krb5_creds *creds)
979 {
980     krb5_error_code ret;
981 
982     ret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, creds);
983     return set_errmsg_filename(context, ret, ((fcc_data *)id->data)->filename);
984 }
985 
986 /* Store a credential in the cache file. */
987 static krb5_error_code KRB5_CALLCONV
fcc_store(krb5_context context,krb5_ccache id,krb5_creds * creds)988 fcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
989 {
990     krb5_error_code ret, ret2;
991     fcc_data *data = id->data;
992     FILE *fp = NULL;
993     int version;
994     struct k5buf buf = EMPTY_K5BUF;
995     ssize_t nwritten;
996 
997     k5_cc_mutex_lock(context, &data->lock);
998 
999     /* Open the cache file for O_APPEND writing. */
1000     ret = open_cache_file(context, data->filename, TRUE, &fp);
1001     if (ret)
1002         goto cleanup;
1003     ret = read_header(context, fp, &version);
1004     if (ret)
1005         goto cleanup;
1006 
1007     /* Marshal the cred and write it to the file with a single append write. */
1008     k5_buf_init_dynamic_zap(&buf);
1009     k5_marshal_cred(&buf, version, creds);
1010     ret = k5_buf_status(&buf);
1011     if (ret)
1012         goto cleanup;
1013     nwritten = write(fileno(fp), buf.data, buf.len);
1014     if (nwritten == -1)
1015         ret = interpret_errno(context, errno);
1016     if ((size_t)nwritten != buf.len)
1017         ret = KRB5_CC_IO;
1018 
1019     krb5_change_cache();
1020 
1021 cleanup:
1022     k5_buf_free(&buf);
1023     ret2 = close_cache_file(context, fp);
1024     k5_cc_mutex_unlock(context, &data->lock);
1025     return set_errmsg_filename(context, ret ? ret : ret2, data->filename);
1026 }
1027 
1028 /*
1029  * Overwrite cred in the ccache file with an entry that should not match any
1030  * reasonable search.  Deletion is not guaranteed.  This method is originally
1031  * from Heimdal, with the addition of setting authtime to -1.
1032  */
1033 static krb5_error_code
delete_cred(krb5_context context,krb5_ccache cache,krb5_cc_cursor * cursor,krb5_creds * cred)1034 delete_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
1035             krb5_creds *cred)
1036 {
1037     krb5_error_code ret;
1038     krb5_fcc_cursor *fcursor = *cursor;
1039     fcc_data *data = cache->data;
1040     struct k5buf expected = EMPTY_K5BUF, overwrite = EMPTY_K5BUF;
1041     int fd = -1;
1042     uint8_t *on_disk = NULL;
1043     ssize_t rwret;
1044     off_t start_offset;
1045 
1046     k5_buf_init_dynamic_zap(&expected);
1047     k5_buf_init_dynamic_zap(&overwrite);
1048 
1049     /* Re-marshal cred to get its byte representation in the file. */
1050     k5_marshal_cred(&expected, fcursor->version, cred);
1051     ret = k5_buf_status(&expected);
1052     if (ret)
1053         goto cleanup;
1054 
1055     /*
1056      * Mark the cred expired so that it will be skipped over by any future
1057      * match checks.  Heimdal only sets endtime, but we also set authtime to
1058      * distinguish from gssproxy's creds.
1059      */
1060     cred->times.endtime = 0;
1061     cred->times.authtime = -1;
1062 
1063     /* For config entries, also change the realm so that other implementations
1064      * won't match them. */
1065     if (data_eq_string(cred->server->realm, "X-CACHECONF:"))
1066         memcpy(cred->server->realm.data, "X-RMED-CONF:", 12);
1067 
1068     k5_marshal_cred(&overwrite, fcursor->version, cred);
1069     ret = k5_buf_status(&overwrite);
1070     if (ret)
1071         goto cleanup;
1072 
1073     if (expected.len != overwrite.len) {
1074         ret = KRB5_CC_FORMAT;
1075         goto cleanup;
1076     }
1077 
1078     /* Get a non-O_APPEND handle to the raw file. */
1079     fd = open(data->filename, O_RDWR | O_BINARY | O_CLOEXEC);
1080     if (fd == -1) {
1081         ret = interpret_errno(context, errno);
1082         goto cleanup;
1083     }
1084 
1085     start_offset = ftell(fcursor->fp);
1086     if (start_offset == -1) {
1087         ret = interpret_errno(context, errno);
1088         goto cleanup;
1089     }
1090     start_offset -= expected.len;
1091 
1092     /* Read the bytes at the entry to be overwritten. */
1093     if (lseek(fd, start_offset, SEEK_SET) == -1) {
1094         ret = interpret_errno(context, errno);
1095         goto cleanup;
1096     }
1097     on_disk = k5alloc(expected.len, &ret);
1098     if (ret != 0)
1099         goto cleanup;
1100     rwret = read(fd, on_disk, expected.len);
1101     if (rwret < 0) {
1102         ret = interpret_errno(context, errno);
1103         goto cleanup;
1104     } else if ((size_t)rwret != expected.len) {
1105         ret = KRB5_CC_FORMAT;
1106         goto cleanup;
1107     }
1108 
1109     /*
1110      * If the bytes have changed, either someone else removed the same cred or
1111      * the cache was reinitialized.  Either way the cred is no longer present,
1112      * so return successfully.
1113      */
1114     if (memcmp(on_disk, expected.data, expected.len) != 0)
1115         goto cleanup;
1116 
1117     /* Write out the altered entry. */
1118     if (lseek(fd, start_offset, SEEK_SET) == -1) {
1119         ret = interpret_errno(context, errno);
1120         goto cleanup;
1121     }
1122     rwret = write(fd, overwrite.data, overwrite.len);
1123     if (rwret < 0) {
1124         ret = interpret_errno(context, errno);
1125         goto cleanup;
1126     }
1127 
1128 cleanup:
1129     if (fd >= 0)
1130         close(fd);
1131     zapfree(on_disk, expected.len);
1132     k5_buf_free(&expected);
1133     k5_buf_free(&overwrite);
1134     return ret;
1135 }
1136 
1137 /* Remove the given creds from the ccache file. */
1138 static krb5_error_code KRB5_CALLCONV
fcc_remove_cred(krb5_context context,krb5_ccache cache,krb5_flags flags,krb5_creds * creds)1139 fcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
1140                 krb5_creds *creds)
1141 {
1142     krb5_error_code ret;
1143     krb5_cc_cursor cursor;
1144     krb5_creds cur;
1145 
1146     ret = krb5_cc_start_seq_get(context, cache, &cursor);
1147     if (ret)
1148         return ret;
1149 
1150     for (;;) {
1151         ret = krb5_cc_next_cred(context, cache, &cursor, &cur);
1152         if (ret)
1153             break;
1154 
1155         if (krb5int_cc_creds_match_request(context, flags, creds, &cur))
1156             ret = delete_cred(context, cache, &cursor, &cur);
1157         krb5_free_cred_contents(context, &cur);
1158         if (ret)
1159             break;
1160     }
1161 
1162     krb5_cc_end_seq_get(context, cache, &cursor);
1163     return (ret == KRB5_CC_END) ? 0 : ret;
1164 }
1165 
1166 static krb5_error_code KRB5_CALLCONV
fcc_set_flags(krb5_context context,krb5_ccache id,krb5_flags flags)1167 fcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
1168 {
1169     return 0;
1170 }
1171 
1172 static krb5_error_code KRB5_CALLCONV
fcc_get_flags(krb5_context context,krb5_ccache id,krb5_flags * flags)1173 fcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
1174 {
1175     *flags = 0;
1176     return 0;
1177 }
1178 
1179 /* Prepare to iterate over the caches in the per-type collection. */
1180 static krb5_error_code KRB5_CALLCONV
fcc_ptcursor_new(krb5_context context,krb5_cc_ptcursor * cursor)1181 fcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor)
1182 {
1183     krb5_cc_ptcursor n = NULL;
1184     struct krb5_fcc_ptcursor_data *cdata = NULL;
1185 
1186     *cursor = NULL;
1187 
1188     n = malloc(sizeof(*n));
1189     if (n == NULL)
1190         return ENOMEM;
1191     n->ops = &krb5_fcc_ops;
1192     cdata = malloc(sizeof(*cdata));
1193     if (cdata == NULL) {
1194         free(n);
1195         return ENOMEM;
1196     }
1197     cdata->first = TRUE;
1198     n->data = cdata;
1199     *cursor = n;
1200     return 0;
1201 }
1202 
1203 /* Get the next cache in the per-type collection.  The FILE per-type collection
1204  * contains only the context's default cache if it is a file cache. */
1205 static krb5_error_code KRB5_CALLCONV
fcc_ptcursor_next(krb5_context context,krb5_cc_ptcursor cursor,krb5_ccache * cache_out)1206 fcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
1207                   krb5_ccache *cache_out)
1208 {
1209     krb5_error_code ret;
1210     struct krb5_fcc_ptcursor_data *cdata = cursor->data;
1211     const char *defname, *residual;
1212     krb5_ccache cache;
1213     struct stat sb;
1214 
1215     *cache_out = NULL;
1216     if (!cdata->first)
1217         return 0;
1218     cdata->first = FALSE;
1219 
1220     defname = krb5_cc_default_name(context);
1221     if (!defname)
1222         return 0;
1223 
1224     /* Check if the default has type FILE or no type; find the residual. */
1225     if (strncmp(defname, "FILE:", 5) == 0)
1226         residual = defname + 5;
1227     else if (strchr(defname + 2, ':') == NULL)  /* Skip drive prefix if any. */
1228         residual = defname;
1229     else
1230         return 0;
1231 
1232     /* Don't yield a nonexistent default file cache. */
1233     if (stat(residual, &sb) != 0)
1234         return 0;
1235 
1236     ret = krb5_cc_resolve(context, defname, &cache);
1237     if (ret)
1238         return set_errmsg_filename(context, ret, defname);
1239     *cache_out = cache;
1240     return 0;
1241 }
1242 
1243 /* Release a per-type collection iteration cursor. */
1244 static krb5_error_code KRB5_CALLCONV
fcc_ptcursor_free(krb5_context context,krb5_cc_ptcursor * cursor)1245 fcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
1246 {
1247     if (*cursor == NULL)
1248         return 0;
1249     free((*cursor)->data);
1250     free(*cursor);
1251     *cursor = NULL;
1252     return 0;
1253 }
1254 
1255 /* Lock the cache handle against other threads.  (This does not lock the cache
1256  * file against other processes.) */
1257 static krb5_error_code KRB5_CALLCONV
fcc_lock(krb5_context context,krb5_ccache id)1258 fcc_lock(krb5_context context, krb5_ccache id)
1259 {
1260     fcc_data *data = id->data;
1261     k5_cc_mutex_lock(context, &data->lock);
1262     return 0;
1263 }
1264 
1265 /* Unlock the cache handle. */
1266 static krb5_error_code KRB5_CALLCONV
fcc_unlock(krb5_context context,krb5_ccache id)1267 fcc_unlock(krb5_context context, krb5_ccache id)
1268 {
1269     fcc_data *data = id->data;
1270     k5_cc_mutex_unlock(context, &data->lock);
1271     return 0;
1272 }
1273 
1274 static krb5_error_code KRB5_CALLCONV
fcc_replace(krb5_context context,krb5_ccache id,krb5_principal princ,krb5_creds ** creds)1275 fcc_replace(krb5_context context, krb5_ccache id, krb5_principal princ,
1276             krb5_creds **creds)
1277 {
1278     krb5_error_code ret;
1279     fcc_data *data = id->data;
1280     char *tmpname = NULL;
1281     int i, st, fd = -1, version = context->fcc_default_format - FVNO_BASE;
1282     ssize_t nwritten;
1283     struct k5buf buf = EMPTY_K5BUF;
1284     krb5_boolean tmpfile_exists = FALSE;
1285 
1286     if (asprintf(&tmpname, "%s.XXXXXX", data->filename) < 0)
1287         return ENOMEM;
1288     fd = mkstemp(tmpname);
1289     if (fd < 0)
1290         goto errno_cleanup;
1291     tmpfile_exists = TRUE;
1292 
1293     k5_buf_init_dynamic_zap(&buf);
1294     marshal_header(context, &buf, princ);
1295     for (i = 0; creds[i] != NULL; i++)
1296         k5_marshal_cred(&buf, version, creds[i]);
1297     ret = k5_buf_status(&buf);
1298     if (ret)
1299         goto cleanup;
1300 
1301     nwritten = write(fd, buf.data, buf.len);
1302     if (nwritten == -1)
1303         goto errno_cleanup;
1304     if ((size_t)nwritten != buf.len) {
1305         ret = KRB5_CC_IO;
1306         goto cleanup;
1307     }
1308     st = close(fd);
1309     fd = -1;
1310     if (st != 0)
1311         goto errno_cleanup;
1312 
1313     st = rename(tmpname, data->filename);
1314     if (st != 0)
1315         goto errno_cleanup;
1316     tmpfile_exists = FALSE;
1317 
1318 cleanup:
1319     k5_buf_free(&buf);
1320     if (fd != -1)
1321         close(fd);
1322     if (tmpfile_exists)
1323         unlink(tmpname);
1324     free(tmpname);
1325     return ret;
1326 
1327 errno_cleanup:
1328     ret = interpret_errno(context, errno);
1329     goto cleanup;
1330 }
1331 
1332 /* Translate a system errno value to a Kerberos com_err code. */
1333 static krb5_error_code
interpret_errno(krb5_context context,int errnum)1334 interpret_errno(krb5_context context, int errnum)
1335 {
1336     krb5_error_code ret;
1337 
1338     switch (errnum) {
1339     case ENOENT:
1340     case ENOTDIR:
1341 #ifdef ELOOP
1342     case ELOOP:
1343 #endif
1344 #ifdef ENAMETOOLONG
1345     case ENAMETOOLONG:
1346 #endif
1347         ret = KRB5_FCC_NOFILE;
1348         break;
1349     case EPERM:
1350     case EACCES:
1351 #ifdef EISDIR
1352     case EISDIR:                /* Mac doesn't have EISDIR */
1353 #endif
1354     case EROFS:
1355         ret = KRB5_FCC_PERM;
1356         break;
1357     case EINVAL:
1358     case EEXIST:
1359     case EFAULT:
1360     case EBADF:
1361 #ifdef EWOULDBLOCK
1362     case EWOULDBLOCK:
1363 #endif
1364         ret = KRB5_FCC_INTERNAL;
1365         break;
1366     /*
1367      * The rest all map to KRB5_CC_IO.  These errnos are listed to
1368      * document that they've been considered explicitly:
1369      *
1370      *  - EDQUOT
1371      *  - ENOSPC
1372      *  - EIO
1373      *  - ENFILE
1374      *  - EMFILE
1375      *  - ENXIO
1376      *  - EBUSY
1377      *  - ETXTBSY
1378      */
1379     default:
1380         ret = KRB5_CC_IO;
1381         break;
1382     }
1383     return ret;
1384 }
1385 
1386 const krb5_cc_ops krb5_fcc_ops = {
1387     0,
1388     "FILE",
1389     fcc_get_name,
1390     fcc_resolve,
1391     fcc_generate_new,
1392     fcc_initialize,
1393     fcc_destroy,
1394     fcc_close,
1395     fcc_store,
1396     fcc_retrieve,
1397     fcc_get_principal,
1398     fcc_start_seq_get,
1399     fcc_next_cred,
1400     fcc_end_seq_get,
1401     fcc_remove_cred,
1402     fcc_set_flags,
1403     fcc_get_flags,
1404     fcc_ptcursor_new,
1405     fcc_ptcursor_next,
1406     fcc_ptcursor_free,
1407     fcc_replace,
1408     NULL, /* wasdefault */
1409     fcc_lock,
1410     fcc_unlock,
1411     NULL, /* switch_to */
1412 };
1413 
1414 #if defined(_WIN32)
1415 /*
1416  * krb5_change_cache should be called after the cache changes.
1417  * A notification message is is posted out to all top level
1418  * windows so that they may recheck the cache based on the
1419  * changes made.  We register a unique message type with which
1420  * we'll communicate to all other processes.
1421  */
1422 
1423 krb5_error_code
krb5_change_cache(void)1424 krb5_change_cache(void)
1425 {
1426     PostMessage(HWND_BROADCAST, krb5_get_notification_message(), 0, 0);
1427     return 0;
1428 }
1429 
1430 unsigned int KRB5_CALLCONV
krb5_get_notification_message(void)1431 krb5_get_notification_message(void)
1432 {
1433     static unsigned int message = 0;
1434 
1435     if (message == 0)
1436         message = RegisterWindowMessage(WM_KERBEROS5_CHANGED);
1437 
1438     return message;
1439 }
1440 #else /* _WIN32 */
1441 
1442 krb5_error_code
krb5_change_cache(void)1443 krb5_change_cache(void)
1444 {
1445     return 0;
1446 }
1447 
1448 unsigned int
krb5_get_notification_message(void)1449 krb5_get_notification_message(void)
1450 {
1451     return 0;
1452 }
1453 
1454 #endif /* _WIN32 */
1455 
1456 const krb5_cc_ops krb5_cc_file_ops = {
1457     0,
1458     "FILE",
1459     fcc_get_name,
1460     fcc_resolve,
1461     fcc_generate_new,
1462     fcc_initialize,
1463     fcc_destroy,
1464     fcc_close,
1465     fcc_store,
1466     fcc_retrieve,
1467     fcc_get_principal,
1468     fcc_start_seq_get,
1469     fcc_next_cred,
1470     fcc_end_seq_get,
1471     fcc_remove_cred,
1472     fcc_set_flags,
1473     fcc_get_flags,
1474     fcc_ptcursor_new,
1475     fcc_ptcursor_next,
1476     fcc_ptcursor_free,
1477     fcc_replace,
1478     NULL, /* wasdefault */
1479     fcc_lock,
1480     fcc_unlock,
1481     NULL, /* switch_to */
1482 };
1483