xref: /freebsd/crypto/krb5/src/lib/krb5/keytab/kt_file.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/keytab/kt_file.c */
3 /*
4  * Copyright 1990,1991,1995,2007,2008 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 /*
27  * Copyright (c) Hewlett-Packard Company 1991
28  * Released to the Massachusetts Institute of Technology for inclusion
29  * in the Kerberos source code distribution.
30  *
31  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
32  * All Rights Reserved.
33  *
34  * Export of this software from the United States of America may
35  *   require a specific license from the United States Government.
36  *   It is the responsibility of any person or organization contemplating
37  *   export to obtain such a license before exporting.
38  *
39  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
40  * distribute this software and its documentation for any purpose and
41  * without fee is hereby granted, provided that the above copyright
42  * notice appear in all copies and that both that copyright notice and
43  * this permission notice appear in supporting documentation, and that
44  * the name of M.I.T. not be used in advertising or publicity pertaining
45  * to distribution of the software without specific, written prior
46  * permission.  Furthermore if you modify this software you must label
47  * your software as modified software and not distribute it in such a
48  * fashion that it might be confused with the original M.I.T. software.
49  * M.I.T. makes no representations about the suitability of
50  * this software for any purpose.  It is provided "as is" without express
51  * or implied warranty.
52  */
53 
54 #ifndef LEAN_CLIENT
55 
56 #include "k5-int.h"
57 #include "../os/os-proto.h"
58 #include <stdio.h>
59 
60 /*
61  * Information needed by internal routines of the file-based ticket
62  * cache implementation.
63  */
64 
65 
66 /*
67  * Constants
68  */
69 
70 #define KRB5_KT_VNO_1   0x0501  /* krb v5, keytab version 1 (DCE compat) */
71 #define KRB5_KT_VNO     0x0502  /* krb v5, keytab version 2 (standard)  */
72 
73 #define KRB5_KT_DEFAULT_VNO KRB5_KT_VNO
74 
75 /*
76  * Types
77  */
78 typedef struct _krb5_ktfile_data {
79     char *name;                 /* Name of the file */
80     FILE *openf;                /* open file, if any. */
81     char iobuf[BUFSIZ];         /* so we can zap it later */
82     int version;                /* Version number of keytab */
83     unsigned int iter_count;    /* Number of active iterators */
84     long start_offset;          /* Starting offset after version */
85     k5_mutex_t lock;            /* Protect openf, version */
86 } krb5_ktfile_data;
87 
88 /*
89  * Some limitations:
90  *
91  * If the file OPENF is left open between calls, we have an iterator
92  * active, and OPENF is opened in read-only mode.  So, no changes
93  * can be made via that handle.
94  *
95  * An advisory file lock is used while the file is open.  Thus,
96  * multiple handles on the same underlying file cannot be used without
97  * disrupting the locking in effect.
98  *
99  * The start_offset field is only valid if the file is open.  It will
100  * almost certainly always be the same constant.  It's used so that
101  * if an iterator is active, and we start another one, we don't have
102  * to seek back to the start and re-read the version number to set
103  * the position for the iterator.
104  */
105 
106 /*
107  * Macros
108  */
109 #define KTPRIVATE(id) ((krb5_ktfile_data *)(id)->data)
110 #define KTFILENAME(id) (((krb5_ktfile_data *)(id)->data)->name)
111 #define KTFILEP(id) (((krb5_ktfile_data *)(id)->data)->openf)
112 #define KTFILEBUFP(id) (((krb5_ktfile_data *)(id)->data)->iobuf)
113 #define KTVERSION(id) (((krb5_ktfile_data *)(id)->data)->version)
114 #define KTITERS(id) (((krb5_ktfile_data *)(id)->data)->iter_count)
115 #define KTSTARTOFF(id) (((krb5_ktfile_data *)(id)->data)->start_offset)
116 #define KTLOCK(id) k5_mutex_lock(&((krb5_ktfile_data *)(id)->data)->lock)
117 #define KTUNLOCK(id) k5_mutex_unlock(&((krb5_ktfile_data *)(id)->data)->lock)
118 #define KTCHECKLOCK(id) k5_mutex_assert_locked(&((krb5_ktfile_data *)(id)->data)->lock)
119 
120 extern const struct _krb5_kt_ops krb5_ktf_ops;
121 extern const struct _krb5_kt_ops krb5_ktf_writable_ops;
122 
123 static krb5_error_code KRB5_CALLCONV
124 krb5_ktfile_resolve(krb5_context, const char *, krb5_keytab *);
125 
126 static krb5_error_code KRB5_CALLCONV
127 krb5_ktfile_get_name(krb5_context, krb5_keytab, char *, unsigned int);
128 
129 static krb5_error_code KRB5_CALLCONV
130 krb5_ktfile_close(krb5_context, krb5_keytab);
131 
132 static krb5_error_code KRB5_CALLCONV
133 krb5_ktfile_get_entry(krb5_context, krb5_keytab, krb5_const_principal,
134                       krb5_kvno, krb5_enctype, krb5_keytab_entry *);
135 
136 static krb5_error_code KRB5_CALLCONV
137 krb5_ktfile_start_seq_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
138 
139 static krb5_error_code KRB5_CALLCONV
140 krb5_ktfile_get_next(krb5_context, krb5_keytab, krb5_keytab_entry *,
141                      krb5_kt_cursor *);
142 
143 static krb5_error_code KRB5_CALLCONV
144 krb5_ktfile_end_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
145 
146 /* routines to be included on extended version (write routines) */
147 static krb5_error_code KRB5_CALLCONV
148 krb5_ktfile_add(krb5_context, krb5_keytab, krb5_keytab_entry *);
149 
150 static krb5_error_code KRB5_CALLCONV
151 krb5_ktfile_remove(krb5_context, krb5_keytab, krb5_keytab_entry *);
152 
153 static krb5_error_code
154 krb5_ktfileint_openr(krb5_context, krb5_keytab);
155 
156 static krb5_error_code
157 krb5_ktfileint_openw(krb5_context, krb5_keytab);
158 
159 static krb5_error_code
160 krb5_ktfileint_close(krb5_context, krb5_keytab);
161 
162 static krb5_error_code
163 krb5_ktfileint_read_entry(krb5_context, krb5_keytab, krb5_keytab_entry *);
164 
165 static krb5_error_code
166 krb5_ktfileint_write_entry(krb5_context, krb5_keytab, krb5_keytab_entry *);
167 
168 static krb5_error_code
169 krb5_ktfileint_delete_entry(krb5_context, krb5_keytab, krb5_int32);
170 
171 static krb5_error_code
172 krb5_ktfileint_internal_read_entry(krb5_context, krb5_keytab,
173                                    krb5_keytab_entry *, krb5_int32 *);
174 
175 static krb5_error_code
176 krb5_ktfileint_size_entry(krb5_context, krb5_keytab_entry *, krb5_int32 *);
177 
178 static krb5_error_code
179 krb5_ktfileint_find_slot(krb5_context, krb5_keytab, krb5_int32 *,
180                          krb5_int32 *);
181 
182 
183 /*
184  * This is an implementation specific resolver.  It returns a keytab id
185  * initialized with file keytab routines.
186  */
187 
188 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_resolve(krb5_context context,const char * name,krb5_keytab * id_out)189 krb5_ktfile_resolve(krb5_context context, const char *name,
190                     krb5_keytab *id_out)
191 {
192     krb5_ktfile_data *data = NULL;
193     krb5_error_code err = ENOMEM;
194     krb5_keytab id;
195 
196     *id_out = NULL;
197 
198     id = calloc(1, sizeof(*id));
199     if (id == NULL)
200         return ENOMEM;
201 
202     id->ops = &krb5_ktf_ops;
203     data = calloc(1, sizeof(krb5_ktfile_data));
204     if (data == NULL)
205         goto cleanup;
206 
207     data->name = strdup(name);
208     if (data->name == NULL)
209         goto cleanup;
210 
211     err = k5_mutex_init(&data->lock);
212     if (err)
213         goto cleanup;
214 
215     data->openf = 0;
216     data->version = 0;
217     data->iter_count = 0;
218 
219     id->data = (krb5_pointer) data;
220     id->magic = KV5M_KEYTAB;
221     *id_out = id;
222     return 0;
223 cleanup:
224     if (data)
225         free(data->name);
226     free(data);
227     free(id);
228     return err;
229 }
230 
231 
232 /*
233  * "Close" a file-based keytab and invalidate the id.  This means
234  * free memory hidden in the structures.
235  */
236 
237 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_close(krb5_context context,krb5_keytab id)238 krb5_ktfile_close(krb5_context context, krb5_keytab id)
239 /*
240  * This routine is responsible for freeing all memory allocated
241  * for this keytab.  There are no system resources that need
242  * to be freed nor are there any open files.
243  *
244  * This routine should undo anything done by krb5_ktfile_resolve().
245  */
246 {
247     free(KTFILENAME(id));
248     zap(KTFILEBUFP(id), BUFSIZ);
249     k5_mutex_destroy(&((krb5_ktfile_data *)id->data)->lock);
250     free(id->data);
251     id->ops = 0;
252     free(id);
253     return (0);
254 }
255 
256 /* Return true if k1 is more recent than k2, applying wraparound heuristics. */
257 static krb5_boolean
more_recent(const krb5_keytab_entry * k1,const krb5_keytab_entry * k2)258 more_recent(const krb5_keytab_entry *k1, const krb5_keytab_entry *k2)
259 {
260     /*
261      * If a small kvno was written at the same time or later than a large kvno,
262      * the kvno probably wrapped at some boundary, so consider the small kvno
263      * more recent.  Wraparound can happen due to pre-1.14 keytab file format
264      * limitations (8-bit kvno storage), pre-1.14 kadmin protocol limitations
265      * (8-bit kvno marshalling), or KDB limitations (16-bit kvno storage).
266      */
267     if (!ts_after(k2->timestamp, k1->timestamp) &&
268         k1->vno < 128 && k2->vno > 240)
269         return TRUE;
270     if (!ts_after(k1->timestamp, k2->timestamp) &&
271         k1->vno > 240 && k2->vno < 128)
272         return FALSE;
273 
274     /* Otherwise do a simple version comparison. */
275     return k1->vno > k2->vno;
276 }
277 
278 /*
279  * This is the get_entry routine for the file based keytab implementation.
280  * It opens the keytab file, and either retrieves the entry or returns
281  * an error.
282  */
283 
284 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_get_entry(krb5_context context,krb5_keytab id,krb5_const_principal principal,krb5_kvno kvno,krb5_enctype enctype,krb5_keytab_entry * entry)285 krb5_ktfile_get_entry(krb5_context context, krb5_keytab id,
286                       krb5_const_principal principal, krb5_kvno kvno,
287                       krb5_enctype enctype, krb5_keytab_entry *entry)
288 {
289     krb5_keytab_entry cur_entry, new_entry;
290     krb5_error_code kerror = 0;
291     int found_wrong_kvno = 0;
292     int was_open;
293     char *princname;
294 
295     KTLOCK(id);
296 
297     if (KTFILEP(id) != NULL) {
298         was_open = 1;
299 
300         if (fseek(KTFILEP(id), KTSTARTOFF(id), SEEK_SET) == -1) {
301             KTUNLOCK(id);
302             return errno;
303         }
304     } else {
305         was_open = 0;
306 
307         /* Open the keyfile for reading */
308         if ((kerror = krb5_ktfileint_openr(context, id))) {
309             KTUNLOCK(id);
310             return(kerror);
311         }
312     }
313 
314     /*
315      * For efficiency and simplicity, we'll use a while true that
316      * is exited with a break statement.
317      */
318     cur_entry.principal = 0;
319     cur_entry.vno = 0;
320     cur_entry.key.contents = 0;
321 
322     while (TRUE) {
323         if ((kerror = krb5_ktfileint_read_entry(context, id, &new_entry)))
324             break;
325 
326         /* by the time this loop exits, it must either free cur_entry,
327            and copy new_entry there, or free new_entry.  Otherwise, it
328            leaks. */
329 
330         /* if the principal isn't the one requested, free new_entry
331            and continue to the next. */
332 
333         if (!krb5_principal_compare(context, principal, new_entry.principal)) {
334             krb5_kt_free_entry(context, &new_entry);
335             continue;
336         }
337 
338         /* If the enctype is not ignored and doesn't match, free new_entry and
339            continue to the next. */
340         if (enctype != IGNORE_ENCTYPE && enctype != new_entry.key.enctype) {
341             krb5_kt_free_entry(context, &new_entry);
342             continue;
343         }
344 
345         if (kvno == IGNORE_VNO || new_entry.vno == IGNORE_VNO) {
346             /* If this entry is more recent (or the first match), free the
347              * current and keep the new.  Otherwise, free the new. */
348             if (cur_entry.principal == NULL ||
349                 more_recent(&new_entry, &cur_entry)) {
350                 krb5_kt_free_entry(context, &cur_entry);
351                 cur_entry = new_entry;
352             } else {
353                 krb5_kt_free_entry(context, &new_entry);
354             }
355         } else {
356             /*
357              * If this kvno matches exactly, free the current, keep the new,
358              * and break out.  If it matches the low 8 bits of the desired
359              * kvno, remember the first match (because the recorded kvno may
360              * have been truncated due to pre-1.14 keytab format or kadmin
361              * protocol limitations) but keep looking for an exact match.
362              * Otherwise, remember that we were here so we can return the right
363              * error, and free the new.
364              */
365             if (new_entry.vno == kvno) {
366                 krb5_kt_free_entry(context, &cur_entry);
367                 cur_entry = new_entry;
368                 if (new_entry.vno == kvno)
369                     break;
370             } else if (new_entry.vno == (kvno & 0xff) &&
371                        cur_entry.principal == NULL) {
372                 cur_entry = new_entry;
373             } else {
374                 found_wrong_kvno++;
375                 krb5_kt_free_entry(context, &new_entry);
376             }
377         }
378     }
379 
380     if (kerror == KRB5_KT_END) {
381         if (cur_entry.principal)
382             kerror = 0;
383         else if (found_wrong_kvno)
384             kerror = KRB5_KT_KVNONOTFOUND;
385         else {
386             kerror = KRB5_KT_NOTFOUND;
387             if (krb5_unparse_name(context, principal, &princname) == 0) {
388                 k5_setmsg(context, kerror,
389                           _("No key table entry found for %s"), princname);
390                 free(princname);
391             }
392         }
393     }
394     if (kerror) {
395         if (was_open == 0)
396             (void) krb5_ktfileint_close(context, id);
397         KTUNLOCK(id);
398         krb5_kt_free_entry(context, &cur_entry);
399         return kerror;
400     }
401     if (was_open == 0 && (kerror = krb5_ktfileint_close(context, id)) != 0) {
402         KTUNLOCK(id);
403         krb5_kt_free_entry(context, &cur_entry);
404         return kerror;
405     }
406     KTUNLOCK(id);
407     *entry = cur_entry;
408     return 0;
409 }
410 
411 /*
412  * Get the name of the file containing a file-based keytab.
413  */
414 
415 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_get_name(krb5_context context,krb5_keytab id,char * name,unsigned int len)416 krb5_ktfile_get_name(krb5_context context, krb5_keytab id, char *name, unsigned int len)
417 /*
418  * This routine returns the name of the name of the file associated with
419  * this file-based keytab.  name is zeroed and the filename is truncated
420  * to fit in name if necessary.  The name is prefixed with PREFIX:, so that
421  * trt will happen if the name is passed back to resolve.
422  */
423 {
424     int result;
425 
426     memset(name, 0, len);
427     result = snprintf(name, len, "%s:%s", id->ops->prefix, KTFILENAME(id));
428     if (SNPRINTF_OVERFLOW(result, len))
429         return(KRB5_KT_NAME_TOOLONG);
430     return(0);
431 }
432 
433 /*
434  * krb5_ktfile_start_seq_get()
435  */
436 
437 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_start_seq_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursorp)438 krb5_ktfile_start_seq_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursorp)
439 {
440     krb5_error_code retval;
441     long *fileoff;
442 
443     KTLOCK(id);
444 
445     if (KTITERS(id) == 0) {
446         if ((retval = krb5_ktfileint_openr(context, id))) {
447             KTUNLOCK(id);
448             return retval;
449         }
450     }
451 
452     if (!(fileoff = (long *)malloc(sizeof(*fileoff)))) {
453         if (KTITERS(id) == 0)
454             krb5_ktfileint_close(context, id);
455         KTUNLOCK(id);
456         return ENOMEM;
457     }
458     *fileoff = KTSTARTOFF(id);
459     *cursorp = (krb5_kt_cursor)fileoff;
460     KTITERS(id)++;
461     if (KTITERS(id) == 0) {
462         /* Wrapped?!  */
463         KTITERS(id)--;
464         KTUNLOCK(id);
465         k5_setmsg(context, KRB5_KT_IOERR, "Too many keytab iterators active");
466         return KRB5_KT_IOERR;   /* XXX */
467     }
468     KTUNLOCK(id);
469 
470     return 0;
471 }
472 
473 /*
474  * krb5_ktfile_get_next()
475  */
476 
477 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_get_next(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry,krb5_kt_cursor * cursor)478 krb5_ktfile_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)
479 {
480     long *fileoff = (long *)*cursor;
481     krb5_keytab_entry cur_entry;
482     krb5_error_code kerror;
483 
484     KTLOCK(id);
485     if (KTFILEP(id) == NULL) {
486         KTUNLOCK(id);
487         return KRB5_KT_IOERR;
488     }
489     if (fseek(KTFILEP(id), *fileoff, 0) == -1) {
490         KTUNLOCK(id);
491         return KRB5_KT_END;
492     }
493     if ((kerror = krb5_ktfileint_read_entry(context, id, &cur_entry))) {
494         KTUNLOCK(id);
495         return kerror;
496     }
497     *fileoff = ftell(KTFILEP(id));
498     *entry = cur_entry;
499     KTUNLOCK(id);
500     return 0;
501 }
502 
503 /*
504  * krb5_ktfile_end_get()
505  */
506 
507 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_end_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursor)508 krb5_ktfile_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
509 {
510     krb5_error_code kerror;
511 
512     free(*cursor);
513     KTLOCK(id);
514     KTITERS(id)--;
515     if (KTFILEP(id) != NULL && KTITERS(id) == 0)
516         kerror = krb5_ktfileint_close(context, id);
517     else
518         kerror = 0;
519     KTUNLOCK(id);
520     return kerror;
521 }
522 
523 /*
524  * krb5_ktfile_add()
525  */
526 
527 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_add(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)528 krb5_ktfile_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
529 {
530     krb5_error_code retval;
531 
532     KTLOCK(id);
533     if (KTFILEP(id)) {
534         /* Iterator(s) active -- no changes.  */
535         KTUNLOCK(id);
536         k5_setmsg(context, KRB5_KT_IOERR,
537                   _("Cannot change keytab with keytab iterators active"));
538         return KRB5_KT_IOERR;   /* XXX */
539     }
540     if ((retval = krb5_ktfileint_openw(context, id))) {
541         KTUNLOCK(id);
542         return retval;
543     }
544     if (fseek(KTFILEP(id), 0, 2) == -1) {
545         KTUNLOCK(id);
546         return KRB5_KT_END;
547     }
548     retval = krb5_ktfileint_write_entry(context, id, entry);
549     krb5_ktfileint_close(context, id);
550     KTUNLOCK(id);
551     return retval;
552 }
553 
554 /*
555  * krb5_ktfile_remove()
556  */
557 
558 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_remove(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)559 krb5_ktfile_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
560 {
561     krb5_keytab_entry   cur_entry;
562     krb5_error_code     kerror;
563     krb5_int32          delete_point;
564 
565     KTLOCK(id);
566     if (KTFILEP(id)) {
567         /* Iterator(s) active -- no changes.  */
568         KTUNLOCK(id);
569         k5_setmsg(context, KRB5_KT_IOERR,
570                   _("Cannot change keytab with keytab iterators active"));
571         return KRB5_KT_IOERR;   /* XXX */
572     }
573 
574     if ((kerror = krb5_ktfileint_openw(context, id))) {
575         KTUNLOCK(id);
576         return kerror;
577     }
578 
579     /*
580      * For efficiency and simplicity, we'll use a while true that
581      * is exited with a break statement.
582      */
583     while (TRUE) {
584         if ((kerror = krb5_ktfileint_internal_read_entry(context, id,
585                                                          &cur_entry,
586                                                          &delete_point)))
587             break;
588 
589         if ((entry->vno == cur_entry.vno) &&
590             (entry->key.enctype == cur_entry.key.enctype) &&
591             krb5_principal_compare(context, entry->principal, cur_entry.principal)) {
592             /* found a match */
593             krb5_kt_free_entry(context, &cur_entry);
594             break;
595         }
596         krb5_kt_free_entry(context, &cur_entry);
597     }
598 
599     if (kerror == KRB5_KT_END)
600         kerror = KRB5_KT_NOTFOUND;
601 
602     if (kerror) {
603         (void) krb5_ktfileint_close(context, id);
604         KTUNLOCK(id);
605         return kerror;
606     }
607 
608     kerror = krb5_ktfileint_delete_entry(context, id, delete_point);
609 
610     if (kerror) {
611         (void) krb5_ktfileint_close(context, id);
612     } else {
613         kerror = krb5_ktfileint_close(context, id);
614     }
615     KTUNLOCK(id);
616     return kerror;
617 }
618 
619 /*
620  * krb5_ktf_ops
621  */
622 
623 const struct _krb5_kt_ops krb5_ktf_ops = {
624     0,
625     "FILE",     /* Prefix -- this string should not appear anywhere else! */
626     krb5_ktfile_resolve,
627     krb5_ktfile_get_name,
628     krb5_ktfile_close,
629     krb5_ktfile_get_entry,
630     krb5_ktfile_start_seq_get,
631     krb5_ktfile_get_next,
632     krb5_ktfile_end_get,
633     krb5_ktfile_add,
634     krb5_ktfile_remove
635 };
636 
637 /*
638  * krb5_ktf_writable_ops -- this is the same as krb5_ktf_ops except for the
639  * prefix.  WRFILE should no longer be needed, but is effectively aliased to
640  * FILE for compatibility.
641  */
642 
643 const struct _krb5_kt_ops krb5_ktf_writable_ops = {
644     0,
645     "WRFILE",   /* Prefix -- this string should not appear anywhere else! */
646     krb5_ktfile_resolve,
647     krb5_ktfile_get_name,
648     krb5_ktfile_close,
649     krb5_ktfile_get_entry,
650     krb5_ktfile_start_seq_get,
651     krb5_ktfile_get_next,
652     krb5_ktfile_end_get,
653     krb5_ktfile_add,
654     krb5_ktfile_remove
655 };
656 
657 /*
658  * krb5_kt_dfl_ops
659  */
660 
661 const krb5_kt_ops krb5_kt_dfl_ops = {
662     0,
663     "FILE",     /* Prefix -- this string should not appear anywhere else! */
664     krb5_ktfile_resolve,
665     krb5_ktfile_get_name,
666     krb5_ktfile_close,
667     krb5_ktfile_get_entry,
668     krb5_ktfile_start_seq_get,
669     krb5_ktfile_get_next,
670     krb5_ktfile_end_get,
671     0,
672     0
673 };
674 
675 /* Formerly lib/krb5/keytab/file/ktf_util.c */
676 
677 /*
678  * This function contains utilities for the file based implementation of
679  * the keytab.  There are no public functions in this file.
680  *
681  * This file is the only one that has knowledge of the format of a
682  * keytab file.
683  *
684  * The format is as follows:
685  *
686  * <file format vno>
687  * <record length>
688  * principal timestamp vno key
689  * <record length>
690  * principal timestamp vno key
691  * ....
692  *
693  * A length field (sizeof(krb5_int32)) exists between entries.  When this
694  * length is positive it indicates an active entry, when negative a hole.
695  * The length indicates the size of the block in the file (this may be
696  * larger than the size of the next record, since we are using a first
697  * fit algorithm for re-using holes and the first fit may be larger than
698  * the entry we are writing).  Another (compatible) implementation could
699  * break up holes when allocating them to smaller entries to minimize
700  * wasted space.  (Such an implementation should also coalesce adjacent
701  * holes to reduce fragmentation).  This implementation does neither.
702  *
703  * There are no separators between fields of an entry.
704  * A principal is a length-encoded array of length-encoded strings.  The
705  * length is a krb5_int16 in each case.  The specific format, then, is
706  * multiple entries concatenated with no separators.  An entry has this
707  * exact format:
708  *
709  * sizeof(krb5_int16) bytes for number of components in the principal;
710  * then, each component listed in ordser.
711  * For each component, sizeof(krb5_int16) bytes for the number of bytes
712  * in the component, followed by the component.
713  * sizeof(krb5_int32) for the principal type (for KEYTAB V2 and higher)
714  * sizeof(krb5_int32) bytes for the timestamp
715  * sizeof(krb5_octet) bytes for the key version number
716  * sizeof(krb5_int16) bytes for the enctype
717  * sizeof(krb5_int16) bytes for the key length, followed by the key
718  */
719 
720 #ifndef SEEK_SET
721 #define SEEK_SET 0
722 #define SEEK_CUR 1
723 #endif
724 
725 typedef krb5_int16  krb5_kt_vno;
726 
727 #define krb5_kt_default_vno ((krb5_kt_vno)KRB5_KT_DEFAULT_VNO)
728 
729 static krb5_error_code
krb5_ktfileint_open(krb5_context context,krb5_keytab id,int mode)730 krb5_ktfileint_open(krb5_context context, krb5_keytab id, int mode)
731 {
732     krb5_error_code kerror;
733     krb5_kt_vno kt_vno;
734     int writevno = 0;
735 
736     KTCHECKLOCK(id);
737     errno = 0;
738     KTFILEP(id) = fopen(KTFILENAME(id),
739                         (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "rb+" : "rb");
740     if (!KTFILEP(id)) {
741         if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
742             /* try making it first time around */
743             k5_create_secure_file(context, KTFILENAME(id));
744             errno = 0;
745             KTFILEP(id) = fopen(KTFILENAME(id), "rb+");
746             if (!KTFILEP(id))
747                 goto report_errno;
748             writevno = 1;
749         } else {
750         report_errno:
751             switch (errno) {
752             case 0:
753                 /* XXX */
754                 return EMFILE;
755             case ENOENT:
756                 k5_setmsg(context, ENOENT,
757                           _("Key table file '%s' not found"), KTFILENAME(id));
758                 return ENOENT;
759             default:
760                 return errno;
761             }
762         }
763     }
764     set_cloexec_file(KTFILEP(id));
765     if ((kerror = krb5_lock_file(context, fileno(KTFILEP(id)), mode))) {
766         (void) fclose(KTFILEP(id));
767         KTFILEP(id) = 0;
768         return kerror;
769     }
770     /* assume ANSI or BSD-style stdio */
771     setbuf(KTFILEP(id), KTFILEBUFP(id));
772 
773     /* get the vno and verify it */
774     if (writevno) {
775         kt_vno = htons(krb5_kt_default_vno);
776         KTVERSION(id) = krb5_kt_default_vno;
777         if (!fwrite(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
778             kerror = errno;
779             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
780             (void) fclose(KTFILEP(id));
781             KTFILEP(id) = 0;
782             return kerror;
783         }
784     } else {
785         /* gotta verify it instead... */
786         if (!fread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
787             if (feof(KTFILEP(id)))
788                 kerror = KRB5_KEYTAB_BADVNO;
789             else
790                 kerror = errno;
791             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
792             (void) fclose(KTFILEP(id));
793             KTFILEP(id) = 0;
794             return kerror;
795         }
796         kt_vno = KTVERSION(id) = ntohs(kt_vno);
797         if ((kt_vno != KRB5_KT_VNO) &&
798             (kt_vno != KRB5_KT_VNO_1)) {
799             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
800             (void) fclose(KTFILEP(id));
801             KTFILEP(id) = 0;
802             return KRB5_KEYTAB_BADVNO;
803         }
804     }
805     KTSTARTOFF(id) = ftell(KTFILEP(id));
806     return 0;
807 }
808 
809 static krb5_error_code
krb5_ktfileint_openr(krb5_context context,krb5_keytab id)810 krb5_ktfileint_openr(krb5_context context, krb5_keytab id)
811 {
812     return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_SHARED);
813 }
814 
815 static krb5_error_code
krb5_ktfileint_openw(krb5_context context,krb5_keytab id)816 krb5_ktfileint_openw(krb5_context context, krb5_keytab id)
817 {
818     return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_EXCLUSIVE);
819 }
820 
821 static krb5_error_code
krb5_ktfileint_close(krb5_context context,krb5_keytab id)822 krb5_ktfileint_close(krb5_context context, krb5_keytab id)
823 {
824     krb5_error_code kerror;
825 
826     KTCHECKLOCK(id);
827     if (!KTFILEP(id))
828         return 0;
829     kerror = krb5_unlock_file(context, fileno(KTFILEP(id)));
830     (void) fclose(KTFILEP(id));
831     KTFILEP(id) = 0;
832     return kerror;
833 }
834 
835 static krb5_error_code
krb5_ktfileint_delete_entry(krb5_context context,krb5_keytab id,krb5_int32 delete_point)836 krb5_ktfileint_delete_entry(krb5_context context, krb5_keytab id, krb5_int32 delete_point)
837 {
838     krb5_int32  size;
839     krb5_int32  len;
840     char        iobuf[BUFSIZ];
841 
842     KTCHECKLOCK(id);
843     if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
844         return errno;
845     }
846     if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
847         return KRB5_KT_END;
848     }
849     if (KTVERSION(id) != KRB5_KT_VNO_1)
850         size = ntohl(size);
851 
852     if (size > 0) {
853         krb5_int32 minus_size = -size;
854         if (KTVERSION(id) != KRB5_KT_VNO_1)
855             minus_size = htonl(minus_size);
856 
857         if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
858             return errno;
859         }
860 
861         if (!fwrite(&minus_size, sizeof(minus_size), 1, KTFILEP(id))) {
862             return KRB5_KT_IOERR;
863         }
864 
865         if (size < BUFSIZ) {
866             len = size;
867         } else {
868             len = BUFSIZ;
869         }
870 
871         memset(iobuf, 0, (size_t) len);
872         while (size > 0) {
873             if (!fwrite(iobuf, 1, (size_t) len, KTFILEP(id))) {
874                 return KRB5_KT_IOERR;
875             }
876             size -= len;
877             if (size < len) {
878                 len = size;
879             }
880         }
881 
882         return k5_sync_disk_file(context, KTFILEP(id));
883     }
884 
885     return 0;
886 }
887 
888 static krb5_error_code
krb5_ktfileint_internal_read_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * ret_entry,krb5_int32 * delete_point)889 krb5_ktfileint_internal_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *ret_entry, krb5_int32 *delete_point)
890 {
891     krb5_octet vno;
892     krb5_int16 count;
893     unsigned int u_count, u_princ_size;
894     krb5_int16 enctype;
895     krb5_int16 princ_size;
896     int i;
897     krb5_int32 size;
898     krb5_int32 start_pos, pos;
899     krb5_error_code error;
900     char        *tmpdata;
901     krb5_data   *princ;
902     uint32_t    vno32;
903 
904     KTCHECKLOCK(id);
905     memset(ret_entry, 0, sizeof(krb5_keytab_entry));
906     ret_entry->magic = KV5M_KEYTAB_ENTRY;
907 
908     /* fseek to synchronise buffered I/O on the key table. */
909 
910     if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
911     {
912         return errno;
913     }
914 
915     do {
916         *delete_point = ftell(KTFILEP(id));
917         if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
918             return KRB5_KT_END;
919         }
920         if (KTVERSION(id) != KRB5_KT_VNO_1)
921             size = ntohl(size);
922 
923         if (size < 0) {
924             if (size == INT32_MIN)  /* INT32_MIN inverts to itself. */
925                 return KRB5_KT_FORMAT;
926             if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
927                 return errno;
928             }
929         }
930     } while (size < 0);
931 
932     if (size == 0) {
933         return KRB5_KT_END;
934     }
935 
936     start_pos = ftell(KTFILEP(id));
937 
938     /* deal with guts of parsing... */
939 
940     /* first, int16 with #princ components */
941     if (!fread(&count, sizeof(count), 1, KTFILEP(id)))
942         return KRB5_KT_END;
943     if (KTVERSION(id) == KRB5_KT_VNO_1) {
944         count -= 1;         /* V1 includes the realm in the count */
945     } else {
946         count = ntohs(count);
947     }
948     if (!count || (count < 0))
949         return KRB5_KT_END;
950     ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
951     if (!ret_entry->principal)
952         return ENOMEM;
953 
954     u_count = count;
955     ret_entry->principal->magic = KV5M_PRINCIPAL;
956     ret_entry->principal->length = u_count;
957     ret_entry->principal->data = (krb5_data *)
958         calloc(u_count, sizeof(krb5_data));
959     if (!ret_entry->principal->data) {
960         free(ret_entry->principal);
961         ret_entry->principal = 0;
962         return ENOMEM;
963     }
964 
965     /* Now, get the realm data */
966     if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
967         error = KRB5_KT_END;
968         goto fail;
969     }
970     if (KTVERSION(id) != KRB5_KT_VNO_1)
971         princ_size = ntohs(princ_size);
972     if (!princ_size || (princ_size < 0)) {
973         error = KRB5_KT_END;
974         goto fail;
975     }
976     u_princ_size = princ_size;
977 
978     ret_entry->principal->realm.length = u_princ_size;
979     tmpdata = malloc(u_princ_size+1);
980     if (!tmpdata) {
981         error = ENOMEM;
982         goto fail;
983     }
984     if (fread(tmpdata, 1, u_princ_size, KTFILEP(id)) != (size_t) princ_size) {
985         free(tmpdata);
986         error = KRB5_KT_END;
987         goto fail;
988     }
989     tmpdata[princ_size] = 0;    /* Some things might be expecting null */
990                                 /* termination...  ``Be conservative in */
991                                 /* what you send out'' */
992     ret_entry->principal->realm.data = tmpdata;
993 
994     for (i = 0; i < count; i++) {
995         princ = &ret_entry->principal->data[i];
996         if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
997             error = KRB5_KT_END;
998             goto fail;
999         }
1000         if (KTVERSION(id) != KRB5_KT_VNO_1)
1001             princ_size = ntohs(princ_size);
1002         if (!princ_size || (princ_size < 0)) {
1003             error = KRB5_KT_END;
1004             goto fail;
1005         }
1006 
1007         u_princ_size = princ_size;
1008         princ->length = u_princ_size;
1009         princ->data = malloc(u_princ_size+1);
1010         if (!princ->data) {
1011             error = ENOMEM;
1012             goto fail;
1013         }
1014         if (!fread(princ->data, sizeof(char), u_princ_size, KTFILEP(id))) {
1015             error = KRB5_KT_END;
1016             goto fail;
1017         }
1018         princ->data[princ_size] = 0; /* Null terminate */
1019     }
1020 
1021     /* read in the principal type, if we can get it */
1022     if (KTVERSION(id) != KRB5_KT_VNO_1) {
1023         if (!fread(&ret_entry->principal->type,
1024                    sizeof(ret_entry->principal->type), 1, KTFILEP(id))) {
1025             error = KRB5_KT_END;
1026             goto fail;
1027         }
1028         ret_entry->principal->type = ntohl(ret_entry->principal->type);
1029     }
1030 
1031     /* read in the timestamp */
1032     if (!fread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
1033         error = KRB5_KT_END;
1034         goto fail;
1035     }
1036     if (KTVERSION(id) != KRB5_KT_VNO_1)
1037         ret_entry->timestamp = ntohl(ret_entry->timestamp);
1038 
1039     /* read in the version number */
1040     if (!fread(&vno, sizeof(vno), 1, KTFILEP(id))) {
1041         error = KRB5_KT_END;
1042         goto fail;
1043     }
1044     ret_entry->vno = (krb5_kvno)vno;
1045 
1046     /* key type */
1047     if (!fread(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1048         error = KRB5_KT_END;
1049         goto fail;
1050     }
1051     if (KTVERSION(id) != KRB5_KT_VNO_1)
1052         enctype = ntohs(enctype);
1053     ret_entry->key.enctype = (krb5_enctype)enctype;
1054 
1055     /* key contents */
1056     ret_entry->key.magic = KV5M_KEYBLOCK;
1057 
1058     if (!fread(&count, sizeof(count), 1, KTFILEP(id))) {
1059         error = KRB5_KT_END;
1060         goto fail;
1061     }
1062     if (KTVERSION(id) != KRB5_KT_VNO_1)
1063         count = ntohs(count);
1064     if (!count || (count < 0)) {
1065         error = KRB5_KT_END;
1066         goto fail;
1067     }
1068 
1069     u_count = count;
1070     ret_entry->key.length = u_count;
1071 
1072     ret_entry->key.contents = (krb5_octet *)malloc(u_count);
1073     if (!ret_entry->key.contents) {
1074         error = ENOMEM;
1075         goto fail;
1076     }
1077     if (!fread(ret_entry->key.contents, sizeof(krb5_octet), count,
1078                KTFILEP(id))) {
1079         error = KRB5_KT_END;
1080         goto fail;
1081     }
1082 
1083     /* Check for a 32-bit kvno extension if four or more bytes remain. */
1084     pos = ftell(KTFILEP(id));
1085     if (pos - start_pos + 4 <= size) {
1086         if (!fread(&vno32, sizeof(vno32), 1, KTFILEP(id))) {
1087             error = KRB5_KT_END;
1088             goto fail;
1089         }
1090         if (KTVERSION(id) != KRB5_KT_VNO_1)
1091             vno32 = ntohl(vno32);
1092         /* If the value is 0, the bytes are just zero-fill. */
1093         if (vno32)
1094             ret_entry->vno = vno32;
1095     }
1096 
1097     /*
1098      * Reposition file pointer to the next inter-record length field.
1099      */
1100     if (fseek(KTFILEP(id), start_pos + size, SEEK_SET) == -1) {
1101         error = errno;
1102         goto fail;
1103     }
1104 
1105     return 0;
1106 fail:
1107 
1108     for (i = 0; i < ret_entry->principal->length; i++)
1109         free(ret_entry->principal->data[i].data);
1110     free(ret_entry->principal->data);
1111     ret_entry->principal->data = 0;
1112     free(ret_entry->principal);
1113     ret_entry->principal = 0;
1114     return error;
1115 }
1116 
1117 static krb5_error_code
krb5_ktfileint_read_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entryp)1118 krb5_ktfileint_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entryp)
1119 {
1120     krb5_int32 delete_point;
1121 
1122     return krb5_ktfileint_internal_read_entry(context, id, entryp, &delete_point);
1123 }
1124 
1125 static krb5_error_code
krb5_ktfileint_write_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)1126 krb5_ktfileint_write_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
1127 {
1128     krb5_octet vno;
1129     krb5_data *princ;
1130     krb5_int16 count, size, enctype;
1131     krb5_error_code retval = 0;
1132     krb5_timestamp timestamp;
1133     krb5_int32  princ_type;
1134     krb5_int32  size_needed;
1135     krb5_int32  commit_point = -1;
1136     uint32_t    vno32;
1137     int         i;
1138 
1139     KTCHECKLOCK(id);
1140     retval = krb5_ktfileint_size_entry(context, entry, &size_needed);
1141     if (retval)
1142         return retval;
1143     retval = krb5_ktfileint_find_slot(context, id, &size_needed, &commit_point);
1144     if (retval)
1145         return retval;
1146 
1147     /* fseek to synchronise buffered I/O on the key table. */
1148     /* XXX Without the weird setbuf crock, can we get rid of this now?  */
1149     if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1150     {
1151         return errno;
1152     }
1153 
1154     if (KTVERSION(id) == KRB5_KT_VNO_1) {
1155         count = (krb5_int16)entry->principal->length + 1;
1156     } else {
1157         count = htons((u_short)entry->principal->length);
1158     }
1159 
1160     if (!fwrite(&count, sizeof(count), 1, KTFILEP(id))) {
1161     abend:
1162         return KRB5_KT_IOERR;
1163     }
1164     size = entry->principal->realm.length;
1165     if (KTVERSION(id) != KRB5_KT_VNO_1)
1166         size = htons(size);
1167     if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1168         goto abend;
1169     }
1170     if (!fwrite(entry->principal->realm.data, sizeof(char),
1171                 entry->principal->realm.length, KTFILEP(id))) {
1172         goto abend;
1173     }
1174 
1175     count = (krb5_int16)entry->principal->length;
1176     for (i = 0; i < count; i++) {
1177         princ = &entry->principal->data[i];
1178         size = princ->length;
1179         if (KTVERSION(id) != KRB5_KT_VNO_1)
1180             size = htons(size);
1181         if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1182             goto abend;
1183         }
1184         if (!fwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
1185             goto abend;
1186         }
1187     }
1188 
1189     /*
1190      * Write out the principal type
1191      */
1192     if (KTVERSION(id) != KRB5_KT_VNO_1) {
1193         princ_type = htonl(entry->principal->type);
1194         if (!fwrite(&princ_type, sizeof(princ_type), 1, KTFILEP(id))) {
1195             goto abend;
1196         }
1197     }
1198 
1199     /*
1200      * Fill in the time of day the entry was written to the keytab.
1201      */
1202     if (krb5_timeofday(context, &entry->timestamp)) {
1203         entry->timestamp = 0;
1204     }
1205     if (KTVERSION(id) == KRB5_KT_VNO_1)
1206         timestamp = entry->timestamp;
1207     else
1208         timestamp = htonl(entry->timestamp);
1209     if (!fwrite(&timestamp, sizeof(timestamp), 1, KTFILEP(id))) {
1210         goto abend;
1211     }
1212 
1213     /* key version number */
1214     vno = (krb5_octet)entry->vno;
1215     if (!fwrite(&vno, sizeof(vno), 1, KTFILEP(id))) {
1216         goto abend;
1217     }
1218     /* key type */
1219     if (KTVERSION(id) == KRB5_KT_VNO_1)
1220         enctype = entry->key.enctype;
1221     else
1222         enctype = htons(entry->key.enctype);
1223     if (!fwrite(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1224         goto abend;
1225     }
1226     /* key length */
1227     if (KTVERSION(id) == KRB5_KT_VNO_1)
1228         size = entry->key.length;
1229     else
1230         size = htons(entry->key.length);
1231     if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1232         goto abend;
1233     }
1234     if (!fwrite(entry->key.contents, sizeof(krb5_octet),
1235                 entry->key.length, KTFILEP(id))) {
1236         goto abend;
1237     }
1238 
1239     /* 32-bit key version number */
1240     vno32 = entry->vno;
1241     if (KTVERSION(id) != KRB5_KT_VNO_1)
1242         vno32 = htonl(vno32);
1243     if (!fwrite(&vno32, sizeof(vno32), 1, KTFILEP(id)))
1244         goto abend;
1245 
1246     if (fflush(KTFILEP(id)))
1247         goto abend;
1248 
1249     retval = k5_sync_disk_file(context, KTFILEP(id));
1250 
1251     if (retval) {
1252         return retval;
1253     }
1254 
1255     if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
1256         return errno;
1257     }
1258     if (KTVERSION(id) != KRB5_KT_VNO_1)
1259         size_needed = htonl(size_needed);
1260     if (!fwrite(&size_needed, sizeof(size_needed), 1, KTFILEP(id))) {
1261         goto abend;
1262     }
1263     if (fflush(KTFILEP(id)))
1264         goto abend;
1265     retval = k5_sync_disk_file(context, KTFILEP(id));
1266 
1267     return retval;
1268 }
1269 
1270 /*
1271  * Determine the size needed for a file entry for the given
1272  * keytab entry.
1273  */
1274 static krb5_error_code
krb5_ktfileint_size_entry(krb5_context context,krb5_keytab_entry * entry,krb5_int32 * size_needed)1275 krb5_ktfileint_size_entry(krb5_context context, krb5_keytab_entry *entry, krb5_int32 *size_needed)
1276 {
1277     krb5_int16 count;
1278     krb5_int32 total_size, i;
1279     krb5_error_code retval = 0;
1280 
1281     count = (krb5_int16)entry->principal->length;
1282 
1283     total_size = sizeof(count);
1284     total_size += entry->principal->realm.length + sizeof(krb5_int16);
1285 
1286     for (i = 0; i < count; i++)
1287         total_size += entry->principal->data[i].length + sizeof(krb5_int16);
1288 
1289     total_size += sizeof(entry->principal->type);
1290     total_size += sizeof(entry->timestamp);
1291     total_size += sizeof(krb5_octet);
1292     total_size += sizeof(krb5_int16);
1293     total_size += sizeof(krb5_int16) + entry->key.length;
1294     total_size += sizeof(uint32_t);
1295 
1296     *size_needed = total_size;
1297     return retval;
1298 }
1299 
1300 /*
1301  * Find and reserve a slot in the file for an entry of the needed size.
1302  * The commit point will be set to the position in the file where the
1303  * the length (sizeof(krb5_int32) bytes) of this node should be written
1304  * when committing the write.  The file position left as a result of this
1305  * call is the position where the actual data should be written.
1306  *
1307  * The size_needed argument may be adjusted if we find a hole that is
1308  * larger than the size needed.  (Recall that size_needed will be used
1309  * to commit the write, but that this field must indicate the size of the
1310  * block in the file rather than the size of the actual entry)
1311  */
1312 static krb5_error_code
krb5_ktfileint_find_slot(krb5_context context,krb5_keytab id,krb5_int32 * size_needed,krb5_int32 * commit_point_ptr)1313 krb5_ktfileint_find_slot(krb5_context context, krb5_keytab id, krb5_int32 *size_needed, krb5_int32 *commit_point_ptr)
1314 {
1315     FILE *fp;
1316     krb5_int32 size, zero_point, commit_point;
1317     krb5_kt_vno kt_vno;
1318 
1319     KTCHECKLOCK(id);
1320     fp = KTFILEP(id);
1321     /* Skip over file version number. */
1322     if (fseek(fp, 0, SEEK_SET))
1323         return errno;
1324     if (!fread(&kt_vno, sizeof(kt_vno), 1, fp))
1325         return errno;
1326 
1327     for (;;) {
1328         commit_point = ftell(fp);
1329         if (commit_point == -1)
1330             return errno;
1331         if (!fread(&size, sizeof(size), 1, fp)) {
1332             /* Hit the end of file, reserve this slot. */
1333             /* Necessary to avoid a later fseek failing on Solaris 10. */
1334             if (fseek(fp, 0, SEEK_CUR))
1335                 return errno;
1336             /* htonl(0) is 0, so no need to worry about byte order */
1337             size = 0;
1338             if (!fwrite(&size, sizeof(size), 1, fp))
1339                 return errno;
1340             break;
1341         }
1342 
1343         if (KTVERSION(id) != KRB5_KT_VNO_1)
1344             size = ntohl(size);
1345 
1346         if (size > 0) {
1347             /* Non-empty record; seek past it. */
1348             if (fseek(fp, size, SEEK_CUR))
1349                 return errno;
1350         } else if (size < 0) {
1351             /* Empty record; use if it's big enough, seek past otherwise. */
1352             if (size == INT32_MIN)  /* INT32_MIN inverts to itself. */
1353                 return KRB5_KT_FORMAT;
1354             size = -size;
1355             if (size >= *size_needed) {
1356                 *size_needed = size;
1357                 break;
1358             } else {
1359                 if (fseek(fp, size, SEEK_CUR))
1360                     return errno;
1361             }
1362         } else {
1363             /* Empty record at end of file; use it. */
1364             /* Ensure the new record will be followed by another 0. */
1365             zero_point = ftell(fp);
1366             if (zero_point == -1)
1367                 return errno;
1368             if (fseek(fp, *size_needed, SEEK_CUR))
1369                 return errno;
1370             /* htonl(0) is 0, so no need to worry about byte order */
1371             if (!fwrite(&size, sizeof(size), 1, fp))
1372                 return errno;
1373             if (fseek(fp, zero_point, SEEK_SET))
1374                 return errno;
1375             break;
1376         }
1377     }
1378 
1379     *commit_point_ptr = commit_point;
1380     return 0;
1381 }
1382 #endif /* LEAN_CLIENT */
1383