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