Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / lib / krb5 / keytab / kt_file.c
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
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
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
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 (k1->timestamp >= k2->timestamp && k1->vno < 128 && k2->vno > 240)
268         return TRUE;
269     if (k1->timestamp <= k2->timestamp && k1->vno > 240 && k2->vno < 128)
270         return FALSE;
271
272     /* Otherwise do a simple version comparison. */
273     return k1->vno > k2->vno;
274 }
275
276 /*
277  * This is the get_entry routine for the file based keytab implementation.
278  * It opens the keytab file, and either retrieves the entry or returns
279  * an error.
280  */
281
282 static krb5_error_code KRB5_CALLCONV
283 krb5_ktfile_get_entry(krb5_context context, krb5_keytab id,
284                       krb5_const_principal principal, krb5_kvno kvno,
285                       krb5_enctype enctype, krb5_keytab_entry *entry)
286 {
287     krb5_keytab_entry cur_entry, new_entry;
288     krb5_error_code kerror = 0;
289     int found_wrong_kvno = 0;
290     krb5_boolean similar;
291     int was_open;
292     char *princname;
293
294     KTLOCK(id);
295
296     if (KTFILEP(id) != NULL) {
297         was_open = 1;
298
299         if (fseek(KTFILEP(id), KTSTARTOFF(id), SEEK_SET) == -1) {
300             KTUNLOCK(id);
301             return errno;
302         }
303     } else {
304         was_open = 0;
305
306         /* Open the keyfile for reading */
307         if ((kerror = krb5_ktfileint_openr(context, id))) {
308             KTUNLOCK(id);
309             return(kerror);
310         }
311     }
312
313     /*
314      * For efficiency and simplicity, we'll use a while true that
315      * is exited with a break statement.
316      */
317     cur_entry.principal = 0;
318     cur_entry.vno = 0;
319     cur_entry.key.contents = 0;
320
321     while (TRUE) {
322         if ((kerror = krb5_ktfileint_read_entry(context, id, &new_entry)))
323             break;
324
325         /* by the time this loop exits, it must either free cur_entry,
326            and copy new_entry there, or free new_entry.  Otherwise, it
327            leaks. */
328
329         /* if the principal isn't the one requested, free new_entry
330            and continue to the next. */
331
332         if (!krb5_principal_compare(context, principal, new_entry.principal)) {
333             krb5_kt_free_entry(context, &new_entry);
334             continue;
335         }
336
337         /* if the enctype is not ignored and doesn't match, free new_entry
338            and continue to the next */
339
340         if (enctype != IGNORE_ENCTYPE) {
341             if ((kerror = krb5_c_enctype_compare(context, enctype,
342                                                  new_entry.key.enctype,
343                                                  &similar))) {
344                 krb5_kt_free_entry(context, &new_entry);
345                 break;
346             }
347
348             if (!similar) {
349                 krb5_kt_free_entry(context, &new_entry);
350                 continue;
351             }
352             /*
353              * Coerce the enctype of the output keyblock in case we
354              * got an inexact match on the enctype.
355              */
356             new_entry.key.enctype = enctype;
357
358         }
359
360         if (kvno == IGNORE_VNO) {
361             /* If this entry is more recent (or the first match), free the
362              * current and keep the new.  Otherwise, free the new. */
363             if (cur_entry.principal == NULL ||
364                 more_recent(&new_entry, &cur_entry)) {
365                 krb5_kt_free_entry(context, &cur_entry);
366                 cur_entry = new_entry;
367             } else {
368                 krb5_kt_free_entry(context, &new_entry);
369             }
370         } else {
371             /*
372              * If this kvno matches exactly, free the current, keep the new,
373              * and break out.  If it matches the low 8 bits of the desired
374              * kvno, remember the first match (because the recorded kvno may
375              * have been truncated due to pre-1.14 keytab format or kadmin
376              * protocol limitations) but keep looking for an exact match.
377              * Otherwise, remember that we were here so we can return the right
378              * error, and free the new.
379              */
380             if (new_entry.vno == kvno) {
381                 krb5_kt_free_entry(context, &cur_entry);
382                 cur_entry = new_entry;
383                 if (new_entry.vno == kvno)
384                     break;
385             } else if (new_entry.vno == (kvno & 0xff) &&
386                        cur_entry.principal == NULL) {
387                 cur_entry = new_entry;
388             } else {
389                 found_wrong_kvno++;
390                 krb5_kt_free_entry(context, &new_entry);
391             }
392         }
393     }
394
395     if (kerror == KRB5_KT_END) {
396         if (cur_entry.principal)
397             kerror = 0;
398         else if (found_wrong_kvno)
399             kerror = KRB5_KT_KVNONOTFOUND;
400         else {
401             kerror = KRB5_KT_NOTFOUND;
402             if (krb5_unparse_name(context, principal, &princname) == 0) {
403                 k5_setmsg(context, kerror,
404                           _("No key table entry found for %s"), princname);
405                 free(princname);
406             }
407         }
408     }
409     if (kerror) {
410         if (was_open == 0)
411             (void) krb5_ktfileint_close(context, id);
412         KTUNLOCK(id);
413         krb5_kt_free_entry(context, &cur_entry);
414         return kerror;
415     }
416     if (was_open == 0 && (kerror = krb5_ktfileint_close(context, id)) != 0) {
417         KTUNLOCK(id);
418         krb5_kt_free_entry(context, &cur_entry);
419         return kerror;
420     }
421     KTUNLOCK(id);
422     *entry = cur_entry;
423     return 0;
424 }
425
426 /*
427  * Get the name of the file containing a file-based keytab.
428  */
429
430 static krb5_error_code KRB5_CALLCONV
431 krb5_ktfile_get_name(krb5_context context, krb5_keytab id, char *name, unsigned int len)
432 /*
433  * This routine returns the name of the name of the file associated with
434  * this file-based keytab.  name is zeroed and the filename is truncated
435  * to fit in name if necessary.  The name is prefixed with PREFIX:, so that
436  * trt will happen if the name is passed back to resolve.
437  */
438 {
439     int result;
440
441     memset(name, 0, len);
442     result = snprintf(name, len, "%s:%s", id->ops->prefix, KTFILENAME(id));
443     if (SNPRINTF_OVERFLOW(result, len))
444         return(KRB5_KT_NAME_TOOLONG);
445     return(0);
446 }
447
448 /*
449  * krb5_ktfile_start_seq_get()
450  */
451
452 static krb5_error_code KRB5_CALLCONV
453 krb5_ktfile_start_seq_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursorp)
454 {
455     krb5_error_code retval;
456     long *fileoff;
457
458     KTLOCK(id);
459
460     if (KTITERS(id) == 0) {
461         if ((retval = krb5_ktfileint_openr(context, id))) {
462             KTUNLOCK(id);
463             return retval;
464         }
465     }
466
467     if (!(fileoff = (long *)malloc(sizeof(*fileoff)))) {
468         if (KTITERS(id) == 0)
469             krb5_ktfileint_close(context, id);
470         KTUNLOCK(id);
471         return ENOMEM;
472     }
473     *fileoff = KTSTARTOFF(id);
474     *cursorp = (krb5_kt_cursor)fileoff;
475     KTITERS(id)++;
476     if (KTITERS(id) == 0) {
477         /* Wrapped?!  */
478         KTITERS(id)--;
479         KTUNLOCK(id);
480         k5_setmsg(context, KRB5_KT_IOERR, "Too many keytab iterators active");
481         return KRB5_KT_IOERR;   /* XXX */
482     }
483     KTUNLOCK(id);
484
485     return 0;
486 }
487
488 /*
489  * krb5_ktfile_get_next()
490  */
491
492 static krb5_error_code KRB5_CALLCONV
493 krb5_ktfile_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)
494 {
495     long *fileoff = (long *)*cursor;
496     krb5_keytab_entry cur_entry;
497     krb5_error_code kerror;
498
499     KTLOCK(id);
500     if (KTFILEP(id) == NULL) {
501         KTUNLOCK(id);
502         return KRB5_KT_IOERR;
503     }
504     if (fseek(KTFILEP(id), *fileoff, 0) == -1) {
505         KTUNLOCK(id);
506         return KRB5_KT_END;
507     }
508     if ((kerror = krb5_ktfileint_read_entry(context, id, &cur_entry))) {
509         KTUNLOCK(id);
510         return kerror;
511     }
512     *fileoff = ftell(KTFILEP(id));
513     *entry = cur_entry;
514     KTUNLOCK(id);
515     return 0;
516 }
517
518 /*
519  * krb5_ktfile_end_get()
520  */
521
522 static krb5_error_code KRB5_CALLCONV
523 krb5_ktfile_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
524 {
525     krb5_error_code kerror;
526
527     free(*cursor);
528     KTLOCK(id);
529     KTITERS(id)--;
530     if (KTFILEP(id) != NULL && KTITERS(id) == 0)
531         kerror = krb5_ktfileint_close(context, id);
532     else
533         kerror = 0;
534     KTUNLOCK(id);
535     return kerror;
536 }
537
538 /*
539  * ser_ktf.c - Serialize keytab file context for subsequent reopen.
540  */
541
542 static const char ktfile_def_name[] = ".";
543
544 /*
545  * Routines to deal with externalizing krb5_keytab for [WR]FILE: variants.
546  *      krb5_ktf_keytab_size();
547  *      krb5_ktf_keytab_externalize();
548  *      krb5_ktf_keytab_internalize();
549  */
550 static krb5_error_code
551 krb5_ktf_keytab_size(krb5_context, krb5_pointer, size_t *);
552
553 static krb5_error_code
554 krb5_ktf_keytab_externalize(krb5_context, krb5_pointer, krb5_octet **,
555                             size_t *);
556
557 static krb5_error_code
558 krb5_ktf_keytab_internalize(krb5_context,krb5_pointer *, krb5_octet **,
559                             size_t *);
560
561 /*
562  * Serialization entry for this type.
563  */
564 const krb5_ser_entry krb5_ktfile_ser_entry = {
565     KV5M_KEYTAB,                        /* Type                 */
566     krb5_ktf_keytab_size,               /* Sizer routine        */
567     krb5_ktf_keytab_externalize,        /* Externalize routine  */
568     krb5_ktf_keytab_internalize         /* Internalize routine  */
569 };
570
571 /*
572  * krb5_ktf_keytab_size()       - Determine the size required to externalize
573  *                                this krb5_keytab variant.
574  */
575 static krb5_error_code
576 krb5_ktf_keytab_size(krb5_context kcontext, krb5_pointer arg, size_t *sizep)
577 {
578     krb5_error_code     kret;
579     krb5_keytab         keytab;
580     size_t              required;
581     krb5_ktfile_data    *ktdata;
582
583     kret = EINVAL;
584     if ((keytab = (krb5_keytab) arg)) {
585         /*
586          * Saving FILE: variants of krb5_keytab requires at minimum:
587          *      krb5_int32      for KV5M_KEYTAB
588          *      krb5_int32      for length of keytab name.
589          *      krb5_int32      for file status.
590          *      krb5_int32      for file position.
591          *      krb5_int32      for file position.
592          *      krb5_int32      for version.
593          *      krb5_int32      for KV5M_KEYTAB
594          */
595         required = sizeof(krb5_int32) * 7;
596         if (keytab->ops && keytab->ops->prefix)
597             required += (strlen(keytab->ops->prefix)+1);
598
599         /*
600          * The keytab name is formed as follows:
601          *      <prefix>:<name>
602          * If there's no name, we use a default name so that we have something
603          * to call krb5_keytab_resolve with.
604          */
605         ktdata = (krb5_ktfile_data *) keytab->data;
606         required += strlen((ktdata && ktdata->name) ?
607                            ktdata->name : ktfile_def_name);
608         kret = 0;
609
610         if (!kret)
611             *sizep += required;
612     }
613     return(kret);
614 }
615
616 /*
617  * krb5_ktf_keytab_externalize()        - Externalize the krb5_keytab.
618  */
619 static krb5_error_code
620 krb5_ktf_keytab_externalize(krb5_context kcontext, krb5_pointer arg, krb5_octet **buffer, size_t *lenremain)
621 {
622     krb5_error_code     kret;
623     krb5_keytab         keytab;
624     size_t              required;
625     krb5_octet          *bp;
626     size_t              remain;
627     krb5_ktfile_data    *ktdata;
628     krb5_int32          file_is_open;
629     int64_t             file_pos;
630     char                *ktname;
631     const char          *fnamep;
632
633     required = 0;
634     bp = *buffer;
635     remain = *lenremain;
636     kret = EINVAL;
637     if ((keytab = (krb5_keytab) arg)) {
638         kret = ENOMEM;
639         if (!krb5_ktf_keytab_size(kcontext, arg, &required) &&
640             (required <= remain)) {
641             /* Our identifier */
642             (void) krb5_ser_pack_int32(KV5M_KEYTAB, &bp, &remain);
643
644             ktdata = (krb5_ktfile_data *) keytab->data;
645             file_is_open = 0;
646             file_pos = 0;
647
648             /* Calculate the length of the name */
649             if (ktdata && ktdata->name)
650                 fnamep = ktdata->name;
651             else
652                 fnamep = ktfile_def_name;
653
654             if (keytab->ops && keytab->ops->prefix) {
655                 if (asprintf(&ktname, "%s:%s", keytab->ops->prefix, fnamep) < 0)
656                     ktname = NULL;
657             } else
658                 ktname = strdup(fnamep);
659
660             if (ktname) {
661                 /* Fill in the file-specific keytab information. */
662                 if (ktdata) {
663                     if (ktdata->openf) {
664                         long    fpos;
665                         int     fflags = 0;
666
667                         file_is_open = 1;
668 #if !defined(_WIN32)
669                         fflags = fcntl(fileno(ktdata->openf), F_GETFL, 0);
670                         if (fflags > 0)
671                             file_is_open |= ((fflags & O_ACCMODE) << 1);
672 #else
673                         file_is_open = 0;
674 #endif
675                         fpos = ftell(ktdata->openf);
676                         file_pos = fpos; /* XX range check? */
677                     }
678                 }
679
680                 /* Put the length of the file name */
681                 (void) krb5_ser_pack_int32((krb5_int32) strlen(ktname),
682                                            &bp, &remain);
683
684                 /* Put the name */
685                 (void) krb5_ser_pack_bytes((krb5_octet *) ktname,
686                                            strlen(ktname),
687                                            &bp, &remain);
688
689                 /* Put the file open flag */
690                 (void) krb5_ser_pack_int32(file_is_open, &bp, &remain);
691
692                 /* Put the file position */
693                 (void) krb5_ser_pack_int64(file_pos, &bp, &remain);
694
695                 /* Put the version */
696                 (void) krb5_ser_pack_int32((krb5_int32) ((ktdata) ?
697                                                          ktdata->version : 0),
698                                            &bp, &remain);
699
700                 /* Put the trailer */
701                 (void) krb5_ser_pack_int32(KV5M_KEYTAB, &bp, &remain);
702                 kret = 0;
703                 *buffer = bp;
704                 *lenremain = remain;
705                 free(ktname);
706             }
707         }
708     }
709     return(kret);
710 }
711
712 /*
713  * krb5_ktf_keytab_internalize()        - Internalize the krb5_ktf_keytab.
714  */
715 static krb5_error_code
716 krb5_ktf_keytab_internalize(krb5_context kcontext, krb5_pointer *argp, krb5_octet **buffer, size_t *lenremain)
717 {
718     krb5_error_code     kret;
719     krb5_keytab         keytab = NULL;
720     krb5_int32          ibuf;
721     krb5_octet          *bp;
722     size_t              remain;
723     char                *ktname = NULL;
724     krb5_ktfile_data    *ktdata;
725     krb5_int32          file_is_open;
726     int64_t             foff;
727
728     *argp = NULL;
729     bp = *buffer;
730     remain = *lenremain;
731
732     /* Read our magic number */
733     if (krb5_ser_unpack_int32(&ibuf, &bp, &remain) || ibuf != KV5M_KEYTAB)
734         return EINVAL;
735
736     /* Read the keytab name */
737     kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
738     if (kret)
739         return kret;
740     ktname = malloc(ibuf + 1);
741     if (!ktname)
742         return ENOMEM;
743     kret = krb5_ser_unpack_bytes((krb5_octet *) ktname, (size_t) ibuf,
744                                  &bp, &remain);
745     if (kret)
746         goto cleanup;
747     ktname[ibuf] = '\0';
748
749     /* Resolve the keytab. */
750     kret = krb5_kt_resolve(kcontext, ktname, &keytab);
751     if (kret)
752         goto cleanup;
753
754     if (keytab->ops != &krb5_ktf_ops) {
755         kret = EINVAL;
756         goto cleanup;
757     }
758     ktdata = (krb5_ktfile_data *) keytab->data;
759
760     if (remain < (sizeof(krb5_int32)*5)) {
761         kret = EINVAL;
762         goto cleanup;
763     }
764     (void) krb5_ser_unpack_int32(&file_is_open, &bp, &remain);
765     (void) krb5_ser_unpack_int64(&foff, &bp, &remain);
766     (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
767     ktdata->version = (int) ibuf;
768     (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
769     if (ibuf != KV5M_KEYTAB) {
770         kret = EINVAL;
771         goto cleanup;
772     }
773
774     if (file_is_open) {
775         int     fmode;
776         long    fpos;
777
778 #if !defined(_WIN32)
779         fmode = (file_is_open >> 1) & O_ACCMODE;
780 #else
781         fmode = 0;
782 #endif
783         if (fmode)
784             kret = krb5_ktfileint_openw(kcontext, keytab);
785         else
786             kret = krb5_ktfileint_openr(kcontext, keytab);
787         if (kret)
788             goto cleanup;
789         fpos = foff; /* XX range check? */
790         if (fseek(KTFILEP(keytab), fpos, SEEK_SET) == -1) {
791             kret = errno;
792             goto cleanup;
793         }
794     }
795
796     *buffer = bp;
797     *lenremain = remain;
798     *argp = (krb5_pointer) keytab;
799 cleanup:
800     if (kret != 0 && keytab)
801         krb5_kt_close(kcontext, keytab);
802     free(ktname);
803     return kret;
804 }
805
806
807 /*
808  * krb5_ktfile_add()
809  */
810
811 static krb5_error_code KRB5_CALLCONV
812 krb5_ktfile_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
813 {
814     krb5_error_code retval;
815
816     KTLOCK(id);
817     if (KTFILEP(id)) {
818         /* Iterator(s) active -- no changes.  */
819         KTUNLOCK(id);
820         k5_setmsg(context, KRB5_KT_IOERR,
821                   _("Cannot change keytab with keytab iterators active"));
822         return KRB5_KT_IOERR;   /* XXX */
823     }
824     if ((retval = krb5_ktfileint_openw(context, id))) {
825         KTUNLOCK(id);
826         return retval;
827     }
828     if (fseek(KTFILEP(id), 0, 2) == -1) {
829         KTUNLOCK(id);
830         return KRB5_KT_END;
831     }
832     retval = krb5_ktfileint_write_entry(context, id, entry);
833     krb5_ktfileint_close(context, id);
834     KTUNLOCK(id);
835     return retval;
836 }
837
838 /*
839  * krb5_ktfile_remove()
840  */
841
842 static krb5_error_code KRB5_CALLCONV
843 krb5_ktfile_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
844 {
845     krb5_keytab_entry   cur_entry;
846     krb5_error_code     kerror;
847     krb5_int32          delete_point;
848
849     KTLOCK(id);
850     if (KTFILEP(id)) {
851         /* Iterator(s) active -- no changes.  */
852         KTUNLOCK(id);
853         k5_setmsg(context, KRB5_KT_IOERR,
854                   _("Cannot change keytab with keytab iterators active"));
855         return KRB5_KT_IOERR;   /* XXX */
856     }
857
858     if ((kerror = krb5_ktfileint_openw(context, id))) {
859         KTUNLOCK(id);
860         return kerror;
861     }
862
863     /*
864      * For efficiency and simplicity, we'll use a while true that
865      * is exited with a break statement.
866      */
867     while (TRUE) {
868         if ((kerror = krb5_ktfileint_internal_read_entry(context, id,
869                                                          &cur_entry,
870                                                          &delete_point)))
871             break;
872
873         if ((entry->vno == cur_entry.vno) &&
874             (entry->key.enctype == cur_entry.key.enctype) &&
875             krb5_principal_compare(context, entry->principal, cur_entry.principal)) {
876             /* found a match */
877             krb5_kt_free_entry(context, &cur_entry);
878             break;
879         }
880         krb5_kt_free_entry(context, &cur_entry);
881     }
882
883     if (kerror == KRB5_KT_END)
884         kerror = KRB5_KT_NOTFOUND;
885
886     if (kerror) {
887         (void) krb5_ktfileint_close(context, id);
888         KTUNLOCK(id);
889         return kerror;
890     }
891
892     kerror = krb5_ktfileint_delete_entry(context, id, delete_point);
893
894     if (kerror) {
895         (void) krb5_ktfileint_close(context, id);
896     } else {
897         kerror = krb5_ktfileint_close(context, id);
898     }
899     KTUNLOCK(id);
900     return kerror;
901 }
902
903 /*
904  * krb5_ktf_ops
905  */
906
907 const struct _krb5_kt_ops krb5_ktf_ops = {
908     0,
909     "FILE",     /* Prefix -- this string should not appear anywhere else! */
910     krb5_ktfile_resolve,
911     krb5_ktfile_get_name,
912     krb5_ktfile_close,
913     krb5_ktfile_get_entry,
914     krb5_ktfile_start_seq_get,
915     krb5_ktfile_get_next,
916     krb5_ktfile_end_get,
917     krb5_ktfile_add,
918     krb5_ktfile_remove,
919     &krb5_ktfile_ser_entry
920 };
921
922 /*
923  * krb5_ktf_writable_ops -- this is the same as krb5_ktf_ops except for the
924  * prefix.  WRFILE should no longer be needed, but is effectively aliased to
925  * FILE for compatibility.
926  */
927
928 const struct _krb5_kt_ops krb5_ktf_writable_ops = {
929     0,
930     "WRFILE",   /* Prefix -- this string should not appear anywhere else! */
931     krb5_ktfile_resolve,
932     krb5_ktfile_get_name,
933     krb5_ktfile_close,
934     krb5_ktfile_get_entry,
935     krb5_ktfile_start_seq_get,
936     krb5_ktfile_get_next,
937     krb5_ktfile_end_get,
938     krb5_ktfile_add,
939     krb5_ktfile_remove,
940     &krb5_ktfile_ser_entry
941 };
942
943 /*
944  * krb5_kt_dfl_ops
945  */
946
947 const krb5_kt_ops krb5_kt_dfl_ops = {
948     0,
949     "FILE",     /* Prefix -- this string should not appear anywhere else! */
950     krb5_ktfile_resolve,
951     krb5_ktfile_get_name,
952     krb5_ktfile_close,
953     krb5_ktfile_get_entry,
954     krb5_ktfile_start_seq_get,
955     krb5_ktfile_get_next,
956     krb5_ktfile_end_get,
957     0,
958     0,
959     &krb5_ktfile_ser_entry
960 };
961
962 /* Formerly lib/krb5/keytab/file/ktf_util.c */
963
964 /*
965  * This function contains utilities for the file based implementation of
966  * the keytab.  There are no public functions in this file.
967  *
968  * This file is the only one that has knowledge of the format of a
969  * keytab file.
970  *
971  * The format is as follows:
972  *
973  * <file format vno>
974  * <record length>
975  * principal timestamp vno key
976  * <record length>
977  * principal timestamp vno key
978  * ....
979  *
980  * A length field (sizeof(krb5_int32)) exists between entries.  When this
981  * length is positive it indicates an active entry, when negative a hole.
982  * The length indicates the size of the block in the file (this may be
983  * larger than the size of the next record, since we are using a first
984  * fit algorithm for re-using holes and the first fit may be larger than
985  * the entry we are writing).  Another (compatible) implementation could
986  * break up holes when allocating them to smaller entries to minimize
987  * wasted space.  (Such an implementation should also coalesce adjacent
988  * holes to reduce fragmentation).  This implementation does neither.
989  *
990  * There are no separators between fields of an entry.
991  * A principal is a length-encoded array of length-encoded strings.  The
992  * length is a krb5_int16 in each case.  The specific format, then, is
993  * multiple entries concatinated with no separators.  An entry has this
994  * exact format:
995  *
996  * sizeof(krb5_int16) bytes for number of components in the principal;
997  * then, each component listed in ordser.
998  * For each component, sizeof(krb5_int16) bytes for the number of bytes
999  * in the component, followed by the component.
1000  * sizeof(krb5_int32) for the principal type (for KEYTAB V2 and higher)
1001  * sizeof(krb5_int32) bytes for the timestamp
1002  * sizeof(krb5_octet) bytes for the key version number
1003  * sizeof(krb5_int16) bytes for the enctype
1004  * sizeof(krb5_int16) bytes for the key length, followed by the key
1005  */
1006
1007 #ifndef SEEK_SET
1008 #define SEEK_SET 0
1009 #define SEEK_CUR 1
1010 #endif
1011
1012 typedef krb5_int16  krb5_kt_vno;
1013
1014 #define krb5_kt_default_vno ((krb5_kt_vno)KRB5_KT_DEFAULT_VNO)
1015
1016 static krb5_error_code
1017 krb5_ktfileint_open(krb5_context context, krb5_keytab id, int mode)
1018 {
1019     krb5_error_code kerror;
1020     krb5_kt_vno kt_vno;
1021     int writevno = 0;
1022
1023     KTCHECKLOCK(id);
1024     errno = 0;
1025     KTFILEP(id) = fopen(KTFILENAME(id),
1026                         (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "rb+" : "rb");
1027     if (!KTFILEP(id)) {
1028         if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
1029             /* try making it first time around */
1030             k5_create_secure_file(context, KTFILENAME(id));
1031             errno = 0;
1032             KTFILEP(id) = fopen(KTFILENAME(id), "rb+");
1033             if (!KTFILEP(id))
1034                 goto report_errno;
1035             writevno = 1;
1036         } else {
1037         report_errno:
1038             switch (errno) {
1039             case 0:
1040                 /* XXX */
1041                 return EMFILE;
1042             case ENOENT:
1043                 k5_setmsg(context, ENOENT,
1044                           _("Key table file '%s' not found"), KTFILENAME(id));
1045                 return ENOENT;
1046             default:
1047                 return errno;
1048             }
1049         }
1050     }
1051     set_cloexec_file(KTFILEP(id));
1052     if ((kerror = krb5_lock_file(context, fileno(KTFILEP(id)), mode))) {
1053         (void) fclose(KTFILEP(id));
1054         KTFILEP(id) = 0;
1055         return kerror;
1056     }
1057     /* assume ANSI or BSD-style stdio */
1058     setbuf(KTFILEP(id), KTFILEBUFP(id));
1059
1060     /* get the vno and verify it */
1061     if (writevno) {
1062         kt_vno = htons(krb5_kt_default_vno);
1063         KTVERSION(id) = krb5_kt_default_vno;
1064         if (!fwrite(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
1065             kerror = errno;
1066             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
1067             (void) fclose(KTFILEP(id));
1068             KTFILEP(id) = 0;
1069             return kerror;
1070         }
1071     } else {
1072         /* gotta verify it instead... */
1073         if (!fread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
1074             if (feof(KTFILEP(id)))
1075                 kerror = KRB5_KEYTAB_BADVNO;
1076             else
1077                 kerror = errno;
1078             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
1079             (void) fclose(KTFILEP(id));
1080             KTFILEP(id) = 0;
1081             return kerror;
1082         }
1083         kt_vno = KTVERSION(id) = ntohs(kt_vno);
1084         if ((kt_vno != KRB5_KT_VNO) &&
1085             (kt_vno != KRB5_KT_VNO_1)) {
1086             (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
1087             (void) fclose(KTFILEP(id));
1088             KTFILEP(id) = 0;
1089             return KRB5_KEYTAB_BADVNO;
1090         }
1091     }
1092     KTSTARTOFF(id) = ftell(KTFILEP(id));
1093     return 0;
1094 }
1095
1096 static krb5_error_code
1097 krb5_ktfileint_openr(krb5_context context, krb5_keytab id)
1098 {
1099     return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_SHARED);
1100 }
1101
1102 static krb5_error_code
1103 krb5_ktfileint_openw(krb5_context context, krb5_keytab id)
1104 {
1105     return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_EXCLUSIVE);
1106 }
1107
1108 static krb5_error_code
1109 krb5_ktfileint_close(krb5_context context, krb5_keytab id)
1110 {
1111     krb5_error_code kerror;
1112
1113     KTCHECKLOCK(id);
1114     if (!KTFILEP(id))
1115         return 0;
1116     kerror = krb5_unlock_file(context, fileno(KTFILEP(id)));
1117     (void) fclose(KTFILEP(id));
1118     KTFILEP(id) = 0;
1119     return kerror;
1120 }
1121
1122 static krb5_error_code
1123 krb5_ktfileint_delete_entry(krb5_context context, krb5_keytab id, krb5_int32 delete_point)
1124 {
1125     krb5_int32  size;
1126     krb5_int32  len;
1127     char        iobuf[BUFSIZ];
1128
1129     KTCHECKLOCK(id);
1130     if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
1131         return errno;
1132     }
1133     if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
1134         return KRB5_KT_END;
1135     }
1136     if (KTVERSION(id) != KRB5_KT_VNO_1)
1137         size = ntohl(size);
1138
1139     if (size > 0) {
1140         krb5_int32 minus_size = -size;
1141         if (KTVERSION(id) != KRB5_KT_VNO_1)
1142             minus_size = htonl(minus_size);
1143
1144         if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
1145             return errno;
1146         }
1147
1148         if (!fwrite(&minus_size, sizeof(minus_size), 1, KTFILEP(id))) {
1149             return KRB5_KT_IOERR;
1150         }
1151
1152         if (size < BUFSIZ) {
1153             len = size;
1154         } else {
1155             len = BUFSIZ;
1156         }
1157
1158         memset(iobuf, 0, (size_t) len);
1159         while (size > 0) {
1160             if (!fwrite(iobuf, 1, (size_t) len, KTFILEP(id))) {
1161                 return KRB5_KT_IOERR;
1162             }
1163             size -= len;
1164             if (size < len) {
1165                 len = size;
1166             }
1167         }
1168
1169         return k5_sync_disk_file(context, KTFILEP(id));
1170     }
1171
1172     return 0;
1173 }
1174
1175 static krb5_error_code
1176 krb5_ktfileint_internal_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *ret_entry, krb5_int32 *delete_point)
1177 {
1178     krb5_octet vno;
1179     krb5_int16 count;
1180     unsigned int u_count, u_princ_size;
1181     krb5_int16 enctype;
1182     krb5_int16 princ_size;
1183     register int i;
1184     krb5_int32 size;
1185     krb5_int32 start_pos, pos;
1186     krb5_error_code error;
1187     char        *tmpdata;
1188     krb5_data   *princ;
1189     uint32_t    vno32;
1190
1191     KTCHECKLOCK(id);
1192     memset(ret_entry, 0, sizeof(krb5_keytab_entry));
1193     ret_entry->magic = KV5M_KEYTAB_ENTRY;
1194
1195     /* fseek to synchronise buffered I/O on the key table. */
1196
1197     if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1198     {
1199         return errno;
1200     }
1201
1202     do {
1203         *delete_point = ftell(KTFILEP(id));
1204         if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
1205             return KRB5_KT_END;
1206         }
1207         if (KTVERSION(id) != KRB5_KT_VNO_1)
1208             size = ntohl(size);
1209
1210         if (size < 0) {
1211             if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
1212                 return errno;
1213             }
1214         }
1215     } while (size < 0);
1216
1217     if (size == 0) {
1218         return KRB5_KT_END;
1219     }
1220
1221     start_pos = ftell(KTFILEP(id));
1222
1223     /* deal with guts of parsing... */
1224
1225     /* first, int16 with #princ components */
1226     if (!fread(&count, sizeof(count), 1, KTFILEP(id)))
1227         return KRB5_KT_END;
1228     if (KTVERSION(id) == KRB5_KT_VNO_1) {
1229         count -= 1;         /* V1 includes the realm in the count */
1230     } else {
1231         count = ntohs(count);
1232     }
1233     if (!count || (count < 0))
1234         return KRB5_KT_END;
1235     ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
1236     if (!ret_entry->principal)
1237         return ENOMEM;
1238
1239     u_count = count;
1240     ret_entry->principal->magic = KV5M_PRINCIPAL;
1241     ret_entry->principal->length = u_count;
1242     ret_entry->principal->data = (krb5_data *)
1243         calloc(u_count, sizeof(krb5_data));
1244     if (!ret_entry->principal->data) {
1245         free(ret_entry->principal);
1246         ret_entry->principal = 0;
1247         return ENOMEM;
1248     }
1249
1250     /* Now, get the realm data */
1251     if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
1252         error = KRB5_KT_END;
1253         goto fail;
1254     }
1255     if (KTVERSION(id) != KRB5_KT_VNO_1)
1256         princ_size = ntohs(princ_size);
1257     if (!princ_size || (princ_size < 0)) {
1258         error = KRB5_KT_END;
1259         goto fail;
1260     }
1261     u_princ_size = princ_size;
1262
1263     ret_entry->principal->realm.length = u_princ_size;
1264     tmpdata = malloc(u_princ_size+1);
1265     if (!tmpdata) {
1266         error = ENOMEM;
1267         goto fail;
1268     }
1269     if (fread(tmpdata, 1, u_princ_size, KTFILEP(id)) != (size_t) princ_size) {
1270         free(tmpdata);
1271         error = KRB5_KT_END;
1272         goto fail;
1273     }
1274     tmpdata[princ_size] = 0;    /* Some things might be expecting null */
1275                                 /* termination...  ``Be conservative in */
1276                                 /* what you send out'' */
1277     ret_entry->principal->realm.data = tmpdata;
1278
1279     for (i = 0; i < count; i++) {
1280         princ = &ret_entry->principal->data[i];
1281         if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
1282             error = KRB5_KT_END;
1283             goto fail;
1284         }
1285         if (KTVERSION(id) != KRB5_KT_VNO_1)
1286             princ_size = ntohs(princ_size);
1287         if (!princ_size || (princ_size < 0)) {
1288             error = KRB5_KT_END;
1289             goto fail;
1290         }
1291
1292         u_princ_size = princ_size;
1293         princ->length = u_princ_size;
1294         princ->data = malloc(u_princ_size+1);
1295         if (!princ->data) {
1296             error = ENOMEM;
1297             goto fail;
1298         }
1299         if (!fread(princ->data, sizeof(char), u_princ_size, KTFILEP(id))) {
1300             error = KRB5_KT_END;
1301             goto fail;
1302         }
1303         princ->data[princ_size] = 0; /* Null terminate */
1304     }
1305
1306     /* read in the principal type, if we can get it */
1307     if (KTVERSION(id) != KRB5_KT_VNO_1) {
1308         if (!fread(&ret_entry->principal->type,
1309                    sizeof(ret_entry->principal->type), 1, KTFILEP(id))) {
1310             error = KRB5_KT_END;
1311             goto fail;
1312         }
1313         ret_entry->principal->type = ntohl(ret_entry->principal->type);
1314     }
1315
1316     /* read in the timestamp */
1317     if (!fread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
1318         error = KRB5_KT_END;
1319         goto fail;
1320     }
1321     if (KTVERSION(id) != KRB5_KT_VNO_1)
1322         ret_entry->timestamp = ntohl(ret_entry->timestamp);
1323
1324     /* read in the version number */
1325     if (!fread(&vno, sizeof(vno), 1, KTFILEP(id))) {
1326         error = KRB5_KT_END;
1327         goto fail;
1328     }
1329     ret_entry->vno = (krb5_kvno)vno;
1330
1331     /* key type */
1332     if (!fread(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1333         error = KRB5_KT_END;
1334         goto fail;
1335     }
1336     if (KTVERSION(id) != KRB5_KT_VNO_1)
1337         enctype = ntohs(enctype);
1338     ret_entry->key.enctype = (krb5_enctype)enctype;
1339
1340     /* key contents */
1341     ret_entry->key.magic = KV5M_KEYBLOCK;
1342
1343     if (!fread(&count, sizeof(count), 1, KTFILEP(id))) {
1344         error = KRB5_KT_END;
1345         goto fail;
1346     }
1347     if (KTVERSION(id) != KRB5_KT_VNO_1)
1348         count = ntohs(count);
1349     if (!count || (count < 0)) {
1350         error = KRB5_KT_END;
1351         goto fail;
1352     }
1353
1354     u_count = count;
1355     ret_entry->key.length = u_count;
1356
1357     ret_entry->key.contents = (krb5_octet *)malloc(u_count);
1358     if (!ret_entry->key.contents) {
1359         error = ENOMEM;
1360         goto fail;
1361     }
1362     if (!fread(ret_entry->key.contents, sizeof(krb5_octet), count,
1363                KTFILEP(id))) {
1364         error = KRB5_KT_END;
1365         goto fail;
1366     }
1367
1368     /* Check for a 32-bit kvno extension if four or more bytes remain. */
1369     pos = ftell(KTFILEP(id));
1370     if (pos - start_pos + 4 <= size) {
1371         if (!fread(&vno32, sizeof(vno32), 1, KTFILEP(id))) {
1372             error = KRB5_KT_END;
1373             goto fail;
1374         }
1375         if (KTVERSION(id) != KRB5_KT_VNO_1)
1376             vno32 = ntohl(vno32);
1377         /* If the value is 0, the bytes are just zero-fill. */
1378         if (vno32)
1379             ret_entry->vno = vno32;
1380     }
1381
1382     /*
1383      * Reposition file pointer to the next inter-record length field.
1384      */
1385     if (fseek(KTFILEP(id), start_pos + size, SEEK_SET) == -1) {
1386         error = errno;
1387         goto fail;
1388     }
1389
1390     return 0;
1391 fail:
1392
1393     for (i = 0; i < ret_entry->principal->length; i++)
1394         free(ret_entry->principal->data[i].data);
1395     free(ret_entry->principal->data);
1396     ret_entry->principal->data = 0;
1397     free(ret_entry->principal);
1398     ret_entry->principal = 0;
1399     return error;
1400 }
1401
1402 static krb5_error_code
1403 krb5_ktfileint_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entryp)
1404 {
1405     krb5_int32 delete_point;
1406
1407     return krb5_ktfileint_internal_read_entry(context, id, entryp, &delete_point);
1408 }
1409
1410 static krb5_error_code
1411 krb5_ktfileint_write_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
1412 {
1413     krb5_octet vno;
1414     krb5_data *princ;
1415     krb5_int16 count, size, enctype;
1416     krb5_error_code retval = 0;
1417     krb5_timestamp timestamp;
1418     krb5_int32  princ_type;
1419     krb5_int32  size_needed;
1420     krb5_int32  commit_point = -1;
1421     uint32_t    vno32;
1422     int         i;
1423
1424     KTCHECKLOCK(id);
1425     retval = krb5_ktfileint_size_entry(context, entry, &size_needed);
1426     if (retval)
1427         return retval;
1428     retval = krb5_ktfileint_find_slot(context, id, &size_needed, &commit_point);
1429     if (retval)
1430         return retval;
1431
1432     /* fseek to synchronise buffered I/O on the key table. */
1433     /* XXX Without the weird setbuf crock, can we get rid of this now?  */
1434     if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1435     {
1436         return errno;
1437     }
1438
1439     if (KTVERSION(id) == KRB5_KT_VNO_1) {
1440         count = (krb5_int16)entry->principal->length + 1;
1441     } else {
1442         count = htons((u_short)entry->principal->length);
1443     }
1444
1445     if (!fwrite(&count, sizeof(count), 1, KTFILEP(id))) {
1446     abend:
1447         return KRB5_KT_IOERR;
1448     }
1449     size = entry->principal->realm.length;
1450     if (KTVERSION(id) != KRB5_KT_VNO_1)
1451         size = htons(size);
1452     if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1453         goto abend;
1454     }
1455     if (!fwrite(entry->principal->realm.data, sizeof(char),
1456                 entry->principal->realm.length, KTFILEP(id))) {
1457         goto abend;
1458     }
1459
1460     count = (krb5_int16)entry->principal->length;
1461     for (i = 0; i < count; i++) {
1462         princ = &entry->principal->data[i];
1463         size = princ->length;
1464         if (KTVERSION(id) != KRB5_KT_VNO_1)
1465             size = htons(size);
1466         if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1467             goto abend;
1468         }
1469         if (!fwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
1470             goto abend;
1471         }
1472     }
1473
1474     /*
1475      * Write out the principal type
1476      */
1477     if (KTVERSION(id) != KRB5_KT_VNO_1) {
1478         princ_type = htonl(entry->principal->type);
1479         if (!fwrite(&princ_type, sizeof(princ_type), 1, KTFILEP(id))) {
1480             goto abend;
1481         }
1482     }
1483
1484     /*
1485      * Fill in the time of day the entry was written to the keytab.
1486      */
1487     if (krb5_timeofday(context, &entry->timestamp)) {
1488         entry->timestamp = 0;
1489     }
1490     if (KTVERSION(id) == KRB5_KT_VNO_1)
1491         timestamp = entry->timestamp;
1492     else
1493         timestamp = htonl(entry->timestamp);
1494     if (!fwrite(&timestamp, sizeof(timestamp), 1, KTFILEP(id))) {
1495         goto abend;
1496     }
1497
1498     /* key version number */
1499     vno = (krb5_octet)entry->vno;
1500     if (!fwrite(&vno, sizeof(vno), 1, KTFILEP(id))) {
1501         goto abend;
1502     }
1503     /* key type */
1504     if (KTVERSION(id) == KRB5_KT_VNO_1)
1505         enctype = entry->key.enctype;
1506     else
1507         enctype = htons(entry->key.enctype);
1508     if (!fwrite(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1509         goto abend;
1510     }
1511     /* key length */
1512     if (KTVERSION(id) == KRB5_KT_VNO_1)
1513         size = entry->key.length;
1514     else
1515         size = htons(entry->key.length);
1516     if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1517         goto abend;
1518     }
1519     if (!fwrite(entry->key.contents, sizeof(krb5_octet),
1520                 entry->key.length, KTFILEP(id))) {
1521         goto abend;
1522     }
1523
1524     /* 32-bit key version number */
1525     vno32 = entry->vno;
1526     if (KTVERSION(id) != KRB5_KT_VNO_1)
1527         vno32 = htonl(vno32);
1528     if (!fwrite(&vno32, sizeof(vno32), 1, KTFILEP(id)))
1529         goto abend;
1530
1531     if (fflush(KTFILEP(id)))
1532         goto abend;
1533
1534     retval = k5_sync_disk_file(context, KTFILEP(id));
1535
1536     if (retval) {
1537         return retval;
1538     }
1539
1540     if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
1541         return errno;
1542     }
1543     if (KTVERSION(id) != KRB5_KT_VNO_1)
1544         size_needed = htonl(size_needed);
1545     if (!fwrite(&size_needed, sizeof(size_needed), 1, KTFILEP(id))) {
1546         goto abend;
1547     }
1548     if (fflush(KTFILEP(id)))
1549         goto abend;
1550     retval = k5_sync_disk_file(context, KTFILEP(id));
1551
1552     return retval;
1553 }
1554
1555 /*
1556  * Determine the size needed for a file entry for the given
1557  * keytab entry.
1558  */
1559 static krb5_error_code
1560 krb5_ktfileint_size_entry(krb5_context context, krb5_keytab_entry *entry, krb5_int32 *size_needed)
1561 {
1562     krb5_int16 count;
1563     krb5_int32 total_size, i;
1564     krb5_error_code retval = 0;
1565
1566     count = (krb5_int16)entry->principal->length;
1567
1568     total_size = sizeof(count);
1569     total_size += entry->principal->realm.length + sizeof(krb5_int16);
1570
1571     for (i = 0; i < count; i++)
1572         total_size += entry->principal->data[i].length + sizeof(krb5_int16);
1573
1574     total_size += sizeof(entry->principal->type);
1575     total_size += sizeof(entry->timestamp);
1576     total_size += sizeof(krb5_octet);
1577     total_size += sizeof(krb5_int16);
1578     total_size += sizeof(krb5_int16) + entry->key.length;
1579     total_size += sizeof(uint32_t);
1580
1581     *size_needed = total_size;
1582     return retval;
1583 }
1584
1585 /*
1586  * Find and reserve a slot in the file for an entry of the needed size.
1587  * The commit point will be set to the position in the file where the
1588  * the length (sizeof(krb5_int32) bytes) of this node should be written
1589  * when commiting the write.  The file position left as a result of this
1590  * call is the position where the actual data should be written.
1591  *
1592  * The size_needed argument may be adjusted if we find a hole that is
1593  * larger than the size needed.  (Recall that size_needed will be used
1594  * to commit the write, but that this field must indicate the size of the
1595  * block in the file rather than the size of the actual entry)
1596  */
1597 static krb5_error_code
1598 krb5_ktfileint_find_slot(krb5_context context, krb5_keytab id, krb5_int32 *size_needed, krb5_int32 *commit_point_ptr)
1599 {
1600     FILE *fp;
1601     krb5_int32 size, zero_point, commit_point;
1602     krb5_kt_vno kt_vno;
1603
1604     KTCHECKLOCK(id);
1605     fp = KTFILEP(id);
1606     /* Skip over file version number. */
1607     if (fseek(fp, 0, SEEK_SET))
1608         return errno;
1609     if (!fread(&kt_vno, sizeof(kt_vno), 1, fp))
1610         return errno;
1611
1612     for (;;) {
1613         commit_point = ftell(fp);
1614         if (commit_point == -1)
1615             return errno;
1616         if (!fread(&size, sizeof(size), 1, fp)) {
1617             /* Hit the end of file, reserve this slot. */
1618             /* Necessary to avoid a later fseek failing on Solaris 10. */
1619             if (fseek(fp, 0, SEEK_CUR))
1620                 return errno;
1621             /* htonl(0) is 0, so no need to worry about byte order */
1622             size = 0;
1623             if (!fwrite(&size, sizeof(size), 1, fp))
1624                 return errno;
1625             break;
1626         }
1627
1628         if (KTVERSION(id) != KRB5_KT_VNO_1)
1629             size = ntohl(size);
1630
1631         if (size > 0) {
1632             /* Non-empty record; seek past it. */
1633             if (fseek(fp, size, SEEK_CUR))
1634                 return errno;
1635         } else if (size < 0) {
1636             /* Empty record; use if it's big enough, seek past otherwise. */
1637             size = -size;
1638             if (size >= *size_needed) {
1639                 *size_needed = size;
1640                 break;
1641             } else {
1642                 if (fseek(fp, size, SEEK_CUR))
1643                     return errno;
1644             }
1645         } else {
1646             /* Empty record at end of file; use it. */
1647             /* Ensure the new record will be followed by another 0. */
1648             zero_point = ftell(fp);
1649             if (zero_point == -1)
1650                 return errno;
1651             if (fseek(fp, *size_needed, SEEK_CUR))
1652                 return errno;
1653             /* htonl(0) is 0, so no need to worry about byte order */
1654             if (!fwrite(&size, sizeof(size), 1, fp))
1655                 return errno;
1656             if (fseek(fp, zero_point, SEEK_SET))
1657                 return errno;
1658             break;
1659         }
1660     }
1661
1662     *commit_point_ptr = commit_point;
1663     return 0;
1664 }
1665 #endif /* LEAN_CLIENT */