1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/keytab/kt_file.c */
3 /*
4 * Copyright 1990,1991,1995,2007,2008 by the Massachusetts Institute of Technology.
5 * All Rights Reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26 /*
27 * Copyright (c) Hewlett-Packard Company 1991
28 * Released to the Massachusetts Institute of Technology for inclusion
29 * in the Kerberos source code distribution.
30 *
31 * Copyright 1990,1991 by the Massachusetts Institute of Technology.
32 * All Rights Reserved.
33 *
34 * Export of this software from the United States of America may
35 * require a specific license from the United States Government.
36 * It is the responsibility of any person or organization contemplating
37 * export to obtain such a license before exporting.
38 *
39 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
40 * distribute this software and its documentation for any purpose and
41 * without fee is hereby granted, provided that the above copyright
42 * notice appear in all copies and that both that copyright notice and
43 * this permission notice appear in supporting documentation, and that
44 * the name of M.I.T. not be used in advertising or publicity pertaining
45 * to distribution of the software without specific, written prior
46 * permission. Furthermore if you modify this software you must label
47 * your software as modified software and not distribute it in such a
48 * fashion that it might be confused with the original M.I.T. software.
49 * M.I.T. makes no representations about the suitability of
50 * this software for any purpose. It is provided "as is" without express
51 * or implied warranty.
52 */
53
54 #ifndef LEAN_CLIENT
55
56 #include "k5-int.h"
57 #include "../os/os-proto.h"
58 #include <stdio.h>
59
60 /*
61 * Information needed by internal routines of the file-based ticket
62 * cache implementation.
63 */
64
65
66 /*
67 * Constants
68 */
69
70 #define KRB5_KT_VNO_1 0x0501 /* krb v5, keytab version 1 (DCE compat) */
71 #define KRB5_KT_VNO 0x0502 /* krb v5, keytab version 2 (standard) */
72
73 #define KRB5_KT_DEFAULT_VNO KRB5_KT_VNO
74
75 /*
76 * Types
77 */
78 typedef struct _krb5_ktfile_data {
79 char *name; /* Name of the file */
80 FILE *openf; /* open file, if any. */
81 char iobuf[BUFSIZ]; /* so we can zap it later */
82 int version; /* Version number of keytab */
83 unsigned int iter_count; /* Number of active iterators */
84 long start_offset; /* Starting offset after version */
85 k5_mutex_t lock; /* Protect openf, version */
86 } krb5_ktfile_data;
87
88 /*
89 * Some limitations:
90 *
91 * If the file OPENF is left open between calls, we have an iterator
92 * active, and OPENF is opened in read-only mode. So, no changes
93 * can be made via that handle.
94 *
95 * An advisory file lock is used while the file is open. Thus,
96 * multiple handles on the same underlying file cannot be used without
97 * disrupting the locking in effect.
98 *
99 * The start_offset field is only valid if the file is open. It will
100 * almost certainly always be the same constant. It's used so that
101 * if an iterator is active, and we start another one, we don't have
102 * to seek back to the start and re-read the version number to set
103 * the position for the iterator.
104 */
105
106 /*
107 * Macros
108 */
109 #define KTPRIVATE(id) ((krb5_ktfile_data *)(id)->data)
110 #define KTFILENAME(id) (((krb5_ktfile_data *)(id)->data)->name)
111 #define KTFILEP(id) (((krb5_ktfile_data *)(id)->data)->openf)
112 #define KTFILEBUFP(id) (((krb5_ktfile_data *)(id)->data)->iobuf)
113 #define KTVERSION(id) (((krb5_ktfile_data *)(id)->data)->version)
114 #define KTITERS(id) (((krb5_ktfile_data *)(id)->data)->iter_count)
115 #define KTSTARTOFF(id) (((krb5_ktfile_data *)(id)->data)->start_offset)
116 #define KTLOCK(id) k5_mutex_lock(&((krb5_ktfile_data *)(id)->data)->lock)
117 #define KTUNLOCK(id) k5_mutex_unlock(&((krb5_ktfile_data *)(id)->data)->lock)
118 #define KTCHECKLOCK(id) k5_mutex_assert_locked(&((krb5_ktfile_data *)(id)->data)->lock)
119
120 extern const struct _krb5_kt_ops krb5_ktf_ops;
121 extern const struct _krb5_kt_ops krb5_ktf_writable_ops;
122
123 static krb5_error_code KRB5_CALLCONV
124 krb5_ktfile_resolve(krb5_context, const char *, krb5_keytab *);
125
126 static krb5_error_code KRB5_CALLCONV
127 krb5_ktfile_get_name(krb5_context, krb5_keytab, char *, unsigned int);
128
129 static krb5_error_code KRB5_CALLCONV
130 krb5_ktfile_close(krb5_context, krb5_keytab);
131
132 static krb5_error_code KRB5_CALLCONV
133 krb5_ktfile_get_entry(krb5_context, krb5_keytab, krb5_const_principal,
134 krb5_kvno, krb5_enctype, krb5_keytab_entry *);
135
136 static krb5_error_code KRB5_CALLCONV
137 krb5_ktfile_start_seq_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
138
139 static krb5_error_code KRB5_CALLCONV
140 krb5_ktfile_get_next(krb5_context, krb5_keytab, krb5_keytab_entry *,
141 krb5_kt_cursor *);
142
143 static krb5_error_code KRB5_CALLCONV
144 krb5_ktfile_end_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
145
146 /* routines to be included on extended version (write routines) */
147 static krb5_error_code KRB5_CALLCONV
148 krb5_ktfile_add(krb5_context, krb5_keytab, krb5_keytab_entry *);
149
150 static krb5_error_code KRB5_CALLCONV
151 krb5_ktfile_remove(krb5_context, krb5_keytab, krb5_keytab_entry *);
152
153 static krb5_error_code
154 krb5_ktfileint_openr(krb5_context, krb5_keytab);
155
156 static krb5_error_code
157 krb5_ktfileint_openw(krb5_context, krb5_keytab);
158
159 static krb5_error_code
160 krb5_ktfileint_close(krb5_context, krb5_keytab);
161
162 static krb5_error_code
163 krb5_ktfileint_read_entry(krb5_context, krb5_keytab, krb5_keytab_entry *);
164
165 static krb5_error_code
166 krb5_ktfileint_write_entry(krb5_context, krb5_keytab, krb5_keytab_entry *);
167
168 static krb5_error_code
169 krb5_ktfileint_delete_entry(krb5_context, krb5_keytab, krb5_int32);
170
171 static krb5_error_code
172 krb5_ktfileint_internal_read_entry(krb5_context, krb5_keytab,
173 krb5_keytab_entry *, krb5_int32 *);
174
175 static krb5_error_code
176 krb5_ktfileint_size_entry(krb5_context, krb5_keytab_entry *, krb5_int32 *);
177
178 static krb5_error_code
179 krb5_ktfileint_find_slot(krb5_context, krb5_keytab, krb5_int32 *,
180 krb5_int32 *);
181
182
183 /*
184 * This is an implementation specific resolver. It returns a keytab id
185 * initialized with file keytab routines.
186 */
187
188 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_resolve(krb5_context context,const char * name,krb5_keytab * id_out)189 krb5_ktfile_resolve(krb5_context context, const char *name,
190 krb5_keytab *id_out)
191 {
192 krb5_ktfile_data *data = NULL;
193 krb5_error_code err = ENOMEM;
194 krb5_keytab id;
195
196 *id_out = NULL;
197
198 id = calloc(1, sizeof(*id));
199 if (id == NULL)
200 return ENOMEM;
201
202 id->ops = &krb5_ktf_ops;
203 data = calloc(1, sizeof(krb5_ktfile_data));
204 if (data == NULL)
205 goto cleanup;
206
207 data->name = strdup(name);
208 if (data->name == NULL)
209 goto cleanup;
210
211 err = k5_mutex_init(&data->lock);
212 if (err)
213 goto cleanup;
214
215 data->openf = 0;
216 data->version = 0;
217 data->iter_count = 0;
218
219 id->data = (krb5_pointer) data;
220 id->magic = KV5M_KEYTAB;
221 *id_out = id;
222 return 0;
223 cleanup:
224 if (data)
225 free(data->name);
226 free(data);
227 free(id);
228 return err;
229 }
230
231
232 /*
233 * "Close" a file-based keytab and invalidate the id. This means
234 * free memory hidden in the structures.
235 */
236
237 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_close(krb5_context context,krb5_keytab id)238 krb5_ktfile_close(krb5_context context, krb5_keytab id)
239 /*
240 * This routine is responsible for freeing all memory allocated
241 * for this keytab. There are no system resources that need
242 * to be freed nor are there any open files.
243 *
244 * This routine should undo anything done by krb5_ktfile_resolve().
245 */
246 {
247 free(KTFILENAME(id));
248 zap(KTFILEBUFP(id), BUFSIZ);
249 k5_mutex_destroy(&((krb5_ktfile_data *)id->data)->lock);
250 free(id->data);
251 id->ops = 0;
252 free(id);
253 return (0);
254 }
255
256 /* Return true if k1 is more recent than k2, applying wraparound heuristics. */
257 static krb5_boolean
more_recent(const krb5_keytab_entry * k1,const krb5_keytab_entry * k2)258 more_recent(const krb5_keytab_entry *k1, const krb5_keytab_entry *k2)
259 {
260 /*
261 * If a small kvno was written at the same time or later than a large kvno,
262 * the kvno probably wrapped at some boundary, so consider the small kvno
263 * more recent. Wraparound can happen due to pre-1.14 keytab file format
264 * limitations (8-bit kvno storage), pre-1.14 kadmin protocol limitations
265 * (8-bit kvno marshalling), or KDB limitations (16-bit kvno storage).
266 */
267 if (!ts_after(k2->timestamp, k1->timestamp) &&
268 k1->vno < 128 && k2->vno > 240)
269 return TRUE;
270 if (!ts_after(k1->timestamp, k2->timestamp) &&
271 k1->vno > 240 && k2->vno < 128)
272 return FALSE;
273
274 /* Otherwise do a simple version comparison. */
275 return k1->vno > k2->vno;
276 }
277
278 /*
279 * This is the get_entry routine for the file based keytab implementation.
280 * It opens the keytab file, and either retrieves the entry or returns
281 * an error.
282 */
283
284 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_get_entry(krb5_context context,krb5_keytab id,krb5_const_principal principal,krb5_kvno kvno,krb5_enctype enctype,krb5_keytab_entry * entry)285 krb5_ktfile_get_entry(krb5_context context, krb5_keytab id,
286 krb5_const_principal principal, krb5_kvno kvno,
287 krb5_enctype enctype, krb5_keytab_entry *entry)
288 {
289 krb5_keytab_entry cur_entry, new_entry;
290 krb5_error_code kerror = 0;
291 int found_wrong_kvno = 0;
292 int was_open;
293 char *princname;
294
295 KTLOCK(id);
296
297 if (KTFILEP(id) != NULL) {
298 was_open = 1;
299
300 if (fseek(KTFILEP(id), KTSTARTOFF(id), SEEK_SET) == -1) {
301 KTUNLOCK(id);
302 return errno;
303 }
304 } else {
305 was_open = 0;
306
307 /* Open the keyfile for reading */
308 if ((kerror = krb5_ktfileint_openr(context, id))) {
309 KTUNLOCK(id);
310 return(kerror);
311 }
312 }
313
314 /*
315 * For efficiency and simplicity, we'll use a while true that
316 * is exited with a break statement.
317 */
318 cur_entry.principal = 0;
319 cur_entry.vno = 0;
320 cur_entry.key.contents = 0;
321
322 while (TRUE) {
323 if ((kerror = krb5_ktfileint_read_entry(context, id, &new_entry)))
324 break;
325
326 /* by the time this loop exits, it must either free cur_entry,
327 and copy new_entry there, or free new_entry. Otherwise, it
328 leaks. */
329
330 /* if the principal isn't the one requested, free new_entry
331 and continue to the next. */
332
333 if (!krb5_principal_compare(context, principal, new_entry.principal)) {
334 krb5_kt_free_entry(context, &new_entry);
335 continue;
336 }
337
338 /* If the enctype is not ignored and doesn't match, free new_entry and
339 continue to the next. */
340 if (enctype != IGNORE_ENCTYPE && enctype != new_entry.key.enctype) {
341 krb5_kt_free_entry(context, &new_entry);
342 continue;
343 }
344
345 if (kvno == IGNORE_VNO || new_entry.vno == IGNORE_VNO) {
346 /* If this entry is more recent (or the first match), free the
347 * current and keep the new. Otherwise, free the new. */
348 if (cur_entry.principal == NULL ||
349 more_recent(&new_entry, &cur_entry)) {
350 krb5_kt_free_entry(context, &cur_entry);
351 cur_entry = new_entry;
352 } else {
353 krb5_kt_free_entry(context, &new_entry);
354 }
355 } else {
356 /*
357 * If this kvno matches exactly, free the current, keep the new,
358 * and break out. If it matches the low 8 bits of the desired
359 * kvno, remember the first match (because the recorded kvno may
360 * have been truncated due to pre-1.14 keytab format or kadmin
361 * protocol limitations) but keep looking for an exact match.
362 * Otherwise, remember that we were here so we can return the right
363 * error, and free the new.
364 */
365 if (new_entry.vno == kvno) {
366 krb5_kt_free_entry(context, &cur_entry);
367 cur_entry = new_entry;
368 if (new_entry.vno == kvno)
369 break;
370 } else if (new_entry.vno == (kvno & 0xff) &&
371 cur_entry.principal == NULL) {
372 cur_entry = new_entry;
373 } else {
374 found_wrong_kvno++;
375 krb5_kt_free_entry(context, &new_entry);
376 }
377 }
378 }
379
380 if (kerror == KRB5_KT_END) {
381 if (cur_entry.principal)
382 kerror = 0;
383 else if (found_wrong_kvno)
384 kerror = KRB5_KT_KVNONOTFOUND;
385 else {
386 kerror = KRB5_KT_NOTFOUND;
387 if (krb5_unparse_name(context, principal, &princname) == 0) {
388 k5_setmsg(context, kerror,
389 _("No key table entry found for %s"), princname);
390 free(princname);
391 }
392 }
393 }
394 if (kerror) {
395 if (was_open == 0)
396 (void) krb5_ktfileint_close(context, id);
397 KTUNLOCK(id);
398 krb5_kt_free_entry(context, &cur_entry);
399 return kerror;
400 }
401 if (was_open == 0 && (kerror = krb5_ktfileint_close(context, id)) != 0) {
402 KTUNLOCK(id);
403 krb5_kt_free_entry(context, &cur_entry);
404 return kerror;
405 }
406 KTUNLOCK(id);
407 *entry = cur_entry;
408 return 0;
409 }
410
411 /*
412 * Get the name of the file containing a file-based keytab.
413 */
414
415 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_get_name(krb5_context context,krb5_keytab id,char * name,unsigned int len)416 krb5_ktfile_get_name(krb5_context context, krb5_keytab id, char *name, unsigned int len)
417 /*
418 * This routine returns the name of the name of the file associated with
419 * this file-based keytab. name is zeroed and the filename is truncated
420 * to fit in name if necessary. The name is prefixed with PREFIX:, so that
421 * trt will happen if the name is passed back to resolve.
422 */
423 {
424 int result;
425
426 memset(name, 0, len);
427 result = snprintf(name, len, "%s:%s", id->ops->prefix, KTFILENAME(id));
428 if (SNPRINTF_OVERFLOW(result, len))
429 return(KRB5_KT_NAME_TOOLONG);
430 return(0);
431 }
432
433 /*
434 * krb5_ktfile_start_seq_get()
435 */
436
437 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_start_seq_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursorp)438 krb5_ktfile_start_seq_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursorp)
439 {
440 krb5_error_code retval;
441 long *fileoff;
442
443 KTLOCK(id);
444
445 if (KTITERS(id) == 0) {
446 if ((retval = krb5_ktfileint_openr(context, id))) {
447 KTUNLOCK(id);
448 return retval;
449 }
450 }
451
452 if (!(fileoff = (long *)malloc(sizeof(*fileoff)))) {
453 if (KTITERS(id) == 0)
454 krb5_ktfileint_close(context, id);
455 KTUNLOCK(id);
456 return ENOMEM;
457 }
458 *fileoff = KTSTARTOFF(id);
459 KTITERS(id)++;
460 if (KTITERS(id) == 0) {
461 /* Wrapped?! */
462 KTITERS(id)--;
463 KTUNLOCK(id);
464 free(fileoff);
465 k5_setmsg(context, KRB5_KT_IOERR, "Too many keytab iterators active");
466 return KRB5_KT_IOERR; /* XXX */
467 }
468 *cursorp = (krb5_kt_cursor)fileoff;
469 KTUNLOCK(id);
470
471 return 0;
472 }
473
474 /*
475 * krb5_ktfile_get_next()
476 */
477
478 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_get_next(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry,krb5_kt_cursor * cursor)479 krb5_ktfile_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)
480 {
481 long *fileoff = (long *)*cursor;
482 krb5_keytab_entry cur_entry;
483 krb5_error_code kerror;
484
485 KTLOCK(id);
486 if (KTFILEP(id) == NULL) {
487 KTUNLOCK(id);
488 return KRB5_KT_IOERR;
489 }
490 if (fseek(KTFILEP(id), *fileoff, 0) == -1) {
491 KTUNLOCK(id);
492 return KRB5_KT_END;
493 }
494 if ((kerror = krb5_ktfileint_read_entry(context, id, &cur_entry))) {
495 KTUNLOCK(id);
496 return kerror;
497 }
498 *fileoff = ftell(KTFILEP(id));
499 *entry = cur_entry;
500 KTUNLOCK(id);
501 return 0;
502 }
503
504 /*
505 * krb5_ktfile_end_get()
506 */
507
508 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_end_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursor)509 krb5_ktfile_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
510 {
511 krb5_error_code kerror;
512
513 free(*cursor);
514 KTLOCK(id);
515 KTITERS(id)--;
516 if (KTFILEP(id) != NULL && KTITERS(id) == 0)
517 kerror = krb5_ktfileint_close(context, id);
518 else
519 kerror = 0;
520 KTUNLOCK(id);
521 return kerror;
522 }
523
524 /*
525 * krb5_ktfile_add()
526 */
527
528 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_add(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)529 krb5_ktfile_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
530 {
531 krb5_error_code retval;
532
533 KTLOCK(id);
534 if (KTFILEP(id)) {
535 /* Iterator(s) active -- no changes. */
536 KTUNLOCK(id);
537 k5_setmsg(context, KRB5_KT_IOERR,
538 _("Cannot change keytab with keytab iterators active"));
539 return KRB5_KT_IOERR; /* XXX */
540 }
541 if ((retval = krb5_ktfileint_openw(context, id))) {
542 KTUNLOCK(id);
543 return retval;
544 }
545 if (fseek(KTFILEP(id), 0, 2) == -1) {
546 KTUNLOCK(id);
547 return KRB5_KT_END;
548 }
549 retval = krb5_ktfileint_write_entry(context, id, entry);
550 krb5_ktfileint_close(context, id);
551 KTUNLOCK(id);
552 return retval;
553 }
554
555 /*
556 * krb5_ktfile_remove()
557 */
558
559 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_remove(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)560 krb5_ktfile_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
561 {
562 krb5_keytab_entry cur_entry;
563 krb5_error_code kerror;
564 krb5_int32 delete_point;
565
566 KTLOCK(id);
567 if (KTFILEP(id)) {
568 /* Iterator(s) active -- no changes. */
569 KTUNLOCK(id);
570 k5_setmsg(context, KRB5_KT_IOERR,
571 _("Cannot change keytab with keytab iterators active"));
572 return KRB5_KT_IOERR; /* XXX */
573 }
574
575 if ((kerror = krb5_ktfileint_openw(context, id))) {
576 KTUNLOCK(id);
577 return kerror;
578 }
579
580 /*
581 * For efficiency and simplicity, we'll use a while true that
582 * is exited with a break statement.
583 */
584 while (TRUE) {
585 if ((kerror = krb5_ktfileint_internal_read_entry(context, id,
586 &cur_entry,
587 &delete_point)))
588 break;
589
590 if ((entry->vno == cur_entry.vno) &&
591 (entry->key.enctype == cur_entry.key.enctype) &&
592 krb5_principal_compare(context, entry->principal, cur_entry.principal)) {
593 /* found a match */
594 krb5_kt_free_entry(context, &cur_entry);
595 break;
596 }
597 krb5_kt_free_entry(context, &cur_entry);
598 }
599
600 if (kerror == KRB5_KT_END)
601 kerror = KRB5_KT_NOTFOUND;
602
603 if (kerror) {
604 (void) krb5_ktfileint_close(context, id);
605 KTUNLOCK(id);
606 return kerror;
607 }
608
609 kerror = krb5_ktfileint_delete_entry(context, id, delete_point);
610
611 if (kerror) {
612 (void) krb5_ktfileint_close(context, id);
613 } else {
614 kerror = krb5_ktfileint_close(context, id);
615 }
616 KTUNLOCK(id);
617 return kerror;
618 }
619
620 /*
621 * krb5_ktf_ops
622 */
623
624 const struct _krb5_kt_ops krb5_ktf_ops = {
625 0,
626 "FILE", /* Prefix -- this string should not appear anywhere else! */
627 krb5_ktfile_resolve,
628 krb5_ktfile_get_name,
629 krb5_ktfile_close,
630 krb5_ktfile_get_entry,
631 krb5_ktfile_start_seq_get,
632 krb5_ktfile_get_next,
633 krb5_ktfile_end_get,
634 krb5_ktfile_add,
635 krb5_ktfile_remove
636 };
637
638 /*
639 * krb5_ktf_writable_ops -- this is the same as krb5_ktf_ops except for the
640 * prefix. WRFILE should no longer be needed, but is effectively aliased to
641 * FILE for compatibility.
642 */
643
644 const struct _krb5_kt_ops krb5_ktf_writable_ops = {
645 0,
646 "WRFILE", /* Prefix -- this string should not appear anywhere else! */
647 krb5_ktfile_resolve,
648 krb5_ktfile_get_name,
649 krb5_ktfile_close,
650 krb5_ktfile_get_entry,
651 krb5_ktfile_start_seq_get,
652 krb5_ktfile_get_next,
653 krb5_ktfile_end_get,
654 krb5_ktfile_add,
655 krb5_ktfile_remove
656 };
657
658 /*
659 * krb5_kt_dfl_ops
660 */
661
662 const krb5_kt_ops krb5_kt_dfl_ops = {
663 0,
664 "FILE", /* Prefix -- this string should not appear anywhere else! */
665 krb5_ktfile_resolve,
666 krb5_ktfile_get_name,
667 krb5_ktfile_close,
668 krb5_ktfile_get_entry,
669 krb5_ktfile_start_seq_get,
670 krb5_ktfile_get_next,
671 krb5_ktfile_end_get,
672 0,
673 0
674 };
675
676 /* Formerly lib/krb5/keytab/file/ktf_util.c */
677
678 /*
679 * This function contains utilities for the file based implementation of
680 * the keytab. There are no public functions in this file.
681 *
682 * This file is the only one that has knowledge of the format of a
683 * keytab file.
684 *
685 * The format is as follows:
686 *
687 * <file format vno>
688 * <record length>
689 * principal timestamp vno key
690 * <record length>
691 * principal timestamp vno key
692 * ....
693 *
694 * A length field (sizeof(krb5_int32)) exists between entries. When this
695 * length is positive it indicates an active entry, when negative a hole.
696 * The length indicates the size of the block in the file (this may be
697 * larger than the size of the next record, since we are using a first
698 * fit algorithm for re-using holes and the first fit may be larger than
699 * the entry we are writing). Another (compatible) implementation could
700 * break up holes when allocating them to smaller entries to minimize
701 * wasted space. (Such an implementation should also coalesce adjacent
702 * holes to reduce fragmentation). This implementation does neither.
703 *
704 * There are no separators between fields of an entry.
705 * A principal is a length-encoded array of length-encoded strings. The
706 * length is a krb5_int16 in each case. The specific format, then, is
707 * multiple entries concatenated with no separators. An entry has this
708 * exact format:
709 *
710 * sizeof(krb5_int16) bytes for number of components in the principal;
711 * then, each component listed in ordser.
712 * For each component, sizeof(krb5_int16) bytes for the number of bytes
713 * in the component, followed by the component.
714 * sizeof(krb5_int32) for the principal type (for KEYTAB V2 and higher)
715 * sizeof(krb5_int32) bytes for the timestamp
716 * sizeof(krb5_octet) bytes for the key version number
717 * sizeof(krb5_int16) bytes for the enctype
718 * sizeof(krb5_int16) bytes for the key length, followed by the key
719 */
720
721 #ifndef SEEK_SET
722 #define SEEK_SET 0
723 #define SEEK_CUR 1
724 #endif
725
726 typedef krb5_int16 krb5_kt_vno;
727
728 #define krb5_kt_default_vno ((krb5_kt_vno)KRB5_KT_DEFAULT_VNO)
729
730 static krb5_error_code
krb5_ktfileint_open(krb5_context context,krb5_keytab id,int mode)731 krb5_ktfileint_open(krb5_context context, krb5_keytab id, int mode)
732 {
733 krb5_error_code kerror;
734 krb5_kt_vno kt_vno;
735 int writevno = 0;
736
737 KTCHECKLOCK(id);
738 errno = 0;
739 KTFILEP(id) = fopen(KTFILENAME(id),
740 (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "rb+" : "rb");
741 if (!KTFILEP(id)) {
742 if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
743 /* try making it first time around */
744 k5_create_secure_file(context, KTFILENAME(id));
745 errno = 0;
746 KTFILEP(id) = fopen(KTFILENAME(id), "rb+");
747 if (!KTFILEP(id))
748 goto report_errno;
749 writevno = 1;
750 } else {
751 report_errno:
752 switch (errno) {
753 case 0:
754 /* XXX */
755 return EMFILE;
756 case ENOENT:
757 k5_setmsg(context, ENOENT,
758 _("Key table file '%s' not found"), KTFILENAME(id));
759 return ENOENT;
760 default:
761 return errno;
762 }
763 }
764 }
765 set_cloexec_file(KTFILEP(id));
766 if ((kerror = krb5_lock_file(context, fileno(KTFILEP(id)), mode))) {
767 (void) fclose(KTFILEP(id));
768 KTFILEP(id) = 0;
769 return kerror;
770 }
771 /* assume ANSI or BSD-style stdio */
772 setbuf(KTFILEP(id), KTFILEBUFP(id));
773
774 /* get the vno and verify it */
775 if (writevno) {
776 kt_vno = htons(krb5_kt_default_vno);
777 KTVERSION(id) = krb5_kt_default_vno;
778 if (!fwrite(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
779 kerror = errno;
780 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
781 (void) fclose(KTFILEP(id));
782 KTFILEP(id) = 0;
783 return kerror;
784 }
785 } else {
786 /* gotta verify it instead... */
787 if (!fread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
788 if (feof(KTFILEP(id)))
789 kerror = KRB5_KEYTAB_BADVNO;
790 else
791 kerror = errno;
792 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
793 (void) fclose(KTFILEP(id));
794 KTFILEP(id) = 0;
795 return kerror;
796 }
797 kt_vno = KTVERSION(id) = ntohs(kt_vno);
798 if ((kt_vno != KRB5_KT_VNO) &&
799 (kt_vno != KRB5_KT_VNO_1)) {
800 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
801 (void) fclose(KTFILEP(id));
802 KTFILEP(id) = 0;
803 return KRB5_KEYTAB_BADVNO;
804 }
805 }
806 KTSTARTOFF(id) = ftell(KTFILEP(id));
807 return 0;
808 }
809
810 static krb5_error_code
krb5_ktfileint_openr(krb5_context context,krb5_keytab id)811 krb5_ktfileint_openr(krb5_context context, krb5_keytab id)
812 {
813 return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_SHARED);
814 }
815
816 static krb5_error_code
krb5_ktfileint_openw(krb5_context context,krb5_keytab id)817 krb5_ktfileint_openw(krb5_context context, krb5_keytab id)
818 {
819 return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_EXCLUSIVE);
820 }
821
822 static krb5_error_code
krb5_ktfileint_close(krb5_context context,krb5_keytab id)823 krb5_ktfileint_close(krb5_context context, krb5_keytab id)
824 {
825 krb5_error_code kerror;
826
827 KTCHECKLOCK(id);
828 if (!KTFILEP(id))
829 return 0;
830 kerror = krb5_unlock_file(context, fileno(KTFILEP(id)));
831 (void) fclose(KTFILEP(id));
832 KTFILEP(id) = 0;
833 return kerror;
834 }
835
836 static krb5_error_code
krb5_ktfileint_delete_entry(krb5_context context,krb5_keytab id,krb5_int32 delete_point)837 krb5_ktfileint_delete_entry(krb5_context context, krb5_keytab id, krb5_int32 delete_point)
838 {
839 krb5_int32 size;
840 krb5_int32 len;
841 char iobuf[BUFSIZ];
842
843 KTCHECKLOCK(id);
844 if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
845 return errno;
846 }
847 if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
848 return KRB5_KT_END;
849 }
850 if (KTVERSION(id) != KRB5_KT_VNO_1)
851 size = ntohl(size);
852
853 if (size > 0) {
854 krb5_int32 minus_size = -size;
855 if (KTVERSION(id) != KRB5_KT_VNO_1)
856 minus_size = htonl(minus_size);
857
858 if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
859 return errno;
860 }
861
862 if (!fwrite(&minus_size, sizeof(minus_size), 1, KTFILEP(id))) {
863 return KRB5_KT_IOERR;
864 }
865
866 if (size < BUFSIZ) {
867 len = size;
868 } else {
869 len = BUFSIZ;
870 }
871
872 memset(iobuf, 0, (size_t) len);
873 while (size > 0) {
874 if (!fwrite(iobuf, 1, (size_t) len, KTFILEP(id))) {
875 return KRB5_KT_IOERR;
876 }
877 size -= len;
878 if (size < len) {
879 len = size;
880 }
881 }
882
883 return k5_sync_disk_file(context, KTFILEP(id));
884 }
885
886 return 0;
887 }
888
889 static krb5_error_code
krb5_ktfileint_internal_read_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * ret_entry,krb5_int32 * delete_point)890 krb5_ktfileint_internal_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *ret_entry, krb5_int32 *delete_point)
891 {
892 krb5_octet vno;
893 krb5_int16 count;
894 unsigned int u_count, u_princ_size;
895 krb5_int16 enctype;
896 krb5_int16 princ_size;
897 int i;
898 krb5_int32 size;
899 krb5_int32 start_pos, pos;
900 krb5_error_code error;
901 char *tmpdata;
902 krb5_data *princ;
903 uint32_t vno32;
904
905 KTCHECKLOCK(id);
906 memset(ret_entry, 0, sizeof(krb5_keytab_entry));
907 ret_entry->magic = KV5M_KEYTAB_ENTRY;
908
909 /* fseek to synchronise buffered I/O on the key table. */
910
911 if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
912 {
913 return errno;
914 }
915
916 do {
917 *delete_point = ftell(KTFILEP(id));
918 if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
919 return KRB5_KT_END;
920 }
921 if (KTVERSION(id) != KRB5_KT_VNO_1)
922 size = ntohl(size);
923
924 if (size < 0) {
925 if (size == INT32_MIN) /* INT32_MIN inverts to itself. */
926 return KRB5_KT_FORMAT;
927 if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
928 return errno;
929 }
930 }
931 } while (size < 0);
932
933 if (size == 0) {
934 return KRB5_KT_END;
935 }
936
937 start_pos = ftell(KTFILEP(id));
938
939 /* deal with guts of parsing... */
940
941 /* first, int16 with #princ components */
942 if (!fread(&count, sizeof(count), 1, KTFILEP(id)))
943 return KRB5_KT_END;
944 if (KTVERSION(id) == KRB5_KT_VNO_1) {
945 count -= 1; /* V1 includes the realm in the count */
946 } else {
947 count = ntohs(count);
948 }
949 if (!count || (count < 0))
950 return KRB5_KT_END;
951 ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
952 if (!ret_entry->principal)
953 return ENOMEM;
954
955 u_count = count;
956 ret_entry->principal->magic = KV5M_PRINCIPAL;
957 ret_entry->principal->length = u_count;
958 ret_entry->principal->data = (krb5_data *)
959 calloc(u_count, sizeof(krb5_data));
960 if (!ret_entry->principal->data) {
961 free(ret_entry->principal);
962 ret_entry->principal = 0;
963 return ENOMEM;
964 }
965
966 /* Now, get the realm data */
967 if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
968 error = KRB5_KT_END;
969 goto fail;
970 }
971 if (KTVERSION(id) != KRB5_KT_VNO_1)
972 princ_size = ntohs(princ_size);
973 if (!princ_size || (princ_size < 0)) {
974 error = KRB5_KT_END;
975 goto fail;
976 }
977 u_princ_size = princ_size;
978
979 ret_entry->principal->realm.length = u_princ_size;
980 tmpdata = malloc(u_princ_size+1);
981 if (!tmpdata) {
982 error = ENOMEM;
983 goto fail;
984 }
985 if (fread(tmpdata, 1, u_princ_size, KTFILEP(id)) != (size_t) princ_size) {
986 free(tmpdata);
987 error = KRB5_KT_END;
988 goto fail;
989 }
990 tmpdata[princ_size] = 0; /* Some things might be expecting null */
991 /* termination... ``Be conservative in */
992 /* what you send out'' */
993 ret_entry->principal->realm.data = tmpdata;
994
995 for (i = 0; i < count; i++) {
996 princ = &ret_entry->principal->data[i];
997 if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
998 error = KRB5_KT_END;
999 goto fail;
1000 }
1001 if (KTVERSION(id) != KRB5_KT_VNO_1)
1002 princ_size = ntohs(princ_size);
1003 if (!princ_size || (princ_size < 0)) {
1004 error = KRB5_KT_END;
1005 goto fail;
1006 }
1007
1008 u_princ_size = princ_size;
1009 princ->length = u_princ_size;
1010 princ->data = malloc(u_princ_size+1);
1011 if (!princ->data) {
1012 error = ENOMEM;
1013 goto fail;
1014 }
1015 if (!fread(princ->data, sizeof(char), u_princ_size, KTFILEP(id))) {
1016 error = KRB5_KT_END;
1017 goto fail;
1018 }
1019 princ->data[princ_size] = 0; /* Null terminate */
1020 }
1021
1022 /* read in the principal type, if we can get it */
1023 if (KTVERSION(id) != KRB5_KT_VNO_1) {
1024 if (!fread(&ret_entry->principal->type,
1025 sizeof(ret_entry->principal->type), 1, KTFILEP(id))) {
1026 error = KRB5_KT_END;
1027 goto fail;
1028 }
1029 ret_entry->principal->type = ntohl(ret_entry->principal->type);
1030 }
1031
1032 /* read in the timestamp */
1033 if (!fread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
1034 error = KRB5_KT_END;
1035 goto fail;
1036 }
1037 if (KTVERSION(id) != KRB5_KT_VNO_1)
1038 ret_entry->timestamp = ntohl(ret_entry->timestamp);
1039
1040 /* read in the version number */
1041 if (!fread(&vno, sizeof(vno), 1, KTFILEP(id))) {
1042 error = KRB5_KT_END;
1043 goto fail;
1044 }
1045 ret_entry->vno = (krb5_kvno)vno;
1046
1047 /* key type */
1048 if (!fread(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1049 error = KRB5_KT_END;
1050 goto fail;
1051 }
1052 if (KTVERSION(id) != KRB5_KT_VNO_1)
1053 enctype = ntohs(enctype);
1054 ret_entry->key.enctype = (krb5_enctype)enctype;
1055
1056 /* key contents */
1057 ret_entry->key.magic = KV5M_KEYBLOCK;
1058
1059 if (!fread(&count, sizeof(count), 1, KTFILEP(id))) {
1060 error = KRB5_KT_END;
1061 goto fail;
1062 }
1063 if (KTVERSION(id) != KRB5_KT_VNO_1)
1064 count = ntohs(count);
1065 if (!count || (count < 0)) {
1066 error = KRB5_KT_END;
1067 goto fail;
1068 }
1069
1070 u_count = count;
1071 ret_entry->key.length = u_count;
1072
1073 ret_entry->key.contents = (krb5_octet *)malloc(u_count);
1074 if (!ret_entry->key.contents) {
1075 error = ENOMEM;
1076 goto fail;
1077 }
1078 if (!fread(ret_entry->key.contents, sizeof(krb5_octet), count,
1079 KTFILEP(id))) {
1080 error = KRB5_KT_END;
1081 goto fail;
1082 }
1083
1084 /* Check for a 32-bit kvno extension if four or more bytes remain. */
1085 pos = ftell(KTFILEP(id));
1086 if (pos - start_pos + 4 <= size) {
1087 if (!fread(&vno32, sizeof(vno32), 1, KTFILEP(id))) {
1088 error = KRB5_KT_END;
1089 goto fail;
1090 }
1091 if (KTVERSION(id) != KRB5_KT_VNO_1)
1092 vno32 = ntohl(vno32);
1093 /* If the value is 0, the bytes are just zero-fill. */
1094 if (vno32)
1095 ret_entry->vno = vno32;
1096 }
1097
1098 /*
1099 * Reposition file pointer to the next inter-record length field.
1100 */
1101 if (fseek(KTFILEP(id), start_pos + size, SEEK_SET) == -1) {
1102 error = errno;
1103 goto fail;
1104 }
1105
1106 return 0;
1107 fail:
1108
1109 for (i = 0; i < ret_entry->principal->length; i++)
1110 free(ret_entry->principal->data[i].data);
1111 free(ret_entry->principal->data);
1112 ret_entry->principal->data = 0;
1113 free(ret_entry->principal);
1114 ret_entry->principal = 0;
1115 return error;
1116 }
1117
1118 static krb5_error_code
krb5_ktfileint_read_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entryp)1119 krb5_ktfileint_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entryp)
1120 {
1121 krb5_int32 delete_point;
1122
1123 return krb5_ktfileint_internal_read_entry(context, id, entryp, &delete_point);
1124 }
1125
1126 static krb5_error_code
krb5_ktfileint_write_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)1127 krb5_ktfileint_write_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
1128 {
1129 krb5_octet vno;
1130 krb5_data *princ;
1131 krb5_int16 count, size, enctype;
1132 krb5_error_code retval = 0;
1133 krb5_timestamp timestamp;
1134 krb5_int32 princ_type;
1135 krb5_int32 size_needed;
1136 krb5_int32 commit_point = -1;
1137 uint32_t vno32;
1138 int i;
1139
1140 KTCHECKLOCK(id);
1141 retval = krb5_ktfileint_size_entry(context, entry, &size_needed);
1142 if (retval)
1143 return retval;
1144 retval = krb5_ktfileint_find_slot(context, id, &size_needed, &commit_point);
1145 if (retval)
1146 return retval;
1147
1148 /* fseek to synchronise buffered I/O on the key table. */
1149 /* XXX Without the weird setbuf crock, can we get rid of this now? */
1150 if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1151 {
1152 return errno;
1153 }
1154
1155 if (KTVERSION(id) == KRB5_KT_VNO_1) {
1156 count = (krb5_int16)entry->principal->length + 1;
1157 } else {
1158 count = htons((u_short)entry->principal->length);
1159 }
1160
1161 if (!fwrite(&count, sizeof(count), 1, KTFILEP(id))) {
1162 abend:
1163 return KRB5_KT_IOERR;
1164 }
1165 size = entry->principal->realm.length;
1166 if (KTVERSION(id) != KRB5_KT_VNO_1)
1167 size = htons(size);
1168 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1169 goto abend;
1170 }
1171 if (!fwrite(entry->principal->realm.data, sizeof(char),
1172 entry->principal->realm.length, KTFILEP(id))) {
1173 goto abend;
1174 }
1175
1176 count = (krb5_int16)entry->principal->length;
1177 for (i = 0; i < count; i++) {
1178 princ = &entry->principal->data[i];
1179 size = princ->length;
1180 if (KTVERSION(id) != KRB5_KT_VNO_1)
1181 size = htons(size);
1182 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1183 goto abend;
1184 }
1185 if (!fwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
1186 goto abend;
1187 }
1188 }
1189
1190 /*
1191 * Write out the principal type
1192 */
1193 if (KTVERSION(id) != KRB5_KT_VNO_1) {
1194 princ_type = htonl(entry->principal->type);
1195 if (!fwrite(&princ_type, sizeof(princ_type), 1, KTFILEP(id))) {
1196 goto abend;
1197 }
1198 }
1199
1200 /*
1201 * Fill in the time of day the entry was written to the keytab.
1202 */
1203 if (krb5_timeofday(context, &entry->timestamp)) {
1204 entry->timestamp = 0;
1205 }
1206 if (KTVERSION(id) == KRB5_KT_VNO_1)
1207 timestamp = entry->timestamp;
1208 else
1209 timestamp = htonl(entry->timestamp);
1210 if (!fwrite(×tamp, sizeof(timestamp), 1, KTFILEP(id))) {
1211 goto abend;
1212 }
1213
1214 /* key version number */
1215 vno = (krb5_octet)entry->vno;
1216 if (!fwrite(&vno, sizeof(vno), 1, KTFILEP(id))) {
1217 goto abend;
1218 }
1219 /* key type */
1220 if (KTVERSION(id) == KRB5_KT_VNO_1)
1221 enctype = entry->key.enctype;
1222 else
1223 enctype = htons(entry->key.enctype);
1224 if (!fwrite(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1225 goto abend;
1226 }
1227 /* key length */
1228 if (KTVERSION(id) == KRB5_KT_VNO_1)
1229 size = entry->key.length;
1230 else
1231 size = htons(entry->key.length);
1232 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1233 goto abend;
1234 }
1235 if (!fwrite(entry->key.contents, sizeof(krb5_octet),
1236 entry->key.length, KTFILEP(id))) {
1237 goto abend;
1238 }
1239
1240 /* 32-bit key version number */
1241 vno32 = entry->vno;
1242 if (KTVERSION(id) != KRB5_KT_VNO_1)
1243 vno32 = htonl(vno32);
1244 if (!fwrite(&vno32, sizeof(vno32), 1, KTFILEP(id)))
1245 goto abend;
1246
1247 if (fflush(KTFILEP(id)))
1248 goto abend;
1249
1250 retval = k5_sync_disk_file(context, KTFILEP(id));
1251
1252 if (retval) {
1253 return retval;
1254 }
1255
1256 if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
1257 return errno;
1258 }
1259 if (KTVERSION(id) != KRB5_KT_VNO_1)
1260 size_needed = htonl(size_needed);
1261 if (!fwrite(&size_needed, sizeof(size_needed), 1, KTFILEP(id))) {
1262 goto abend;
1263 }
1264 if (fflush(KTFILEP(id)))
1265 goto abend;
1266 retval = k5_sync_disk_file(context, KTFILEP(id));
1267
1268 return retval;
1269 }
1270
1271 /*
1272 * Determine the size needed for a file entry for the given
1273 * keytab entry.
1274 */
1275 static krb5_error_code
krb5_ktfileint_size_entry(krb5_context context,krb5_keytab_entry * entry,krb5_int32 * size_needed)1276 krb5_ktfileint_size_entry(krb5_context context, krb5_keytab_entry *entry, krb5_int32 *size_needed)
1277 {
1278 krb5_int16 count;
1279 krb5_int32 total_size, i;
1280 krb5_error_code retval = 0;
1281
1282 count = (krb5_int16)entry->principal->length;
1283
1284 total_size = sizeof(count);
1285 total_size += entry->principal->realm.length + sizeof(krb5_int16);
1286
1287 for (i = 0; i < count; i++)
1288 total_size += entry->principal->data[i].length + sizeof(krb5_int16);
1289
1290 total_size += sizeof(entry->principal->type);
1291 total_size += sizeof(entry->timestamp);
1292 total_size += sizeof(krb5_octet);
1293 total_size += sizeof(krb5_int16);
1294 total_size += sizeof(krb5_int16) + entry->key.length;
1295 total_size += sizeof(uint32_t);
1296
1297 *size_needed = total_size;
1298 return retval;
1299 }
1300
1301 /*
1302 * Find and reserve a slot in the file for an entry of the needed size.
1303 * The commit point will be set to the position in the file where the
1304 * the length (sizeof(krb5_int32) bytes) of this node should be written
1305 * when committing the write. The file position left as a result of this
1306 * call is the position where the actual data should be written.
1307 *
1308 * The size_needed argument may be adjusted if we find a hole that is
1309 * larger than the size needed. (Recall that size_needed will be used
1310 * to commit the write, but that this field must indicate the size of the
1311 * block in the file rather than the size of the actual entry)
1312 */
1313 static krb5_error_code
krb5_ktfileint_find_slot(krb5_context context,krb5_keytab id,krb5_int32 * size_needed,krb5_int32 * commit_point_ptr)1314 krb5_ktfileint_find_slot(krb5_context context, krb5_keytab id, krb5_int32 *size_needed, krb5_int32 *commit_point_ptr)
1315 {
1316 FILE *fp;
1317 krb5_int32 size, zero_point, commit_point;
1318 krb5_kt_vno kt_vno;
1319
1320 KTCHECKLOCK(id);
1321 fp = KTFILEP(id);
1322 /* Skip over file version number. */
1323 if (fseek(fp, 0, SEEK_SET))
1324 return errno;
1325 if (!fread(&kt_vno, sizeof(kt_vno), 1, fp))
1326 return errno;
1327
1328 for (;;) {
1329 commit_point = ftell(fp);
1330 if (commit_point == -1)
1331 return errno;
1332 if (!fread(&size, sizeof(size), 1, fp)) {
1333 /* Hit the end of file, reserve this slot. */
1334 /* Necessary to avoid a later fseek failing on Solaris 10. */
1335 if (fseek(fp, 0, SEEK_CUR))
1336 return errno;
1337 /* htonl(0) is 0, so no need to worry about byte order */
1338 size = 0;
1339 if (!fwrite(&size, sizeof(size), 1, fp))
1340 return errno;
1341 break;
1342 }
1343
1344 if (KTVERSION(id) != KRB5_KT_VNO_1)
1345 size = ntohl(size);
1346
1347 if (size > 0) {
1348 /* Non-empty record; seek past it. */
1349 if (fseek(fp, size, SEEK_CUR))
1350 return errno;
1351 } else if (size < 0) {
1352 /* Empty record; use if it's big enough, seek past otherwise. */
1353 if (size == INT32_MIN) /* INT32_MIN inverts to itself. */
1354 return KRB5_KT_FORMAT;
1355 size = -size;
1356 if (size >= *size_needed) {
1357 *size_needed = size;
1358 break;
1359 } else {
1360 if (fseek(fp, size, SEEK_CUR))
1361 return errno;
1362 }
1363 } else {
1364 /* Empty record at end of file; use it. */
1365 /* Ensure the new record will be followed by another 0. */
1366 zero_point = ftell(fp);
1367 if (zero_point == -1)
1368 return errno;
1369 if (fseek(fp, *size_needed, SEEK_CUR))
1370 return errno;
1371 /* htonl(0) is 0, so no need to worry about byte order */
1372 if (!fwrite(&size, sizeof(size), 1, fp))
1373 return errno;
1374 if (fseek(fp, zero_point, SEEK_SET))
1375 return errno;
1376 break;
1377 }
1378 }
1379
1380 *commit_point_ptr = commit_point;
1381 return 0;
1382 }
1383 #endif /* LEAN_CLIENT */
1384