1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/keytab/kt_file.c */
4 * Copyright 1990,1991,1995,2007,2008 by the Massachusetts Institute of Technology.
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.
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.
27 * Copyright (c) Hewlett-Packard Company 1991
28 * Released to the Massachusetts Institute of Technology for inclusion
29 * in the Kerberos source code distribution.
31 * Copyright 1990,1991 by the Massachusetts Institute of Technology.
32 * All Rights Reserved.
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.
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.
57 #include "../os/os-proto.h"
61 * Information needed by internal routines of the file-based ticket
62 * cache implementation.
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) */
73 #define KRB5_KT_DEFAULT_VNO KRB5_KT_VNO
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 */
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.
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.
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.
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)
120 extern const struct _krb5_kt_ops krb5_ktf_ops;
121 extern const struct _krb5_kt_ops krb5_ktf_writable_ops;
123 static krb5_error_code KRB5_CALLCONV
124 krb5_ktfile_resolve(krb5_context, const char *, krb5_keytab *);
126 static krb5_error_code KRB5_CALLCONV
127 krb5_ktfile_get_name(krb5_context, krb5_keytab, char *, unsigned int);
129 static krb5_error_code KRB5_CALLCONV
130 krb5_ktfile_close(krb5_context, krb5_keytab);
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 *);
136 static krb5_error_code KRB5_CALLCONV
137 krb5_ktfile_start_seq_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
139 static krb5_error_code KRB5_CALLCONV
140 krb5_ktfile_get_next(krb5_context, krb5_keytab, krb5_keytab_entry *,
143 static krb5_error_code KRB5_CALLCONV
144 krb5_ktfile_end_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
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 *);
150 static krb5_error_code KRB5_CALLCONV
151 krb5_ktfile_remove(krb5_context, krb5_keytab, krb5_keytab_entry *);
153 static krb5_error_code
154 krb5_ktfileint_openr(krb5_context, krb5_keytab);
156 static krb5_error_code
157 krb5_ktfileint_openw(krb5_context, krb5_keytab);
159 static krb5_error_code
160 krb5_ktfileint_close(krb5_context, krb5_keytab);
162 static krb5_error_code
163 krb5_ktfileint_read_entry(krb5_context, krb5_keytab, krb5_keytab_entry *);
165 static krb5_error_code
166 krb5_ktfileint_write_entry(krb5_context, krb5_keytab, krb5_keytab_entry *);
168 static krb5_error_code
169 krb5_ktfileint_delete_entry(krb5_context, krb5_keytab, krb5_int32);
171 static krb5_error_code
172 krb5_ktfileint_internal_read_entry(krb5_context, krb5_keytab,
173 krb5_keytab_entry *, krb5_int32 *);
175 static krb5_error_code
176 krb5_ktfileint_size_entry(krb5_context, krb5_keytab_entry *, krb5_int32 *);
178 static krb5_error_code
179 krb5_ktfileint_find_slot(krb5_context, krb5_keytab, krb5_int32 *,
184 * This is an implementation specific resolver. It returns a keytab id
185 * initialized with file keytab routines.
188 static krb5_error_code KRB5_CALLCONV
189 krb5_ktfile_resolve(krb5_context context, const char *name,
192 krb5_ktfile_data *data = NULL;
193 krb5_error_code err = ENOMEM;
198 id = calloc(1, sizeof(*id));
202 id->ops = &krb5_ktf_ops;
203 data = calloc(1, sizeof(krb5_ktfile_data));
207 data->name = strdup(name);
208 if (data->name == NULL)
211 err = k5_mutex_init(&data->lock);
217 data->iter_count = 0;
219 id->data = (krb5_pointer) data;
220 id->magic = KV5M_KEYTAB;
233 * "Close" a file-based keytab and invalidate the id. This means
234 * free memory hidden in the structures.
237 static krb5_error_code KRB5_CALLCONV
238 krb5_ktfile_close(krb5_context context, krb5_keytab id)
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.
244 * This routine should undo anything done by krb5_ktfile_resolve().
247 free(KTFILENAME(id));
248 zap(KTFILEBUFP(id), BUFSIZ);
249 k5_mutex_destroy(&((krb5_ktfile_data *)id->data)->lock);
256 /* Return true if k1 is more recent than k2, applying wraparound heuristics. */
258 more_recent(const krb5_keytab_entry *k1, const krb5_keytab_entry *k2)
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).
267 if (!ts_after(k2->timestamp, k1->timestamp) &&
268 k1->vno < 128 && k2->vno > 240)
270 if (!ts_after(k1->timestamp, k2->timestamp) &&
271 k1->vno > 240 && k2->vno < 128)
274 /* Otherwise do a simple version comparison. */
275 return k1->vno > k2->vno;
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
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)
289 krb5_keytab_entry cur_entry, new_entry;
290 krb5_error_code kerror = 0;
291 int found_wrong_kvno = 0;
292 krb5_boolean similar;
298 if (KTFILEP(id) != NULL) {
301 if (fseek(KTFILEP(id), KTSTARTOFF(id), SEEK_SET) == -1) {
308 /* Open the keyfile for reading */
309 if ((kerror = krb5_ktfileint_openr(context, id))) {
316 * For efficiency and simplicity, we'll use a while true that
317 * is exited with a break statement.
319 cur_entry.principal = 0;
321 cur_entry.key.contents = 0;
324 if ((kerror = krb5_ktfileint_read_entry(context, id, &new_entry)))
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
331 /* if the principal isn't the one requested, free new_entry
332 and continue to the next. */
334 if (!krb5_principal_compare(context, principal, new_entry.principal)) {
335 krb5_kt_free_entry(context, &new_entry);
339 /* if the enctype is not ignored and doesn't match, free new_entry
340 and continue to the next */
342 if (enctype != IGNORE_ENCTYPE) {
343 if ((kerror = krb5_c_enctype_compare(context, enctype,
344 new_entry.key.enctype,
346 krb5_kt_free_entry(context, &new_entry);
351 krb5_kt_free_entry(context, &new_entry);
355 * Coerce the enctype of the output keyblock in case we
356 * got an inexact match on the enctype.
358 new_entry.key.enctype = enctype;
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;
370 krb5_kt_free_entry(context, &new_entry);
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.
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)
387 } else if (new_entry.vno == (kvno & 0xff) &&
388 cur_entry.principal == NULL) {
389 cur_entry = new_entry;
392 krb5_kt_free_entry(context, &new_entry);
397 if (kerror == KRB5_KT_END) {
398 if (cur_entry.principal)
400 else if (found_wrong_kvno)
401 kerror = KRB5_KT_KVNONOTFOUND;
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);
413 (void) krb5_ktfileint_close(context, id);
415 krb5_kt_free_entry(context, &cur_entry);
418 if (was_open == 0 && (kerror = krb5_ktfileint_close(context, id)) != 0) {
420 krb5_kt_free_entry(context, &cur_entry);
429 * Get the name of the file containing a file-based keytab.
432 static krb5_error_code KRB5_CALLCONV
433 krb5_ktfile_get_name(krb5_context context, krb5_keytab id, char *name, unsigned int len)
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.
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);
451 * krb5_ktfile_start_seq_get()
454 static krb5_error_code KRB5_CALLCONV
455 krb5_ktfile_start_seq_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursorp)
457 krb5_error_code retval;
462 if (KTITERS(id) == 0) {
463 if ((retval = krb5_ktfileint_openr(context, id))) {
469 if (!(fileoff = (long *)malloc(sizeof(*fileoff)))) {
470 if (KTITERS(id) == 0)
471 krb5_ktfileint_close(context, id);
475 *fileoff = KTSTARTOFF(id);
476 *cursorp = (krb5_kt_cursor)fileoff;
478 if (KTITERS(id) == 0) {
482 k5_setmsg(context, KRB5_KT_IOERR, "Too many keytab iterators active");
483 return KRB5_KT_IOERR; /* XXX */
491 * krb5_ktfile_get_next()
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)
497 long *fileoff = (long *)*cursor;
498 krb5_keytab_entry cur_entry;
499 krb5_error_code kerror;
502 if (KTFILEP(id) == NULL) {
504 return KRB5_KT_IOERR;
506 if (fseek(KTFILEP(id), *fileoff, 0) == -1) {
510 if ((kerror = krb5_ktfileint_read_entry(context, id, &cur_entry))) {
514 *fileoff = ftell(KTFILEP(id));
521 * krb5_ktfile_end_get()
524 static krb5_error_code KRB5_CALLCONV
525 krb5_ktfile_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
527 krb5_error_code kerror;
532 if (KTFILEP(id) != NULL && KTITERS(id) == 0)
533 kerror = krb5_ktfileint_close(context, id);
541 * ser_ktf.c - Serialize keytab file context for subsequent reopen.
544 static const char ktfile_def_name[] = ".";
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();
552 static krb5_error_code
553 krb5_ktf_keytab_size(krb5_context, krb5_pointer, size_t *);
555 static krb5_error_code
556 krb5_ktf_keytab_externalize(krb5_context, krb5_pointer, krb5_octet **,
559 static krb5_error_code
560 krb5_ktf_keytab_internalize(krb5_context,krb5_pointer *, krb5_octet **,
564 * Serialization entry for this type.
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 */
574 * krb5_ktf_keytab_size() - Determine the size required to externalize
575 * this krb5_keytab variant.
577 static krb5_error_code
578 krb5_ktf_keytab_size(krb5_context kcontext, krb5_pointer arg, size_t *sizep)
580 krb5_error_code kret;
583 krb5_ktfile_data *ktdata;
586 if ((keytab = (krb5_keytab) arg)) {
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
597 required = sizeof(krb5_int32) * 7;
598 if (keytab->ops && keytab->ops->prefix)
599 required += (strlen(keytab->ops->prefix)+1);
602 * The keytab name is formed as follows:
604 * If there's no name, we use a default name so that we have something
605 * to call krb5_keytab_resolve with.
607 ktdata = (krb5_ktfile_data *) keytab->data;
608 required += strlen((ktdata && ktdata->name) ?
609 ktdata->name : ktfile_def_name);
619 * krb5_ktf_keytab_externalize() - Externalize the krb5_keytab.
621 static krb5_error_code
622 krb5_ktf_keytab_externalize(krb5_context kcontext, krb5_pointer arg, krb5_octet **buffer, size_t *lenremain)
624 krb5_error_code kret;
629 krb5_ktfile_data *ktdata;
630 krb5_int32 file_is_open;
639 if ((keytab = (krb5_keytab) arg)) {
641 if (!krb5_ktf_keytab_size(kcontext, arg, &required) &&
642 (required <= remain)) {
644 (void) krb5_ser_pack_int32(KV5M_KEYTAB, &bp, &remain);
646 ktdata = (krb5_ktfile_data *) keytab->data;
650 /* Calculate the length of the name */
651 if (ktdata && ktdata->name)
652 fnamep = ktdata->name;
654 fnamep = ktfile_def_name;
656 if (keytab->ops && keytab->ops->prefix) {
657 if (asprintf(&ktname, "%s:%s", keytab->ops->prefix, fnamep) < 0)
660 ktname = strdup(fnamep);
663 /* Fill in the file-specific keytab information. */
671 fflags = fcntl(fileno(ktdata->openf), F_GETFL, 0);
673 file_is_open |= ((fflags & O_ACCMODE) << 1);
677 fpos = ftell(ktdata->openf);
678 file_pos = fpos; /* XX range check? */
682 /* Put the length of the file name */
683 (void) krb5_ser_pack_int32((krb5_int32) strlen(ktname),
687 (void) krb5_ser_pack_bytes((krb5_octet *) ktname,
691 /* Put the file open flag */
692 (void) krb5_ser_pack_int32(file_is_open, &bp, &remain);
694 /* Put the file position */
695 (void) krb5_ser_pack_int64(file_pos, &bp, &remain);
697 /* Put the version */
698 (void) krb5_ser_pack_int32((krb5_int32) ((ktdata) ?
699 ktdata->version : 0),
702 /* Put the trailer */
703 (void) krb5_ser_pack_int32(KV5M_KEYTAB, &bp, &remain);
715 * krb5_ktf_keytab_internalize() - Internalize the krb5_ktf_keytab.
717 static krb5_error_code
718 krb5_ktf_keytab_internalize(krb5_context kcontext, krb5_pointer *argp, krb5_octet **buffer, size_t *lenremain)
720 krb5_error_code kret;
721 krb5_keytab keytab = NULL;
726 krb5_ktfile_data *ktdata;
727 krb5_int32 file_is_open;
734 /* Read our magic number */
735 if (krb5_ser_unpack_int32(&ibuf, &bp, &remain) || ibuf != KV5M_KEYTAB)
738 /* Read the keytab name */
739 kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
742 ktname = malloc(ibuf + 1);
745 kret = krb5_ser_unpack_bytes((krb5_octet *) ktname, (size_t) ibuf,
751 /* Resolve the keytab. */
752 kret = krb5_kt_resolve(kcontext, ktname, &keytab);
756 if (keytab->ops != &krb5_ktf_ops) {
760 ktdata = (krb5_ktfile_data *) keytab->data;
762 if (remain < (sizeof(krb5_int32)*5)) {
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) {
781 fmode = (file_is_open >> 1) & O_ACCMODE;
786 kret = krb5_ktfileint_openw(kcontext, keytab);
788 kret = krb5_ktfileint_openr(kcontext, keytab);
791 fpos = foff; /* XX range check? */
792 if (fseek(KTFILEP(keytab), fpos, SEEK_SET) == -1) {
800 *argp = (krb5_pointer) keytab;
802 if (kret != 0 && keytab)
803 krb5_kt_close(kcontext, keytab);
813 static krb5_error_code KRB5_CALLCONV
814 krb5_ktfile_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
816 krb5_error_code retval;
820 /* Iterator(s) active -- no changes. */
822 k5_setmsg(context, KRB5_KT_IOERR,
823 _("Cannot change keytab with keytab iterators active"));
824 return KRB5_KT_IOERR; /* XXX */
826 if ((retval = krb5_ktfileint_openw(context, id))) {
830 if (fseek(KTFILEP(id), 0, 2) == -1) {
834 retval = krb5_ktfileint_write_entry(context, id, entry);
835 krb5_ktfileint_close(context, id);
841 * krb5_ktfile_remove()
844 static krb5_error_code KRB5_CALLCONV
845 krb5_ktfile_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
847 krb5_keytab_entry cur_entry;
848 krb5_error_code kerror;
849 krb5_int32 delete_point;
853 /* Iterator(s) active -- no changes. */
855 k5_setmsg(context, KRB5_KT_IOERR,
856 _("Cannot change keytab with keytab iterators active"));
857 return KRB5_KT_IOERR; /* XXX */
860 if ((kerror = krb5_ktfileint_openw(context, id))) {
866 * For efficiency and simplicity, we'll use a while true that
867 * is exited with a break statement.
870 if ((kerror = krb5_ktfileint_internal_read_entry(context, id,
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)) {
879 krb5_kt_free_entry(context, &cur_entry);
882 krb5_kt_free_entry(context, &cur_entry);
885 if (kerror == KRB5_KT_END)
886 kerror = KRB5_KT_NOTFOUND;
889 (void) krb5_ktfileint_close(context, id);
894 kerror = krb5_ktfileint_delete_entry(context, id, delete_point);
897 (void) krb5_ktfileint_close(context, id);
899 kerror = krb5_ktfileint_close(context, id);
909 const struct _krb5_kt_ops krb5_ktf_ops = {
911 "FILE", /* Prefix -- this string should not appear anywhere else! */
913 krb5_ktfile_get_name,
915 krb5_ktfile_get_entry,
916 krb5_ktfile_start_seq_get,
917 krb5_ktfile_get_next,
921 &krb5_ktfile_ser_entry
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.
930 const struct _krb5_kt_ops krb5_ktf_writable_ops = {
932 "WRFILE", /* Prefix -- this string should not appear anywhere else! */
934 krb5_ktfile_get_name,
936 krb5_ktfile_get_entry,
937 krb5_ktfile_start_seq_get,
938 krb5_ktfile_get_next,
942 &krb5_ktfile_ser_entry
949 const krb5_kt_ops krb5_kt_dfl_ops = {
951 "FILE", /* Prefix -- this string should not appear anywhere else! */
953 krb5_ktfile_get_name,
955 krb5_ktfile_get_entry,
956 krb5_ktfile_start_seq_get,
957 krb5_ktfile_get_next,
961 &krb5_ktfile_ser_entry
964 /* Formerly lib/krb5/keytab/file/ktf_util.c */
967 * This function contains utilities for the file based implementation of
968 * the keytab. There are no public functions in this file.
970 * This file is the only one that has knowledge of the format of a
973 * The format is as follows:
977 * principal timestamp vno key
979 * principal timestamp vno key
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.
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
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
1014 typedef krb5_int16 krb5_kt_vno;
1016 #define krb5_kt_default_vno ((krb5_kt_vno)KRB5_KT_DEFAULT_VNO)
1018 static krb5_error_code
1019 krb5_ktfileint_open(krb5_context context, krb5_keytab id, int mode)
1021 krb5_error_code kerror;
1027 KTFILEP(id) = fopen(KTFILENAME(id),
1028 (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "rb+" : "rb");
1030 if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
1031 /* try making it first time around */
1032 k5_create_secure_file(context, KTFILENAME(id));
1034 KTFILEP(id) = fopen(KTFILENAME(id), "rb+");
1045 k5_setmsg(context, ENOENT,
1046 _("Key table file '%s' not found"), KTFILENAME(id));
1053 set_cloexec_file(KTFILEP(id));
1054 if ((kerror = krb5_lock_file(context, fileno(KTFILEP(id)), mode))) {
1055 (void) fclose(KTFILEP(id));
1059 /* assume ANSI or BSD-style stdio */
1060 setbuf(KTFILEP(id), KTFILEBUFP(id));
1062 /* get the vno and verify it */
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))) {
1068 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
1069 (void) fclose(KTFILEP(id));
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;
1080 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
1081 (void) fclose(KTFILEP(id));
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));
1091 return KRB5_KEYTAB_BADVNO;
1094 KTSTARTOFF(id) = ftell(KTFILEP(id));
1098 static krb5_error_code
1099 krb5_ktfileint_openr(krb5_context context, krb5_keytab id)
1101 return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_SHARED);
1104 static krb5_error_code
1105 krb5_ktfileint_openw(krb5_context context, krb5_keytab id)
1107 return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_EXCLUSIVE);
1110 static krb5_error_code
1111 krb5_ktfileint_close(krb5_context context, krb5_keytab id)
1113 krb5_error_code kerror;
1118 kerror = krb5_unlock_file(context, fileno(KTFILEP(id)));
1119 (void) fclose(KTFILEP(id));
1124 static krb5_error_code
1125 krb5_ktfileint_delete_entry(krb5_context context, krb5_keytab id, krb5_int32 delete_point)
1132 if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
1135 if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
1138 if (KTVERSION(id) != KRB5_KT_VNO_1)
1142 krb5_int32 minus_size = -size;
1143 if (KTVERSION(id) != KRB5_KT_VNO_1)
1144 minus_size = htonl(minus_size);
1146 if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
1150 if (!fwrite(&minus_size, sizeof(minus_size), 1, KTFILEP(id))) {
1151 return KRB5_KT_IOERR;
1154 if (size < BUFSIZ) {
1160 memset(iobuf, 0, (size_t) len);
1162 if (!fwrite(iobuf, 1, (size_t) len, KTFILEP(id))) {
1163 return KRB5_KT_IOERR;
1171 return k5_sync_disk_file(context, KTFILEP(id));
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)
1182 unsigned int u_count, u_princ_size;
1184 krb5_int16 princ_size;
1187 krb5_int32 start_pos, pos;
1188 krb5_error_code error;
1194 memset(ret_entry, 0, sizeof(krb5_keytab_entry));
1195 ret_entry->magic = KV5M_KEYTAB_ENTRY;
1197 /* fseek to synchronise buffered I/O on the key table. */
1199 if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1205 *delete_point = ftell(KTFILEP(id));
1206 if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
1209 if (KTVERSION(id) != KRB5_KT_VNO_1)
1213 if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
1223 start_pos = ftell(KTFILEP(id));
1225 /* deal with guts of parsing... */
1227 /* first, int16 with #princ components */
1228 if (!fread(&count, sizeof(count), 1, KTFILEP(id)))
1230 if (KTVERSION(id) == KRB5_KT_VNO_1) {
1231 count -= 1; /* V1 includes the realm in the count */
1233 count = ntohs(count);
1235 if (!count || (count < 0))
1237 ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
1238 if (!ret_entry->principal)
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;
1252 /* Now, get the realm data */
1253 if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
1254 error = KRB5_KT_END;
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;
1263 u_princ_size = princ_size;
1265 ret_entry->principal->realm.length = u_princ_size;
1266 tmpdata = malloc(u_princ_size+1);
1271 if (fread(tmpdata, 1, u_princ_size, KTFILEP(id)) != (size_t) princ_size) {
1273 error = KRB5_KT_END;
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;
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;
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;
1294 u_princ_size = princ_size;
1295 princ->length = u_princ_size;
1296 princ->data = malloc(u_princ_size+1);
1301 if (!fread(princ->data, sizeof(char), u_princ_size, KTFILEP(id))) {
1302 error = KRB5_KT_END;
1305 princ->data[princ_size] = 0; /* Null terminate */
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;
1315 ret_entry->principal->type = ntohl(ret_entry->principal->type);
1318 /* read in the timestamp */
1319 if (!fread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
1320 error = KRB5_KT_END;
1323 if (KTVERSION(id) != KRB5_KT_VNO_1)
1324 ret_entry->timestamp = ntohl(ret_entry->timestamp);
1326 /* read in the version number */
1327 if (!fread(&vno, sizeof(vno), 1, KTFILEP(id))) {
1328 error = KRB5_KT_END;
1331 ret_entry->vno = (krb5_kvno)vno;
1334 if (!fread(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1335 error = KRB5_KT_END;
1338 if (KTVERSION(id) != KRB5_KT_VNO_1)
1339 enctype = ntohs(enctype);
1340 ret_entry->key.enctype = (krb5_enctype)enctype;
1343 ret_entry->key.magic = KV5M_KEYBLOCK;
1345 if (!fread(&count, sizeof(count), 1, KTFILEP(id))) {
1346 error = KRB5_KT_END;
1349 if (KTVERSION(id) != KRB5_KT_VNO_1)
1350 count = ntohs(count);
1351 if (!count || (count < 0)) {
1352 error = KRB5_KT_END;
1357 ret_entry->key.length = u_count;
1359 ret_entry->key.contents = (krb5_octet *)malloc(u_count);
1360 if (!ret_entry->key.contents) {
1364 if (!fread(ret_entry->key.contents, sizeof(krb5_octet), count,
1366 error = KRB5_KT_END;
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;
1377 if (KTVERSION(id) != KRB5_KT_VNO_1)
1378 vno32 = ntohl(vno32);
1379 /* If the value is 0, the bytes are just zero-fill. */
1381 ret_entry->vno = vno32;
1385 * Reposition file pointer to the next inter-record length field.
1387 if (fseek(KTFILEP(id), start_pos + size, SEEK_SET) == -1) {
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;
1404 static krb5_error_code
1405 krb5_ktfileint_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entryp)
1407 krb5_int32 delete_point;
1409 return krb5_ktfileint_internal_read_entry(context, id, entryp, &delete_point);
1412 static krb5_error_code
1413 krb5_ktfileint_write_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
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;
1427 retval = krb5_ktfileint_size_entry(context, entry, &size_needed);
1430 retval = krb5_ktfileint_find_slot(context, id, &size_needed, &commit_point);
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)
1441 if (KTVERSION(id) == KRB5_KT_VNO_1) {
1442 count = (krb5_int16)entry->principal->length + 1;
1444 count = htons((u_short)entry->principal->length);
1447 if (!fwrite(&count, sizeof(count), 1, KTFILEP(id))) {
1449 return KRB5_KT_IOERR;
1451 size = entry->principal->realm.length;
1452 if (KTVERSION(id) != KRB5_KT_VNO_1)
1454 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1457 if (!fwrite(entry->principal->realm.data, sizeof(char),
1458 entry->principal->realm.length, KTFILEP(id))) {
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)
1468 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1471 if (!fwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
1477 * Write out the principal type
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))) {
1487 * Fill in the time of day the entry was written to the keytab.
1489 if (krb5_timeofday(context, &entry->timestamp)) {
1490 entry->timestamp = 0;
1492 if (KTVERSION(id) == KRB5_KT_VNO_1)
1493 timestamp = entry->timestamp;
1495 timestamp = htonl(entry->timestamp);
1496 if (!fwrite(×tamp, sizeof(timestamp), 1, KTFILEP(id))) {
1500 /* key version number */
1501 vno = (krb5_octet)entry->vno;
1502 if (!fwrite(&vno, sizeof(vno), 1, KTFILEP(id))) {
1506 if (KTVERSION(id) == KRB5_KT_VNO_1)
1507 enctype = entry->key.enctype;
1509 enctype = htons(entry->key.enctype);
1510 if (!fwrite(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1514 if (KTVERSION(id) == KRB5_KT_VNO_1)
1515 size = entry->key.length;
1517 size = htons(entry->key.length);
1518 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1521 if (!fwrite(entry->key.contents, sizeof(krb5_octet),
1522 entry->key.length, KTFILEP(id))) {
1526 /* 32-bit key version number */
1528 if (KTVERSION(id) != KRB5_KT_VNO_1)
1529 vno32 = htonl(vno32);
1530 if (!fwrite(&vno32, sizeof(vno32), 1, KTFILEP(id)))
1533 if (fflush(KTFILEP(id)))
1536 retval = k5_sync_disk_file(context, KTFILEP(id));
1542 if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
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))) {
1550 if (fflush(KTFILEP(id)))
1552 retval = k5_sync_disk_file(context, KTFILEP(id));
1558 * Determine the size needed for a file entry for the given
1561 static krb5_error_code
1562 krb5_ktfileint_size_entry(krb5_context context, krb5_keytab_entry *entry, krb5_int32 *size_needed)
1565 krb5_int32 total_size, i;
1566 krb5_error_code retval = 0;
1568 count = (krb5_int16)entry->principal->length;
1570 total_size = sizeof(count);
1571 total_size += entry->principal->realm.length + sizeof(krb5_int16);
1573 for (i = 0; i < count; i++)
1574 total_size += entry->principal->data[i].length + sizeof(krb5_int16);
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);
1583 *size_needed = total_size;
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.
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)
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)
1603 krb5_int32 size, zero_point, commit_point;
1608 /* Skip over file version number. */
1609 if (fseek(fp, 0, SEEK_SET))
1611 if (!fread(&kt_vno, sizeof(kt_vno), 1, fp))
1615 commit_point = ftell(fp);
1616 if (commit_point == -1)
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))
1623 /* htonl(0) is 0, so no need to worry about byte order */
1625 if (!fwrite(&size, sizeof(size), 1, fp))
1630 if (KTVERSION(id) != KRB5_KT_VNO_1)
1634 /* Non-empty record; seek past it. */
1635 if (fseek(fp, size, SEEK_CUR))
1637 } else if (size < 0) {
1638 /* Empty record; use if it's big enough, seek past otherwise. */
1640 if (size >= *size_needed) {
1641 *size_needed = size;
1644 if (fseek(fp, size, SEEK_CUR))
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)
1653 if (fseek(fp, *size_needed, SEEK_CUR))
1655 /* htonl(0) is 0, so no need to worry about byte order */
1656 if (!fwrite(&size, sizeof(size), 1, fp))
1658 if (fseek(fp, zero_point, SEEK_SET))
1664 *commit_point_ptr = commit_point;
1667 #endif /* LEAN_CLIENT */