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 (k1->timestamp >= k2->timestamp && k1->vno < 128 && k2->vno > 240)
269 if (k1->timestamp <= k2->timestamp && k1->vno > 240 && k2->vno < 128)
272 /* Otherwise do a simple version comparison. */
273 return k1->vno > k2->vno;
277 * This is the get_entry routine for the file based keytab implementation.
278 * It opens the keytab file, and either retrieves the entry or returns
282 static krb5_error_code KRB5_CALLCONV
283 krb5_ktfile_get_entry(krb5_context context, krb5_keytab id,
284 krb5_const_principal principal, krb5_kvno kvno,
285 krb5_enctype enctype, krb5_keytab_entry *entry)
287 krb5_keytab_entry cur_entry, new_entry;
288 krb5_error_code kerror = 0;
289 int found_wrong_kvno = 0;
290 krb5_boolean similar;
296 if (KTFILEP(id) != NULL) {
299 if (fseek(KTFILEP(id), KTSTARTOFF(id), SEEK_SET) == -1) {
306 /* Open the keyfile for reading */
307 if ((kerror = krb5_ktfileint_openr(context, id))) {
314 * For efficiency and simplicity, we'll use a while true that
315 * is exited with a break statement.
317 cur_entry.principal = 0;
319 cur_entry.key.contents = 0;
322 if ((kerror = krb5_ktfileint_read_entry(context, id, &new_entry)))
325 /* by the time this loop exits, it must either free cur_entry,
326 and copy new_entry there, or free new_entry. Otherwise, it
329 /* if the principal isn't the one requested, free new_entry
330 and continue to the next. */
332 if (!krb5_principal_compare(context, principal, new_entry.principal)) {
333 krb5_kt_free_entry(context, &new_entry);
337 /* if the enctype is not ignored and doesn't match, free new_entry
338 and continue to the next */
340 if (enctype != IGNORE_ENCTYPE) {
341 if ((kerror = krb5_c_enctype_compare(context, enctype,
342 new_entry.key.enctype,
344 krb5_kt_free_entry(context, &new_entry);
349 krb5_kt_free_entry(context, &new_entry);
353 * Coerce the enctype of the output keyblock in case we
354 * got an inexact match on the enctype.
356 new_entry.key.enctype = enctype;
360 if (kvno == IGNORE_VNO) {
361 /* If this entry is more recent (or the first match), free the
362 * current and keep the new. Otherwise, free the new. */
363 if (cur_entry.principal == NULL ||
364 more_recent(&new_entry, &cur_entry)) {
365 krb5_kt_free_entry(context, &cur_entry);
366 cur_entry = new_entry;
368 krb5_kt_free_entry(context, &new_entry);
372 * If this kvno matches exactly, free the current, keep the new,
373 * and break out. If it matches the low 8 bits of the desired
374 * kvno, remember the first match (because the recorded kvno may
375 * have been truncated due to pre-1.14 keytab format or kadmin
376 * protocol limitations) but keep looking for an exact match.
377 * Otherwise, remember that we were here so we can return the right
378 * error, and free the new.
380 if (new_entry.vno == kvno) {
381 krb5_kt_free_entry(context, &cur_entry);
382 cur_entry = new_entry;
383 if (new_entry.vno == kvno)
385 } else if (new_entry.vno == (kvno & 0xff) &&
386 cur_entry.principal == NULL) {
387 cur_entry = new_entry;
390 krb5_kt_free_entry(context, &new_entry);
395 if (kerror == KRB5_KT_END) {
396 if (cur_entry.principal)
398 else if (found_wrong_kvno)
399 kerror = KRB5_KT_KVNONOTFOUND;
401 kerror = KRB5_KT_NOTFOUND;
402 if (krb5_unparse_name(context, principal, &princname) == 0) {
403 k5_setmsg(context, kerror,
404 _("No key table entry found for %s"), princname);
411 (void) krb5_ktfileint_close(context, id);
413 krb5_kt_free_entry(context, &cur_entry);
416 if (was_open == 0 && (kerror = krb5_ktfileint_close(context, id)) != 0) {
418 krb5_kt_free_entry(context, &cur_entry);
427 * Get the name of the file containing a file-based keytab.
430 static krb5_error_code KRB5_CALLCONV
431 krb5_ktfile_get_name(krb5_context context, krb5_keytab id, char *name, unsigned int len)
433 * This routine returns the name of the name of the file associated with
434 * this file-based keytab. name is zeroed and the filename is truncated
435 * to fit in name if necessary. The name is prefixed with PREFIX:, so that
436 * trt will happen if the name is passed back to resolve.
441 memset(name, 0, len);
442 result = snprintf(name, len, "%s:%s", id->ops->prefix, KTFILENAME(id));
443 if (SNPRINTF_OVERFLOW(result, len))
444 return(KRB5_KT_NAME_TOOLONG);
449 * krb5_ktfile_start_seq_get()
452 static krb5_error_code KRB5_CALLCONV
453 krb5_ktfile_start_seq_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursorp)
455 krb5_error_code retval;
460 if (KTITERS(id) == 0) {
461 if ((retval = krb5_ktfileint_openr(context, id))) {
467 if (!(fileoff = (long *)malloc(sizeof(*fileoff)))) {
468 if (KTITERS(id) == 0)
469 krb5_ktfileint_close(context, id);
473 *fileoff = KTSTARTOFF(id);
474 *cursorp = (krb5_kt_cursor)fileoff;
476 if (KTITERS(id) == 0) {
480 k5_setmsg(context, KRB5_KT_IOERR, "Too many keytab iterators active");
481 return KRB5_KT_IOERR; /* XXX */
489 * krb5_ktfile_get_next()
492 static krb5_error_code KRB5_CALLCONV
493 krb5_ktfile_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)
495 long *fileoff = (long *)*cursor;
496 krb5_keytab_entry cur_entry;
497 krb5_error_code kerror;
500 if (KTFILEP(id) == NULL) {
502 return KRB5_KT_IOERR;
504 if (fseek(KTFILEP(id), *fileoff, 0) == -1) {
508 if ((kerror = krb5_ktfileint_read_entry(context, id, &cur_entry))) {
512 *fileoff = ftell(KTFILEP(id));
519 * krb5_ktfile_end_get()
522 static krb5_error_code KRB5_CALLCONV
523 krb5_ktfile_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
525 krb5_error_code kerror;
530 if (KTFILEP(id) != NULL && KTITERS(id) == 0)
531 kerror = krb5_ktfileint_close(context, id);
539 * ser_ktf.c - Serialize keytab file context for subsequent reopen.
542 static const char ktfile_def_name[] = ".";
545 * Routines to deal with externalizing krb5_keytab for [WR]FILE: variants.
546 * krb5_ktf_keytab_size();
547 * krb5_ktf_keytab_externalize();
548 * krb5_ktf_keytab_internalize();
550 static krb5_error_code
551 krb5_ktf_keytab_size(krb5_context, krb5_pointer, size_t *);
553 static krb5_error_code
554 krb5_ktf_keytab_externalize(krb5_context, krb5_pointer, krb5_octet **,
557 static krb5_error_code
558 krb5_ktf_keytab_internalize(krb5_context,krb5_pointer *, krb5_octet **,
562 * Serialization entry for this type.
564 const krb5_ser_entry krb5_ktfile_ser_entry = {
565 KV5M_KEYTAB, /* Type */
566 krb5_ktf_keytab_size, /* Sizer routine */
567 krb5_ktf_keytab_externalize, /* Externalize routine */
568 krb5_ktf_keytab_internalize /* Internalize routine */
572 * krb5_ktf_keytab_size() - Determine the size required to externalize
573 * this krb5_keytab variant.
575 static krb5_error_code
576 krb5_ktf_keytab_size(krb5_context kcontext, krb5_pointer arg, size_t *sizep)
578 krb5_error_code kret;
581 krb5_ktfile_data *ktdata;
584 if ((keytab = (krb5_keytab) arg)) {
586 * Saving FILE: variants of krb5_keytab requires at minimum:
587 * krb5_int32 for KV5M_KEYTAB
588 * krb5_int32 for length of keytab name.
589 * krb5_int32 for file status.
590 * krb5_int32 for file position.
591 * krb5_int32 for file position.
592 * krb5_int32 for version.
593 * krb5_int32 for KV5M_KEYTAB
595 required = sizeof(krb5_int32) * 7;
596 if (keytab->ops && keytab->ops->prefix)
597 required += (strlen(keytab->ops->prefix)+1);
600 * The keytab name is formed as follows:
602 * If there's no name, we use a default name so that we have something
603 * to call krb5_keytab_resolve with.
605 ktdata = (krb5_ktfile_data *) keytab->data;
606 required += strlen((ktdata && ktdata->name) ?
607 ktdata->name : ktfile_def_name);
617 * krb5_ktf_keytab_externalize() - Externalize the krb5_keytab.
619 static krb5_error_code
620 krb5_ktf_keytab_externalize(krb5_context kcontext, krb5_pointer arg, krb5_octet **buffer, size_t *lenremain)
622 krb5_error_code kret;
627 krb5_ktfile_data *ktdata;
628 krb5_int32 file_is_open;
637 if ((keytab = (krb5_keytab) arg)) {
639 if (!krb5_ktf_keytab_size(kcontext, arg, &required) &&
640 (required <= remain)) {
642 (void) krb5_ser_pack_int32(KV5M_KEYTAB, &bp, &remain);
644 ktdata = (krb5_ktfile_data *) keytab->data;
648 /* Calculate the length of the name */
649 if (ktdata && ktdata->name)
650 fnamep = ktdata->name;
652 fnamep = ktfile_def_name;
654 if (keytab->ops && keytab->ops->prefix) {
655 if (asprintf(&ktname, "%s:%s", keytab->ops->prefix, fnamep) < 0)
658 ktname = strdup(fnamep);
661 /* Fill in the file-specific keytab information. */
669 fflags = fcntl(fileno(ktdata->openf), F_GETFL, 0);
671 file_is_open |= ((fflags & O_ACCMODE) << 1);
675 fpos = ftell(ktdata->openf);
676 file_pos = fpos; /* XX range check? */
680 /* Put the length of the file name */
681 (void) krb5_ser_pack_int32((krb5_int32) strlen(ktname),
685 (void) krb5_ser_pack_bytes((krb5_octet *) ktname,
689 /* Put the file open flag */
690 (void) krb5_ser_pack_int32(file_is_open, &bp, &remain);
692 /* Put the file position */
693 (void) krb5_ser_pack_int64(file_pos, &bp, &remain);
695 /* Put the version */
696 (void) krb5_ser_pack_int32((krb5_int32) ((ktdata) ?
697 ktdata->version : 0),
700 /* Put the trailer */
701 (void) krb5_ser_pack_int32(KV5M_KEYTAB, &bp, &remain);
713 * krb5_ktf_keytab_internalize() - Internalize the krb5_ktf_keytab.
715 static krb5_error_code
716 krb5_ktf_keytab_internalize(krb5_context kcontext, krb5_pointer *argp, krb5_octet **buffer, size_t *lenremain)
718 krb5_error_code kret;
719 krb5_keytab keytab = NULL;
724 krb5_ktfile_data *ktdata;
725 krb5_int32 file_is_open;
732 /* Read our magic number */
733 if (krb5_ser_unpack_int32(&ibuf, &bp, &remain) || ibuf != KV5M_KEYTAB)
736 /* Read the keytab name */
737 kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
740 ktname = malloc(ibuf + 1);
743 kret = krb5_ser_unpack_bytes((krb5_octet *) ktname, (size_t) ibuf,
749 /* Resolve the keytab. */
750 kret = krb5_kt_resolve(kcontext, ktname, &keytab);
754 if (keytab->ops != &krb5_ktf_ops) {
758 ktdata = (krb5_ktfile_data *) keytab->data;
760 if (remain < (sizeof(krb5_int32)*5)) {
764 (void) krb5_ser_unpack_int32(&file_is_open, &bp, &remain);
765 (void) krb5_ser_unpack_int64(&foff, &bp, &remain);
766 (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
767 ktdata->version = (int) ibuf;
768 (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
769 if (ibuf != KV5M_KEYTAB) {
779 fmode = (file_is_open >> 1) & O_ACCMODE;
784 kret = krb5_ktfileint_openw(kcontext, keytab);
786 kret = krb5_ktfileint_openr(kcontext, keytab);
789 fpos = foff; /* XX range check? */
790 if (fseek(KTFILEP(keytab), fpos, SEEK_SET) == -1) {
798 *argp = (krb5_pointer) keytab;
800 if (kret != 0 && keytab)
801 krb5_kt_close(kcontext, keytab);
811 static krb5_error_code KRB5_CALLCONV
812 krb5_ktfile_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
814 krb5_error_code retval;
818 /* Iterator(s) active -- no changes. */
820 k5_setmsg(context, KRB5_KT_IOERR,
821 _("Cannot change keytab with keytab iterators active"));
822 return KRB5_KT_IOERR; /* XXX */
824 if ((retval = krb5_ktfileint_openw(context, id))) {
828 if (fseek(KTFILEP(id), 0, 2) == -1) {
832 retval = krb5_ktfileint_write_entry(context, id, entry);
833 krb5_ktfileint_close(context, id);
839 * krb5_ktfile_remove()
842 static krb5_error_code KRB5_CALLCONV
843 krb5_ktfile_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
845 krb5_keytab_entry cur_entry;
846 krb5_error_code kerror;
847 krb5_int32 delete_point;
851 /* Iterator(s) active -- no changes. */
853 k5_setmsg(context, KRB5_KT_IOERR,
854 _("Cannot change keytab with keytab iterators active"));
855 return KRB5_KT_IOERR; /* XXX */
858 if ((kerror = krb5_ktfileint_openw(context, id))) {
864 * For efficiency and simplicity, we'll use a while true that
865 * is exited with a break statement.
868 if ((kerror = krb5_ktfileint_internal_read_entry(context, id,
873 if ((entry->vno == cur_entry.vno) &&
874 (entry->key.enctype == cur_entry.key.enctype) &&
875 krb5_principal_compare(context, entry->principal, cur_entry.principal)) {
877 krb5_kt_free_entry(context, &cur_entry);
880 krb5_kt_free_entry(context, &cur_entry);
883 if (kerror == KRB5_KT_END)
884 kerror = KRB5_KT_NOTFOUND;
887 (void) krb5_ktfileint_close(context, id);
892 kerror = krb5_ktfileint_delete_entry(context, id, delete_point);
895 (void) krb5_ktfileint_close(context, id);
897 kerror = krb5_ktfileint_close(context, id);
907 const struct _krb5_kt_ops krb5_ktf_ops = {
909 "FILE", /* Prefix -- this string should not appear anywhere else! */
911 krb5_ktfile_get_name,
913 krb5_ktfile_get_entry,
914 krb5_ktfile_start_seq_get,
915 krb5_ktfile_get_next,
919 &krb5_ktfile_ser_entry
923 * krb5_ktf_writable_ops -- this is the same as krb5_ktf_ops except for the
924 * prefix. WRFILE should no longer be needed, but is effectively aliased to
925 * FILE for compatibility.
928 const struct _krb5_kt_ops krb5_ktf_writable_ops = {
930 "WRFILE", /* Prefix -- this string should not appear anywhere else! */
932 krb5_ktfile_get_name,
934 krb5_ktfile_get_entry,
935 krb5_ktfile_start_seq_get,
936 krb5_ktfile_get_next,
940 &krb5_ktfile_ser_entry
947 const krb5_kt_ops krb5_kt_dfl_ops = {
949 "FILE", /* Prefix -- this string should not appear anywhere else! */
951 krb5_ktfile_get_name,
953 krb5_ktfile_get_entry,
954 krb5_ktfile_start_seq_get,
955 krb5_ktfile_get_next,
959 &krb5_ktfile_ser_entry
962 /* Formerly lib/krb5/keytab/file/ktf_util.c */
965 * This function contains utilities for the file based implementation of
966 * the keytab. There are no public functions in this file.
968 * This file is the only one that has knowledge of the format of a
971 * The format is as follows:
975 * principal timestamp vno key
977 * principal timestamp vno key
980 * A length field (sizeof(krb5_int32)) exists between entries. When this
981 * length is positive it indicates an active entry, when negative a hole.
982 * The length indicates the size of the block in the file (this may be
983 * larger than the size of the next record, since we are using a first
984 * fit algorithm for re-using holes and the first fit may be larger than
985 * the entry we are writing). Another (compatible) implementation could
986 * break up holes when allocating them to smaller entries to minimize
987 * wasted space. (Such an implementation should also coalesce adjacent
988 * holes to reduce fragmentation). This implementation does neither.
990 * There are no separators between fields of an entry.
991 * A principal is a length-encoded array of length-encoded strings. The
992 * length is a krb5_int16 in each case. The specific format, then, is
993 * multiple entries concatinated with no separators. An entry has this
996 * sizeof(krb5_int16) bytes for number of components in the principal;
997 * then, each component listed in ordser.
998 * For each component, sizeof(krb5_int16) bytes for the number of bytes
999 * in the component, followed by the component.
1000 * sizeof(krb5_int32) for the principal type (for KEYTAB V2 and higher)
1001 * sizeof(krb5_int32) bytes for the timestamp
1002 * sizeof(krb5_octet) bytes for the key version number
1003 * sizeof(krb5_int16) bytes for the enctype
1004 * sizeof(krb5_int16) bytes for the key length, followed by the key
1012 typedef krb5_int16 krb5_kt_vno;
1014 #define krb5_kt_default_vno ((krb5_kt_vno)KRB5_KT_DEFAULT_VNO)
1016 static krb5_error_code
1017 krb5_ktfileint_open(krb5_context context, krb5_keytab id, int mode)
1019 krb5_error_code kerror;
1025 KTFILEP(id) = fopen(KTFILENAME(id),
1026 (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "rb+" : "rb");
1028 if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
1029 /* try making it first time around */
1030 k5_create_secure_file(context, KTFILENAME(id));
1032 KTFILEP(id) = fopen(KTFILENAME(id), "rb+");
1043 k5_setmsg(context, ENOENT,
1044 _("Key table file '%s' not found"), KTFILENAME(id));
1051 set_cloexec_file(KTFILEP(id));
1052 if ((kerror = krb5_lock_file(context, fileno(KTFILEP(id)), mode))) {
1053 (void) fclose(KTFILEP(id));
1057 /* assume ANSI or BSD-style stdio */
1058 setbuf(KTFILEP(id), KTFILEBUFP(id));
1060 /* get the vno and verify it */
1062 kt_vno = htons(krb5_kt_default_vno);
1063 KTVERSION(id) = krb5_kt_default_vno;
1064 if (!fwrite(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
1066 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
1067 (void) fclose(KTFILEP(id));
1072 /* gotta verify it instead... */
1073 if (!fread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
1074 if (feof(KTFILEP(id)))
1075 kerror = KRB5_KEYTAB_BADVNO;
1078 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
1079 (void) fclose(KTFILEP(id));
1083 kt_vno = KTVERSION(id) = ntohs(kt_vno);
1084 if ((kt_vno != KRB5_KT_VNO) &&
1085 (kt_vno != KRB5_KT_VNO_1)) {
1086 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
1087 (void) fclose(KTFILEP(id));
1089 return KRB5_KEYTAB_BADVNO;
1092 KTSTARTOFF(id) = ftell(KTFILEP(id));
1096 static krb5_error_code
1097 krb5_ktfileint_openr(krb5_context context, krb5_keytab id)
1099 return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_SHARED);
1102 static krb5_error_code
1103 krb5_ktfileint_openw(krb5_context context, krb5_keytab id)
1105 return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_EXCLUSIVE);
1108 static krb5_error_code
1109 krb5_ktfileint_close(krb5_context context, krb5_keytab id)
1111 krb5_error_code kerror;
1116 kerror = krb5_unlock_file(context, fileno(KTFILEP(id)));
1117 (void) fclose(KTFILEP(id));
1122 static krb5_error_code
1123 krb5_ktfileint_delete_entry(krb5_context context, krb5_keytab id, krb5_int32 delete_point)
1130 if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
1133 if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
1136 if (KTVERSION(id) != KRB5_KT_VNO_1)
1140 krb5_int32 minus_size = -size;
1141 if (KTVERSION(id) != KRB5_KT_VNO_1)
1142 minus_size = htonl(minus_size);
1144 if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
1148 if (!fwrite(&minus_size, sizeof(minus_size), 1, KTFILEP(id))) {
1149 return KRB5_KT_IOERR;
1152 if (size < BUFSIZ) {
1158 memset(iobuf, 0, (size_t) len);
1160 if (!fwrite(iobuf, 1, (size_t) len, KTFILEP(id))) {
1161 return KRB5_KT_IOERR;
1169 return k5_sync_disk_file(context, KTFILEP(id));
1175 static krb5_error_code
1176 krb5_ktfileint_internal_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *ret_entry, krb5_int32 *delete_point)
1180 unsigned int u_count, u_princ_size;
1182 krb5_int16 princ_size;
1185 krb5_int32 start_pos, pos;
1186 krb5_error_code error;
1192 memset(ret_entry, 0, sizeof(krb5_keytab_entry));
1193 ret_entry->magic = KV5M_KEYTAB_ENTRY;
1195 /* fseek to synchronise buffered I/O on the key table. */
1197 if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1203 *delete_point = ftell(KTFILEP(id));
1204 if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
1207 if (KTVERSION(id) != KRB5_KT_VNO_1)
1211 if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
1221 start_pos = ftell(KTFILEP(id));
1223 /* deal with guts of parsing... */
1225 /* first, int16 with #princ components */
1226 if (!fread(&count, sizeof(count), 1, KTFILEP(id)))
1228 if (KTVERSION(id) == KRB5_KT_VNO_1) {
1229 count -= 1; /* V1 includes the realm in the count */
1231 count = ntohs(count);
1233 if (!count || (count < 0))
1235 ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
1236 if (!ret_entry->principal)
1240 ret_entry->principal->magic = KV5M_PRINCIPAL;
1241 ret_entry->principal->length = u_count;
1242 ret_entry->principal->data = (krb5_data *)
1243 calloc(u_count, sizeof(krb5_data));
1244 if (!ret_entry->principal->data) {
1245 free(ret_entry->principal);
1246 ret_entry->principal = 0;
1250 /* Now, get the realm data */
1251 if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
1252 error = KRB5_KT_END;
1255 if (KTVERSION(id) != KRB5_KT_VNO_1)
1256 princ_size = ntohs(princ_size);
1257 if (!princ_size || (princ_size < 0)) {
1258 error = KRB5_KT_END;
1261 u_princ_size = princ_size;
1263 ret_entry->principal->realm.length = u_princ_size;
1264 tmpdata = malloc(u_princ_size+1);
1269 if (fread(tmpdata, 1, u_princ_size, KTFILEP(id)) != (size_t) princ_size) {
1271 error = KRB5_KT_END;
1274 tmpdata[princ_size] = 0; /* Some things might be expecting null */
1275 /* termination... ``Be conservative in */
1276 /* what you send out'' */
1277 ret_entry->principal->realm.data = tmpdata;
1279 for (i = 0; i < count; i++) {
1280 princ = &ret_entry->principal->data[i];
1281 if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
1282 error = KRB5_KT_END;
1285 if (KTVERSION(id) != KRB5_KT_VNO_1)
1286 princ_size = ntohs(princ_size);
1287 if (!princ_size || (princ_size < 0)) {
1288 error = KRB5_KT_END;
1292 u_princ_size = princ_size;
1293 princ->length = u_princ_size;
1294 princ->data = malloc(u_princ_size+1);
1299 if (!fread(princ->data, sizeof(char), u_princ_size, KTFILEP(id))) {
1300 error = KRB5_KT_END;
1303 princ->data[princ_size] = 0; /* Null terminate */
1306 /* read in the principal type, if we can get it */
1307 if (KTVERSION(id) != KRB5_KT_VNO_1) {
1308 if (!fread(&ret_entry->principal->type,
1309 sizeof(ret_entry->principal->type), 1, KTFILEP(id))) {
1310 error = KRB5_KT_END;
1313 ret_entry->principal->type = ntohl(ret_entry->principal->type);
1316 /* read in the timestamp */
1317 if (!fread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
1318 error = KRB5_KT_END;
1321 if (KTVERSION(id) != KRB5_KT_VNO_1)
1322 ret_entry->timestamp = ntohl(ret_entry->timestamp);
1324 /* read in the version number */
1325 if (!fread(&vno, sizeof(vno), 1, KTFILEP(id))) {
1326 error = KRB5_KT_END;
1329 ret_entry->vno = (krb5_kvno)vno;
1332 if (!fread(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1333 error = KRB5_KT_END;
1336 if (KTVERSION(id) != KRB5_KT_VNO_1)
1337 enctype = ntohs(enctype);
1338 ret_entry->key.enctype = (krb5_enctype)enctype;
1341 ret_entry->key.magic = KV5M_KEYBLOCK;
1343 if (!fread(&count, sizeof(count), 1, KTFILEP(id))) {
1344 error = KRB5_KT_END;
1347 if (KTVERSION(id) != KRB5_KT_VNO_1)
1348 count = ntohs(count);
1349 if (!count || (count < 0)) {
1350 error = KRB5_KT_END;
1355 ret_entry->key.length = u_count;
1357 ret_entry->key.contents = (krb5_octet *)malloc(u_count);
1358 if (!ret_entry->key.contents) {
1362 if (!fread(ret_entry->key.contents, sizeof(krb5_octet), count,
1364 error = KRB5_KT_END;
1368 /* Check for a 32-bit kvno extension if four or more bytes remain. */
1369 pos = ftell(KTFILEP(id));
1370 if (pos - start_pos + 4 <= size) {
1371 if (!fread(&vno32, sizeof(vno32), 1, KTFILEP(id))) {
1372 error = KRB5_KT_END;
1375 if (KTVERSION(id) != KRB5_KT_VNO_1)
1376 vno32 = ntohl(vno32);
1377 /* If the value is 0, the bytes are just zero-fill. */
1379 ret_entry->vno = vno32;
1383 * Reposition file pointer to the next inter-record length field.
1385 if (fseek(KTFILEP(id), start_pos + size, SEEK_SET) == -1) {
1393 for (i = 0; i < ret_entry->principal->length; i++)
1394 free(ret_entry->principal->data[i].data);
1395 free(ret_entry->principal->data);
1396 ret_entry->principal->data = 0;
1397 free(ret_entry->principal);
1398 ret_entry->principal = 0;
1402 static krb5_error_code
1403 krb5_ktfileint_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entryp)
1405 krb5_int32 delete_point;
1407 return krb5_ktfileint_internal_read_entry(context, id, entryp, &delete_point);
1410 static krb5_error_code
1411 krb5_ktfileint_write_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
1415 krb5_int16 count, size, enctype;
1416 krb5_error_code retval = 0;
1417 krb5_timestamp timestamp;
1418 krb5_int32 princ_type;
1419 krb5_int32 size_needed;
1420 krb5_int32 commit_point = -1;
1425 retval = krb5_ktfileint_size_entry(context, entry, &size_needed);
1428 retval = krb5_ktfileint_find_slot(context, id, &size_needed, &commit_point);
1432 /* fseek to synchronise buffered I/O on the key table. */
1433 /* XXX Without the weird setbuf crock, can we get rid of this now? */
1434 if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1439 if (KTVERSION(id) == KRB5_KT_VNO_1) {
1440 count = (krb5_int16)entry->principal->length + 1;
1442 count = htons((u_short)entry->principal->length);
1445 if (!fwrite(&count, sizeof(count), 1, KTFILEP(id))) {
1447 return KRB5_KT_IOERR;
1449 size = entry->principal->realm.length;
1450 if (KTVERSION(id) != KRB5_KT_VNO_1)
1452 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1455 if (!fwrite(entry->principal->realm.data, sizeof(char),
1456 entry->principal->realm.length, KTFILEP(id))) {
1460 count = (krb5_int16)entry->principal->length;
1461 for (i = 0; i < count; i++) {
1462 princ = &entry->principal->data[i];
1463 size = princ->length;
1464 if (KTVERSION(id) != KRB5_KT_VNO_1)
1466 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1469 if (!fwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
1475 * Write out the principal type
1477 if (KTVERSION(id) != KRB5_KT_VNO_1) {
1478 princ_type = htonl(entry->principal->type);
1479 if (!fwrite(&princ_type, sizeof(princ_type), 1, KTFILEP(id))) {
1485 * Fill in the time of day the entry was written to the keytab.
1487 if (krb5_timeofday(context, &entry->timestamp)) {
1488 entry->timestamp = 0;
1490 if (KTVERSION(id) == KRB5_KT_VNO_1)
1491 timestamp = entry->timestamp;
1493 timestamp = htonl(entry->timestamp);
1494 if (!fwrite(×tamp, sizeof(timestamp), 1, KTFILEP(id))) {
1498 /* key version number */
1499 vno = (krb5_octet)entry->vno;
1500 if (!fwrite(&vno, sizeof(vno), 1, KTFILEP(id))) {
1504 if (KTVERSION(id) == KRB5_KT_VNO_1)
1505 enctype = entry->key.enctype;
1507 enctype = htons(entry->key.enctype);
1508 if (!fwrite(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1512 if (KTVERSION(id) == KRB5_KT_VNO_1)
1513 size = entry->key.length;
1515 size = htons(entry->key.length);
1516 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1519 if (!fwrite(entry->key.contents, sizeof(krb5_octet),
1520 entry->key.length, KTFILEP(id))) {
1524 /* 32-bit key version number */
1526 if (KTVERSION(id) != KRB5_KT_VNO_1)
1527 vno32 = htonl(vno32);
1528 if (!fwrite(&vno32, sizeof(vno32), 1, KTFILEP(id)))
1531 if (fflush(KTFILEP(id)))
1534 retval = k5_sync_disk_file(context, KTFILEP(id));
1540 if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
1543 if (KTVERSION(id) != KRB5_KT_VNO_1)
1544 size_needed = htonl(size_needed);
1545 if (!fwrite(&size_needed, sizeof(size_needed), 1, KTFILEP(id))) {
1548 if (fflush(KTFILEP(id)))
1550 retval = k5_sync_disk_file(context, KTFILEP(id));
1556 * Determine the size needed for a file entry for the given
1559 static krb5_error_code
1560 krb5_ktfileint_size_entry(krb5_context context, krb5_keytab_entry *entry, krb5_int32 *size_needed)
1563 krb5_int32 total_size, i;
1564 krb5_error_code retval = 0;
1566 count = (krb5_int16)entry->principal->length;
1568 total_size = sizeof(count);
1569 total_size += entry->principal->realm.length + sizeof(krb5_int16);
1571 for (i = 0; i < count; i++)
1572 total_size += entry->principal->data[i].length + sizeof(krb5_int16);
1574 total_size += sizeof(entry->principal->type);
1575 total_size += sizeof(entry->timestamp);
1576 total_size += sizeof(krb5_octet);
1577 total_size += sizeof(krb5_int16);
1578 total_size += sizeof(krb5_int16) + entry->key.length;
1579 total_size += sizeof(uint32_t);
1581 *size_needed = total_size;
1586 * Find and reserve a slot in the file for an entry of the needed size.
1587 * The commit point will be set to the position in the file where the
1588 * the length (sizeof(krb5_int32) bytes) of this node should be written
1589 * when commiting the write. The file position left as a result of this
1590 * call is the position where the actual data should be written.
1592 * The size_needed argument may be adjusted if we find a hole that is
1593 * larger than the size needed. (Recall that size_needed will be used
1594 * to commit the write, but that this field must indicate the size of the
1595 * block in the file rather than the size of the actual entry)
1597 static krb5_error_code
1598 krb5_ktfileint_find_slot(krb5_context context, krb5_keytab id, krb5_int32 *size_needed, krb5_int32 *commit_point_ptr)
1601 krb5_int32 size, zero_point, commit_point;
1606 /* Skip over file version number. */
1607 if (fseek(fp, 0, SEEK_SET))
1609 if (!fread(&kt_vno, sizeof(kt_vno), 1, fp))
1613 commit_point = ftell(fp);
1614 if (commit_point == -1)
1616 if (!fread(&size, sizeof(size), 1, fp)) {
1617 /* Hit the end of file, reserve this slot. */
1618 /* Necessary to avoid a later fseek failing on Solaris 10. */
1619 if (fseek(fp, 0, SEEK_CUR))
1621 /* htonl(0) is 0, so no need to worry about byte order */
1623 if (!fwrite(&size, sizeof(size), 1, fp))
1628 if (KTVERSION(id) != KRB5_KT_VNO_1)
1632 /* Non-empty record; seek past it. */
1633 if (fseek(fp, size, SEEK_CUR))
1635 } else if (size < 0) {
1636 /* Empty record; use if it's big enough, seek past otherwise. */
1638 if (size >= *size_needed) {
1639 *size_needed = size;
1642 if (fseek(fp, size, SEEK_CUR))
1646 /* Empty record at end of file; use it. */
1647 /* Ensure the new record will be followed by another 0. */
1648 zero_point = ftell(fp);
1649 if (zero_point == -1)
1651 if (fseek(fp, *size_needed, SEEK_CUR))
1653 /* htonl(0) is 0, so no need to worry about byte order */
1654 if (!fwrite(&size, sizeof(size), 1, fp))
1656 if (fseek(fp, zero_point, SEEK_SET))
1662 *commit_point_ptr = commit_point;
1665 #endif /* LEAN_CLIENT */