xref: /freebsd/crypto/krb5/src/lib/krb5/keytab/kt_file.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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     KTITERS(id)++;
460     if (KTITERS(id) == 0) {
461         /* Wrapped?!  */
462         KTITERS(id)--;
463         KTUNLOCK(id);
464         free(fileoff);
465         k5_setmsg(context, KRB5_KT_IOERR, "Too many keytab iterators active");
466         return KRB5_KT_IOERR;   /* XXX */
467     }
468     *cursorp = (krb5_kt_cursor)fileoff;
469     KTUNLOCK(id);
470 
471     return 0;
472 }
473 
474 /*
475  * krb5_ktfile_get_next()
476  */
477 
478 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_get_next(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry,krb5_kt_cursor * cursor)479 krb5_ktfile_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)
480 {
481     long *fileoff = (long *)*cursor;
482     krb5_keytab_entry cur_entry;
483     krb5_error_code kerror;
484 
485     KTLOCK(id);
486     if (KTFILEP(id) == NULL) {
487         KTUNLOCK(id);
488         return KRB5_KT_IOERR;
489     }
490     if (fseek(KTFILEP(id), *fileoff, 0) == -1) {
491         KTUNLOCK(id);
492         return KRB5_KT_END;
493     }
494     if ((kerror = krb5_ktfileint_read_entry(context, id, &cur_entry))) {
495         KTUNLOCK(id);
496         return kerror;
497     }
498     *fileoff = ftell(KTFILEP(id));
499     *entry = cur_entry;
500     KTUNLOCK(id);
501     return 0;
502 }
503 
504 /*
505  * krb5_ktfile_end_get()
506  */
507 
508 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_end_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursor)509 krb5_ktfile_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
510 {
511     krb5_error_code kerror;
512 
513     free(*cursor);
514     KTLOCK(id);
515     KTITERS(id)--;
516     if (KTFILEP(id) != NULL && KTITERS(id) == 0)
517         kerror = krb5_ktfileint_close(context, id);
518     else
519         kerror = 0;
520     KTUNLOCK(id);
521     return kerror;
522 }
523 
524 /*
525  * krb5_ktfile_add()
526  */
527 
528 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_add(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)529 krb5_ktfile_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
530 {
531     krb5_error_code retval;
532 
533     KTLOCK(id);
534     if (KTFILEP(id)) {
535         /* Iterator(s) active -- no changes.  */
536         KTUNLOCK(id);
537         k5_setmsg(context, KRB5_KT_IOERR,
538                   _("Cannot change keytab with keytab iterators active"));
539         return KRB5_KT_IOERR;   /* XXX */
540     }
541     if ((retval = krb5_ktfileint_openw(context, id))) {
542         KTUNLOCK(id);
543         return retval;
544     }
545     if (fseek(KTFILEP(id), 0, 2) == -1) {
546         KTUNLOCK(id);
547         return KRB5_KT_END;
548     }
549     retval = krb5_ktfileint_write_entry(context, id, entry);
550     krb5_ktfileint_close(context, id);
551     KTUNLOCK(id);
552     return retval;
553 }
554 
555 /*
556  * krb5_ktfile_remove()
557  */
558 
559 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_remove(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)560 krb5_ktfile_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
561 {
562     krb5_keytab_entry   cur_entry;
563     krb5_error_code     kerror;
564     krb5_int32          delete_point;
565 
566     KTLOCK(id);
567     if (KTFILEP(id)) {
568         /* Iterator(s) active -- no changes.  */
569         KTUNLOCK(id);
570         k5_setmsg(context, KRB5_KT_IOERR,
571                   _("Cannot change keytab with keytab iterators active"));
572         return KRB5_KT_IOERR;   /* XXX */
573     }
574 
575     if ((kerror = krb5_ktfileint_openw(context, id))) {
576         KTUNLOCK(id);
577         return kerror;
578     }
579 
580     /*
581      * For efficiency and simplicity, we'll use a while true that
582      * is exited with a break statement.
583      */
584     while (TRUE) {
585         if ((kerror = krb5_ktfileint_internal_read_entry(context, id,
586                                                          &cur_entry,
587                                                          &delete_point)))
588             break;
589 
590         if ((entry->vno == cur_entry.vno) &&
591             (entry->key.enctype == cur_entry.key.enctype) &&
592             krb5_principal_compare(context, entry->principal, cur_entry.principal)) {
593             /* found a match */
594             krb5_kt_free_entry(context, &cur_entry);
595             break;
596         }
597         krb5_kt_free_entry(context, &cur_entry);
598     }
599 
600     if (kerror == KRB5_KT_END)
601         kerror = KRB5_KT_NOTFOUND;
602 
603     if (kerror) {
604         (void) krb5_ktfileint_close(context, id);
605         KTUNLOCK(id);
606         return kerror;
607     }
608 
609     kerror = krb5_ktfileint_delete_entry(context, id, delete_point);
610 
611     if (kerror) {
612         (void) krb5_ktfileint_close(context, id);
613     } else {
614         kerror = krb5_ktfileint_close(context, id);
615     }
616     KTUNLOCK(id);
617     return kerror;
618 }
619 
620 /*
621  * krb5_ktf_ops
622  */
623 
624 const struct _krb5_kt_ops krb5_ktf_ops = {
625     0,
626     "FILE",     /* Prefix -- this string should not appear anywhere else! */
627     krb5_ktfile_resolve,
628     krb5_ktfile_get_name,
629     krb5_ktfile_close,
630     krb5_ktfile_get_entry,
631     krb5_ktfile_start_seq_get,
632     krb5_ktfile_get_next,
633     krb5_ktfile_end_get,
634     krb5_ktfile_add,
635     krb5_ktfile_remove
636 };
637 
638 /*
639  * krb5_ktf_writable_ops -- this is the same as krb5_ktf_ops except for the
640  * prefix.  WRFILE should no longer be needed, but is effectively aliased to
641  * FILE for compatibility.
642  */
643 
644 const struct _krb5_kt_ops krb5_ktf_writable_ops = {
645     0,
646     "WRFILE",   /* Prefix -- this string should not appear anywhere else! */
647     krb5_ktfile_resolve,
648     krb5_ktfile_get_name,
649     krb5_ktfile_close,
650     krb5_ktfile_get_entry,
651     krb5_ktfile_start_seq_get,
652     krb5_ktfile_get_next,
653     krb5_ktfile_end_get,
654     krb5_ktfile_add,
655     krb5_ktfile_remove
656 };
657 
658 /*
659  * krb5_kt_dfl_ops
660  */
661 
662 const krb5_kt_ops krb5_kt_dfl_ops = {
663     0,
664     "FILE",     /* Prefix -- this string should not appear anywhere else! */
665     krb5_ktfile_resolve,
666     krb5_ktfile_get_name,
667     krb5_ktfile_close,
668     krb5_ktfile_get_entry,
669     krb5_ktfile_start_seq_get,
670     krb5_ktfile_get_next,
671     krb5_ktfile_end_get,
672     0,
673     0
674 };
675 
676 /* Formerly lib/krb5/keytab/file/ktf_util.c */
677 
678 /*
679  * This function contains utilities for the file based implementation of
680  * the keytab.  There are no public functions in this file.
681  *
682  * This file is the only one that has knowledge of the format of a
683  * keytab file.
684  *
685  * The format is as follows:
686  *
687  * <file format vno>
688  * <record length>
689  * principal timestamp vno key
690  * <record length>
691  * principal timestamp vno key
692  * ....
693  *
694  * A length field (sizeof(krb5_int32)) exists between entries.  When this
695  * length is positive it indicates an active entry, when negative a hole.
696  * The length indicates the size of the block in the file (this may be
697  * larger than the size of the next record, since we are using a first
698  * fit algorithm for re-using holes and the first fit may be larger than
699  * the entry we are writing).  Another (compatible) implementation could
700  * break up holes when allocating them to smaller entries to minimize
701  * wasted space.  (Such an implementation should also coalesce adjacent
702  * holes to reduce fragmentation).  This implementation does neither.
703  *
704  * There are no separators between fields of an entry.
705  * A principal is a length-encoded array of length-encoded strings.  The
706  * length is a krb5_int16 in each case.  The specific format, then, is
707  * multiple entries concatenated with no separators.  An entry has this
708  * exact format:
709  *
710  * sizeof(krb5_int16) bytes for number of components in the principal;
711  * then, each component listed in ordser.
712  * For each component, sizeof(krb5_int16) bytes for the number of bytes
713  * in the component, followed by the component.
714  * sizeof(krb5_int32) for the principal type (for KEYTAB V2 and higher)
715  * sizeof(krb5_int32) bytes for the timestamp
716  * sizeof(krb5_octet) bytes for the key version number
717  * sizeof(krb5_int16) bytes for the enctype
718  * sizeof(krb5_int16) bytes for the key length, followed by the key
719  */
720 
721 #ifndef SEEK_SET
722 #define SEEK_SET 0
723 #define SEEK_CUR 1
724 #endif
725 
726 typedef krb5_int16  krb5_kt_vno;
727 
728 #define krb5_kt_default_vno ((krb5_kt_vno)KRB5_KT_DEFAULT_VNO)
729 
730 static krb5_error_code
krb5_ktfileint_open(krb5_context context,krb5_keytab id,int mode)731 krb5_ktfileint_open(krb5_context context, krb5_keytab id, int mode)
732 {
733     krb5_error_code kerror;
734     krb5_kt_vno kt_vno;
735     int writevno = 0;
736 
737     KTCHECKLOCK(id);
738     errno = 0;
739     KTFILEP(id) = fopen(KTFILENAME(id),
740                         (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "rb+" : "rb");
741     if (!KTFILEP(id)) {
742         if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
743             /* try making it first time around */
744             k5_create_secure_file(context, KTFILENAME(id));
745             errno = 0;
746             KTFILEP(id) = fopen(KTFILENAME(id), "rb+");
747             if (!KTFILEP(id))
748                 goto report_errno;
749             writevno = 1;
750         } else {
751         report_errno:
752             switch (errno) {
753             case 0:
754                 /* XXX */
755                 return EMFILE;
756             case ENOENT:
757                 k5_setmsg(context, ENOENT,
758                           _("Key table file '%s' not found"), KTFILENAME(id));
759                 return ENOENT;
760             default:
761                 return errno;
762             }
763         }
764     }
765     set_cloexec_file(KTFILEP(id));
766     if ((kerror = krb5_lock_file(context, fileno(KTFILEP(id)), mode))) {
767         (void) fclose(KTFILEP(id));
768         KTFILEP(id) = 0;
769         return kerror;
770     }
771     /* assume ANSI or BSD-style stdio */
772     setbuf(KTFILEP(id), KTFILEBUFP(id));
773 
774     /* get the vno and verify it */
775     if (writevno) {
776         kt_vno = htons(krb5_kt_default_vno);
777         KTVERSION(id) = krb5_kt_default_vno;
778         if (!fwrite(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
779             kerror = errno;
780             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
781             (void) fclose(KTFILEP(id));
782             KTFILEP(id) = 0;
783             return kerror;
784         }
785     } else {
786         /* gotta verify it instead... */
787         if (!fread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
788             if (feof(KTFILEP(id)))
789                 kerror = KRB5_KEYTAB_BADVNO;
790             else
791                 kerror = errno;
792             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
793             (void) fclose(KTFILEP(id));
794             KTFILEP(id) = 0;
795             return kerror;
796         }
797         kt_vno = KTVERSION(id) = ntohs(kt_vno);
798         if ((kt_vno != KRB5_KT_VNO) &&
799             (kt_vno != KRB5_KT_VNO_1)) {
800             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
801             (void) fclose(KTFILEP(id));
802             KTFILEP(id) = 0;
803             return KRB5_KEYTAB_BADVNO;
804         }
805     }
806     KTSTARTOFF(id) = ftell(KTFILEP(id));
807     return 0;
808 }
809 
810 static krb5_error_code
krb5_ktfileint_openr(krb5_context context,krb5_keytab id)811 krb5_ktfileint_openr(krb5_context context, krb5_keytab id)
812 {
813     return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_SHARED);
814 }
815 
816 static krb5_error_code
krb5_ktfileint_openw(krb5_context context,krb5_keytab id)817 krb5_ktfileint_openw(krb5_context context, krb5_keytab id)
818 {
819     return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_EXCLUSIVE);
820 }
821 
822 static krb5_error_code
krb5_ktfileint_close(krb5_context context,krb5_keytab id)823 krb5_ktfileint_close(krb5_context context, krb5_keytab id)
824 {
825     krb5_error_code kerror;
826 
827     KTCHECKLOCK(id);
828     if (!KTFILEP(id))
829         return 0;
830     kerror = krb5_unlock_file(context, fileno(KTFILEP(id)));
831     (void) fclose(KTFILEP(id));
832     KTFILEP(id) = 0;
833     return kerror;
834 }
835 
836 static krb5_error_code
krb5_ktfileint_delete_entry(krb5_context context,krb5_keytab id,krb5_int32 delete_point)837 krb5_ktfileint_delete_entry(krb5_context context, krb5_keytab id, krb5_int32 delete_point)
838 {
839     krb5_int32  size;
840     krb5_int32  len;
841     char        iobuf[BUFSIZ];
842 
843     KTCHECKLOCK(id);
844     if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
845         return errno;
846     }
847     if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
848         return KRB5_KT_END;
849     }
850     if (KTVERSION(id) != KRB5_KT_VNO_1)
851         size = ntohl(size);
852 
853     if (size > 0) {
854         krb5_int32 minus_size = -size;
855         if (KTVERSION(id) != KRB5_KT_VNO_1)
856             minus_size = htonl(minus_size);
857 
858         if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
859             return errno;
860         }
861 
862         if (!fwrite(&minus_size, sizeof(minus_size), 1, KTFILEP(id))) {
863             return KRB5_KT_IOERR;
864         }
865 
866         if (size < BUFSIZ) {
867             len = size;
868         } else {
869             len = BUFSIZ;
870         }
871 
872         memset(iobuf, 0, (size_t) len);
873         while (size > 0) {
874             if (!fwrite(iobuf, 1, (size_t) len, KTFILEP(id))) {
875                 return KRB5_KT_IOERR;
876             }
877             size -= len;
878             if (size < len) {
879                 len = size;
880             }
881         }
882 
883         return k5_sync_disk_file(context, KTFILEP(id));
884     }
885 
886     return 0;
887 }
888 
889 static krb5_error_code
krb5_ktfileint_internal_read_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * ret_entry,krb5_int32 * delete_point)890 krb5_ktfileint_internal_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *ret_entry, krb5_int32 *delete_point)
891 {
892     krb5_octet vno;
893     krb5_int16 count;
894     unsigned int u_count, u_princ_size;
895     krb5_int16 enctype;
896     krb5_int16 princ_size;
897     int i;
898     krb5_int32 size;
899     krb5_int32 start_pos, pos;
900     krb5_error_code error;
901     char        *tmpdata;
902     krb5_data   *princ;
903     uint32_t    vno32;
904 
905     KTCHECKLOCK(id);
906     memset(ret_entry, 0, sizeof(krb5_keytab_entry));
907     ret_entry->magic = KV5M_KEYTAB_ENTRY;
908 
909     /* fseek to synchronise buffered I/O on the key table. */
910 
911     if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
912     {
913         return errno;
914     }
915 
916     do {
917         *delete_point = ftell(KTFILEP(id));
918         if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
919             return KRB5_KT_END;
920         }
921         if (KTVERSION(id) != KRB5_KT_VNO_1)
922             size = ntohl(size);
923 
924         if (size < 0) {
925             if (size == INT32_MIN)  /* INT32_MIN inverts to itself. */
926                 return KRB5_KT_FORMAT;
927             if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
928                 return errno;
929             }
930         }
931     } while (size < 0);
932 
933     if (size == 0) {
934         return KRB5_KT_END;
935     }
936 
937     start_pos = ftell(KTFILEP(id));
938 
939     /* deal with guts of parsing... */
940 
941     /* first, int16 with #princ components */
942     if (!fread(&count, sizeof(count), 1, KTFILEP(id)))
943         return KRB5_KT_END;
944     if (KTVERSION(id) == KRB5_KT_VNO_1) {
945         count -= 1;         /* V1 includes the realm in the count */
946     } else {
947         count = ntohs(count);
948     }
949     if (!count || (count < 0))
950         return KRB5_KT_END;
951     ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
952     if (!ret_entry->principal)
953         return ENOMEM;
954 
955     u_count = count;
956     ret_entry->principal->magic = KV5M_PRINCIPAL;
957     ret_entry->principal->length = u_count;
958     ret_entry->principal->data = (krb5_data *)
959         calloc(u_count, sizeof(krb5_data));
960     if (!ret_entry->principal->data) {
961         free(ret_entry->principal);
962         ret_entry->principal = 0;
963         return ENOMEM;
964     }
965 
966     /* Now, get the realm data */
967     if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
968         error = KRB5_KT_END;
969         goto fail;
970     }
971     if (KTVERSION(id) != KRB5_KT_VNO_1)
972         princ_size = ntohs(princ_size);
973     if (!princ_size || (princ_size < 0)) {
974         error = KRB5_KT_END;
975         goto fail;
976     }
977     u_princ_size = princ_size;
978 
979     ret_entry->principal->realm.length = u_princ_size;
980     tmpdata = malloc(u_princ_size+1);
981     if (!tmpdata) {
982         error = ENOMEM;
983         goto fail;
984     }
985     if (fread(tmpdata, 1, u_princ_size, KTFILEP(id)) != (size_t) princ_size) {
986         free(tmpdata);
987         error = KRB5_KT_END;
988         goto fail;
989     }
990     tmpdata[princ_size] = 0;    /* Some things might be expecting null */
991                                 /* termination...  ``Be conservative in */
992                                 /* what you send out'' */
993     ret_entry->principal->realm.data = tmpdata;
994 
995     for (i = 0; i < count; i++) {
996         princ = &ret_entry->principal->data[i];
997         if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
998             error = KRB5_KT_END;
999             goto fail;
1000         }
1001         if (KTVERSION(id) != KRB5_KT_VNO_1)
1002             princ_size = ntohs(princ_size);
1003         if (!princ_size || (princ_size < 0)) {
1004             error = KRB5_KT_END;
1005             goto fail;
1006         }
1007 
1008         u_princ_size = princ_size;
1009         princ->length = u_princ_size;
1010         princ->data = malloc(u_princ_size+1);
1011         if (!princ->data) {
1012             error = ENOMEM;
1013             goto fail;
1014         }
1015         if (!fread(princ->data, sizeof(char), u_princ_size, KTFILEP(id))) {
1016             error = KRB5_KT_END;
1017             goto fail;
1018         }
1019         princ->data[princ_size] = 0; /* Null terminate */
1020     }
1021 
1022     /* read in the principal type, if we can get it */
1023     if (KTVERSION(id) != KRB5_KT_VNO_1) {
1024         if (!fread(&ret_entry->principal->type,
1025                    sizeof(ret_entry->principal->type), 1, KTFILEP(id))) {
1026             error = KRB5_KT_END;
1027             goto fail;
1028         }
1029         ret_entry->principal->type = ntohl(ret_entry->principal->type);
1030     }
1031 
1032     /* read in the timestamp */
1033     if (!fread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
1034         error = KRB5_KT_END;
1035         goto fail;
1036     }
1037     if (KTVERSION(id) != KRB5_KT_VNO_1)
1038         ret_entry->timestamp = ntohl(ret_entry->timestamp);
1039 
1040     /* read in the version number */
1041     if (!fread(&vno, sizeof(vno), 1, KTFILEP(id))) {
1042         error = KRB5_KT_END;
1043         goto fail;
1044     }
1045     ret_entry->vno = (krb5_kvno)vno;
1046 
1047     /* key type */
1048     if (!fread(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1049         error = KRB5_KT_END;
1050         goto fail;
1051     }
1052     if (KTVERSION(id) != KRB5_KT_VNO_1)
1053         enctype = ntohs(enctype);
1054     ret_entry->key.enctype = (krb5_enctype)enctype;
1055 
1056     /* key contents */
1057     ret_entry->key.magic = KV5M_KEYBLOCK;
1058 
1059     if (!fread(&count, sizeof(count), 1, KTFILEP(id))) {
1060         error = KRB5_KT_END;
1061         goto fail;
1062     }
1063     if (KTVERSION(id) != KRB5_KT_VNO_1)
1064         count = ntohs(count);
1065     if (!count || (count < 0)) {
1066         error = KRB5_KT_END;
1067         goto fail;
1068     }
1069 
1070     u_count = count;
1071     ret_entry->key.length = u_count;
1072 
1073     ret_entry->key.contents = (krb5_octet *)malloc(u_count);
1074     if (!ret_entry->key.contents) {
1075         error = ENOMEM;
1076         goto fail;
1077     }
1078     if (!fread(ret_entry->key.contents, sizeof(krb5_octet), count,
1079                KTFILEP(id))) {
1080         error = KRB5_KT_END;
1081         goto fail;
1082     }
1083 
1084     /* Check for a 32-bit kvno extension if four or more bytes remain. */
1085     pos = ftell(KTFILEP(id));
1086     if (pos - start_pos + 4 <= size) {
1087         if (!fread(&vno32, sizeof(vno32), 1, KTFILEP(id))) {
1088             error = KRB5_KT_END;
1089             goto fail;
1090         }
1091         if (KTVERSION(id) != KRB5_KT_VNO_1)
1092             vno32 = ntohl(vno32);
1093         /* If the value is 0, the bytes are just zero-fill. */
1094         if (vno32)
1095             ret_entry->vno = vno32;
1096     }
1097 
1098     /*
1099      * Reposition file pointer to the next inter-record length field.
1100      */
1101     if (fseek(KTFILEP(id), start_pos + size, SEEK_SET) == -1) {
1102         error = errno;
1103         goto fail;
1104     }
1105 
1106     return 0;
1107 fail:
1108 
1109     for (i = 0; i < ret_entry->principal->length; i++)
1110         free(ret_entry->principal->data[i].data);
1111     free(ret_entry->principal->data);
1112     ret_entry->principal->data = 0;
1113     free(ret_entry->principal);
1114     ret_entry->principal = 0;
1115     return error;
1116 }
1117 
1118 static krb5_error_code
krb5_ktfileint_read_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entryp)1119 krb5_ktfileint_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entryp)
1120 {
1121     krb5_int32 delete_point;
1122 
1123     return krb5_ktfileint_internal_read_entry(context, id, entryp, &delete_point);
1124 }
1125 
1126 static krb5_error_code
krb5_ktfileint_write_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)1127 krb5_ktfileint_write_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
1128 {
1129     krb5_octet vno;
1130     krb5_data *princ;
1131     krb5_int16 count, size, enctype;
1132     krb5_error_code retval = 0;
1133     krb5_timestamp timestamp;
1134     krb5_int32  princ_type;
1135     krb5_int32  size_needed;
1136     krb5_int32  commit_point = -1;
1137     uint32_t    vno32;
1138     int         i;
1139 
1140     KTCHECKLOCK(id);
1141     retval = krb5_ktfileint_size_entry(context, entry, &size_needed);
1142     if (retval)
1143         return retval;
1144     retval = krb5_ktfileint_find_slot(context, id, &size_needed, &commit_point);
1145     if (retval)
1146         return retval;
1147 
1148     /* fseek to synchronise buffered I/O on the key table. */
1149     /* XXX Without the weird setbuf crock, can we get rid of this now?  */
1150     if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1151     {
1152         return errno;
1153     }
1154 
1155     if (KTVERSION(id) == KRB5_KT_VNO_1) {
1156         count = (krb5_int16)entry->principal->length + 1;
1157     } else {
1158         count = htons((u_short)entry->principal->length);
1159     }
1160 
1161     if (!fwrite(&count, sizeof(count), 1, KTFILEP(id))) {
1162     abend:
1163         return KRB5_KT_IOERR;
1164     }
1165     size = entry->principal->realm.length;
1166     if (KTVERSION(id) != KRB5_KT_VNO_1)
1167         size = htons(size);
1168     if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1169         goto abend;
1170     }
1171     if (!fwrite(entry->principal->realm.data, sizeof(char),
1172                 entry->principal->realm.length, KTFILEP(id))) {
1173         goto abend;
1174     }
1175 
1176     count = (krb5_int16)entry->principal->length;
1177     for (i = 0; i < count; i++) {
1178         princ = &entry->principal->data[i];
1179         size = princ->length;
1180         if (KTVERSION(id) != KRB5_KT_VNO_1)
1181             size = htons(size);
1182         if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1183             goto abend;
1184         }
1185         if (!fwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
1186             goto abend;
1187         }
1188     }
1189 
1190     /*
1191      * Write out the principal type
1192      */
1193     if (KTVERSION(id) != KRB5_KT_VNO_1) {
1194         princ_type = htonl(entry->principal->type);
1195         if (!fwrite(&princ_type, sizeof(princ_type), 1, KTFILEP(id))) {
1196             goto abend;
1197         }
1198     }
1199 
1200     /*
1201      * Fill in the time of day the entry was written to the keytab.
1202      */
1203     if (krb5_timeofday(context, &entry->timestamp)) {
1204         entry->timestamp = 0;
1205     }
1206     if (KTVERSION(id) == KRB5_KT_VNO_1)
1207         timestamp = entry->timestamp;
1208     else
1209         timestamp = htonl(entry->timestamp);
1210     if (!fwrite(&timestamp, sizeof(timestamp), 1, KTFILEP(id))) {
1211         goto abend;
1212     }
1213 
1214     /* key version number */
1215     vno = (krb5_octet)entry->vno;
1216     if (!fwrite(&vno, sizeof(vno), 1, KTFILEP(id))) {
1217         goto abend;
1218     }
1219     /* key type */
1220     if (KTVERSION(id) == KRB5_KT_VNO_1)
1221         enctype = entry->key.enctype;
1222     else
1223         enctype = htons(entry->key.enctype);
1224     if (!fwrite(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1225         goto abend;
1226     }
1227     /* key length */
1228     if (KTVERSION(id) == KRB5_KT_VNO_1)
1229         size = entry->key.length;
1230     else
1231         size = htons(entry->key.length);
1232     if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1233         goto abend;
1234     }
1235     if (!fwrite(entry->key.contents, sizeof(krb5_octet),
1236                 entry->key.length, KTFILEP(id))) {
1237         goto abend;
1238     }
1239 
1240     /* 32-bit key version number */
1241     vno32 = entry->vno;
1242     if (KTVERSION(id) != KRB5_KT_VNO_1)
1243         vno32 = htonl(vno32);
1244     if (!fwrite(&vno32, sizeof(vno32), 1, KTFILEP(id)))
1245         goto abend;
1246 
1247     if (fflush(KTFILEP(id)))
1248         goto abend;
1249 
1250     retval = k5_sync_disk_file(context, KTFILEP(id));
1251 
1252     if (retval) {
1253         return retval;
1254     }
1255 
1256     if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
1257         return errno;
1258     }
1259     if (KTVERSION(id) != KRB5_KT_VNO_1)
1260         size_needed = htonl(size_needed);
1261     if (!fwrite(&size_needed, sizeof(size_needed), 1, KTFILEP(id))) {
1262         goto abend;
1263     }
1264     if (fflush(KTFILEP(id)))
1265         goto abend;
1266     retval = k5_sync_disk_file(context, KTFILEP(id));
1267 
1268     return retval;
1269 }
1270 
1271 /*
1272  * Determine the size needed for a file entry for the given
1273  * keytab entry.
1274  */
1275 static krb5_error_code
krb5_ktfileint_size_entry(krb5_context context,krb5_keytab_entry * entry,krb5_int32 * size_needed)1276 krb5_ktfileint_size_entry(krb5_context context, krb5_keytab_entry *entry, krb5_int32 *size_needed)
1277 {
1278     krb5_int16 count;
1279     krb5_int32 total_size, i;
1280     krb5_error_code retval = 0;
1281 
1282     count = (krb5_int16)entry->principal->length;
1283 
1284     total_size = sizeof(count);
1285     total_size += entry->principal->realm.length + sizeof(krb5_int16);
1286 
1287     for (i = 0; i < count; i++)
1288         total_size += entry->principal->data[i].length + sizeof(krb5_int16);
1289 
1290     total_size += sizeof(entry->principal->type);
1291     total_size += sizeof(entry->timestamp);
1292     total_size += sizeof(krb5_octet);
1293     total_size += sizeof(krb5_int16);
1294     total_size += sizeof(krb5_int16) + entry->key.length;
1295     total_size += sizeof(uint32_t);
1296 
1297     *size_needed = total_size;
1298     return retval;
1299 }
1300 
1301 /*
1302  * Find and reserve a slot in the file for an entry of the needed size.
1303  * The commit point will be set to the position in the file where the
1304  * the length (sizeof(krb5_int32) bytes) of this node should be written
1305  * when committing the write.  The file position left as a result of this
1306  * call is the position where the actual data should be written.
1307  *
1308  * The size_needed argument may be adjusted if we find a hole that is
1309  * larger than the size needed.  (Recall that size_needed will be used
1310  * to commit the write, but that this field must indicate the size of the
1311  * block in the file rather than the size of the actual entry)
1312  */
1313 static krb5_error_code
krb5_ktfileint_find_slot(krb5_context context,krb5_keytab id,krb5_int32 * size_needed,krb5_int32 * commit_point_ptr)1314 krb5_ktfileint_find_slot(krb5_context context, krb5_keytab id, krb5_int32 *size_needed, krb5_int32 *commit_point_ptr)
1315 {
1316     FILE *fp;
1317     krb5_int32 size, zero_point, commit_point;
1318     krb5_kt_vno kt_vno;
1319 
1320     KTCHECKLOCK(id);
1321     fp = KTFILEP(id);
1322     /* Skip over file version number. */
1323     if (fseek(fp, 0, SEEK_SET))
1324         return errno;
1325     if (!fread(&kt_vno, sizeof(kt_vno), 1, fp))
1326         return errno;
1327 
1328     for (;;) {
1329         commit_point = ftell(fp);
1330         if (commit_point == -1)
1331             return errno;
1332         if (!fread(&size, sizeof(size), 1, fp)) {
1333             /* Hit the end of file, reserve this slot. */
1334             /* Necessary to avoid a later fseek failing on Solaris 10. */
1335             if (fseek(fp, 0, SEEK_CUR))
1336                 return errno;
1337             /* htonl(0) is 0, so no need to worry about byte order */
1338             size = 0;
1339             if (!fwrite(&size, sizeof(size), 1, fp))
1340                 return errno;
1341             break;
1342         }
1343 
1344         if (KTVERSION(id) != KRB5_KT_VNO_1)
1345             size = ntohl(size);
1346 
1347         if (size > 0) {
1348             /* Non-empty record; seek past it. */
1349             if (fseek(fp, size, SEEK_CUR))
1350                 return errno;
1351         } else if (size < 0) {
1352             /* Empty record; use if it's big enough, seek past otherwise. */
1353             if (size == INT32_MIN)  /* INT32_MIN inverts to itself. */
1354                 return KRB5_KT_FORMAT;
1355             size = -size;
1356             if (size >= *size_needed) {
1357                 *size_needed = size;
1358                 break;
1359             } else {
1360                 if (fseek(fp, size, SEEK_CUR))
1361                     return errno;
1362             }
1363         } else {
1364             /* Empty record at end of file; use it. */
1365             /* Ensure the new record will be followed by another 0. */
1366             zero_point = ftell(fp);
1367             if (zero_point == -1)
1368                 return errno;
1369             if (fseek(fp, *size_needed, SEEK_CUR))
1370                 return errno;
1371             /* htonl(0) is 0, so no need to worry about byte order */
1372             if (!fwrite(&size, sizeof(size), 1, fp))
1373                 return errno;
1374             if (fseek(fp, zero_point, SEEK_SET))
1375                 return errno;
1376             break;
1377         }
1378     }
1379 
1380     *commit_point_ptr = commit_point;
1381     return 0;
1382 }
1383 #endif /* LEAN_CLIENT */
1384