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 *cursorp = (krb5_kt_cursor)fileoff;
460 KTITERS(id)++;
461 if (KTITERS(id) == 0) {
462 /* Wrapped?! */
463 KTITERS(id)--;
464 KTUNLOCK(id);
465 k5_setmsg(context, KRB5_KT_IOERR, "Too many keytab iterators active");
466 return KRB5_KT_IOERR; /* XXX */
467 }
468 KTUNLOCK(id);
469
470 return 0;
471 }
472
473 /*
474 * krb5_ktfile_get_next()
475 */
476
477 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_get_next(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry,krb5_kt_cursor * cursor)478 krb5_ktfile_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)
479 {
480 long *fileoff = (long *)*cursor;
481 krb5_keytab_entry cur_entry;
482 krb5_error_code kerror;
483
484 KTLOCK(id);
485 if (KTFILEP(id) == NULL) {
486 KTUNLOCK(id);
487 return KRB5_KT_IOERR;
488 }
489 if (fseek(KTFILEP(id), *fileoff, 0) == -1) {
490 KTUNLOCK(id);
491 return KRB5_KT_END;
492 }
493 if ((kerror = krb5_ktfileint_read_entry(context, id, &cur_entry))) {
494 KTUNLOCK(id);
495 return kerror;
496 }
497 *fileoff = ftell(KTFILEP(id));
498 *entry = cur_entry;
499 KTUNLOCK(id);
500 return 0;
501 }
502
503 /*
504 * krb5_ktfile_end_get()
505 */
506
507 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_end_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursor)508 krb5_ktfile_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
509 {
510 krb5_error_code kerror;
511
512 free(*cursor);
513 KTLOCK(id);
514 KTITERS(id)--;
515 if (KTFILEP(id) != NULL && KTITERS(id) == 0)
516 kerror = krb5_ktfileint_close(context, id);
517 else
518 kerror = 0;
519 KTUNLOCK(id);
520 return kerror;
521 }
522
523 /*
524 * krb5_ktfile_add()
525 */
526
527 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_add(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)528 krb5_ktfile_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
529 {
530 krb5_error_code retval;
531
532 KTLOCK(id);
533 if (KTFILEP(id)) {
534 /* Iterator(s) active -- no changes. */
535 KTUNLOCK(id);
536 k5_setmsg(context, KRB5_KT_IOERR,
537 _("Cannot change keytab with keytab iterators active"));
538 return KRB5_KT_IOERR; /* XXX */
539 }
540 if ((retval = krb5_ktfileint_openw(context, id))) {
541 KTUNLOCK(id);
542 return retval;
543 }
544 if (fseek(KTFILEP(id), 0, 2) == -1) {
545 KTUNLOCK(id);
546 return KRB5_KT_END;
547 }
548 retval = krb5_ktfileint_write_entry(context, id, entry);
549 krb5_ktfileint_close(context, id);
550 KTUNLOCK(id);
551 return retval;
552 }
553
554 /*
555 * krb5_ktfile_remove()
556 */
557
558 static krb5_error_code KRB5_CALLCONV
krb5_ktfile_remove(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)559 krb5_ktfile_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
560 {
561 krb5_keytab_entry cur_entry;
562 krb5_error_code kerror;
563 krb5_int32 delete_point;
564
565 KTLOCK(id);
566 if (KTFILEP(id)) {
567 /* Iterator(s) active -- no changes. */
568 KTUNLOCK(id);
569 k5_setmsg(context, KRB5_KT_IOERR,
570 _("Cannot change keytab with keytab iterators active"));
571 return KRB5_KT_IOERR; /* XXX */
572 }
573
574 if ((kerror = krb5_ktfileint_openw(context, id))) {
575 KTUNLOCK(id);
576 return kerror;
577 }
578
579 /*
580 * For efficiency and simplicity, we'll use a while true that
581 * is exited with a break statement.
582 */
583 while (TRUE) {
584 if ((kerror = krb5_ktfileint_internal_read_entry(context, id,
585 &cur_entry,
586 &delete_point)))
587 break;
588
589 if ((entry->vno == cur_entry.vno) &&
590 (entry->key.enctype == cur_entry.key.enctype) &&
591 krb5_principal_compare(context, entry->principal, cur_entry.principal)) {
592 /* found a match */
593 krb5_kt_free_entry(context, &cur_entry);
594 break;
595 }
596 krb5_kt_free_entry(context, &cur_entry);
597 }
598
599 if (kerror == KRB5_KT_END)
600 kerror = KRB5_KT_NOTFOUND;
601
602 if (kerror) {
603 (void) krb5_ktfileint_close(context, id);
604 KTUNLOCK(id);
605 return kerror;
606 }
607
608 kerror = krb5_ktfileint_delete_entry(context, id, delete_point);
609
610 if (kerror) {
611 (void) krb5_ktfileint_close(context, id);
612 } else {
613 kerror = krb5_ktfileint_close(context, id);
614 }
615 KTUNLOCK(id);
616 return kerror;
617 }
618
619 /*
620 * krb5_ktf_ops
621 */
622
623 const struct _krb5_kt_ops krb5_ktf_ops = {
624 0,
625 "FILE", /* Prefix -- this string should not appear anywhere else! */
626 krb5_ktfile_resolve,
627 krb5_ktfile_get_name,
628 krb5_ktfile_close,
629 krb5_ktfile_get_entry,
630 krb5_ktfile_start_seq_get,
631 krb5_ktfile_get_next,
632 krb5_ktfile_end_get,
633 krb5_ktfile_add,
634 krb5_ktfile_remove
635 };
636
637 /*
638 * krb5_ktf_writable_ops -- this is the same as krb5_ktf_ops except for the
639 * prefix. WRFILE should no longer be needed, but is effectively aliased to
640 * FILE for compatibility.
641 */
642
643 const struct _krb5_kt_ops krb5_ktf_writable_ops = {
644 0,
645 "WRFILE", /* Prefix -- this string should not appear anywhere else! */
646 krb5_ktfile_resolve,
647 krb5_ktfile_get_name,
648 krb5_ktfile_close,
649 krb5_ktfile_get_entry,
650 krb5_ktfile_start_seq_get,
651 krb5_ktfile_get_next,
652 krb5_ktfile_end_get,
653 krb5_ktfile_add,
654 krb5_ktfile_remove
655 };
656
657 /*
658 * krb5_kt_dfl_ops
659 */
660
661 const krb5_kt_ops krb5_kt_dfl_ops = {
662 0,
663 "FILE", /* Prefix -- this string should not appear anywhere else! */
664 krb5_ktfile_resolve,
665 krb5_ktfile_get_name,
666 krb5_ktfile_close,
667 krb5_ktfile_get_entry,
668 krb5_ktfile_start_seq_get,
669 krb5_ktfile_get_next,
670 krb5_ktfile_end_get,
671 0,
672 0
673 };
674
675 /* Formerly lib/krb5/keytab/file/ktf_util.c */
676
677 /*
678 * This function contains utilities for the file based implementation of
679 * the keytab. There are no public functions in this file.
680 *
681 * This file is the only one that has knowledge of the format of a
682 * keytab file.
683 *
684 * The format is as follows:
685 *
686 * <file format vno>
687 * <record length>
688 * principal timestamp vno key
689 * <record length>
690 * principal timestamp vno key
691 * ....
692 *
693 * A length field (sizeof(krb5_int32)) exists between entries. When this
694 * length is positive it indicates an active entry, when negative a hole.
695 * The length indicates the size of the block in the file (this may be
696 * larger than the size of the next record, since we are using a first
697 * fit algorithm for re-using holes and the first fit may be larger than
698 * the entry we are writing). Another (compatible) implementation could
699 * break up holes when allocating them to smaller entries to minimize
700 * wasted space. (Such an implementation should also coalesce adjacent
701 * holes to reduce fragmentation). This implementation does neither.
702 *
703 * There are no separators between fields of an entry.
704 * A principal is a length-encoded array of length-encoded strings. The
705 * length is a krb5_int16 in each case. The specific format, then, is
706 * multiple entries concatenated with no separators. An entry has this
707 * exact format:
708 *
709 * sizeof(krb5_int16) bytes for number of components in the principal;
710 * then, each component listed in ordser.
711 * For each component, sizeof(krb5_int16) bytes for the number of bytes
712 * in the component, followed by the component.
713 * sizeof(krb5_int32) for the principal type (for KEYTAB V2 and higher)
714 * sizeof(krb5_int32) bytes for the timestamp
715 * sizeof(krb5_octet) bytes for the key version number
716 * sizeof(krb5_int16) bytes for the enctype
717 * sizeof(krb5_int16) bytes for the key length, followed by the key
718 */
719
720 #ifndef SEEK_SET
721 #define SEEK_SET 0
722 #define SEEK_CUR 1
723 #endif
724
725 typedef krb5_int16 krb5_kt_vno;
726
727 #define krb5_kt_default_vno ((krb5_kt_vno)KRB5_KT_DEFAULT_VNO)
728
729 static krb5_error_code
krb5_ktfileint_open(krb5_context context,krb5_keytab id,int mode)730 krb5_ktfileint_open(krb5_context context, krb5_keytab id, int mode)
731 {
732 krb5_error_code kerror;
733 krb5_kt_vno kt_vno;
734 int writevno = 0;
735
736 KTCHECKLOCK(id);
737 errno = 0;
738 KTFILEP(id) = fopen(KTFILENAME(id),
739 (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "rb+" : "rb");
740 if (!KTFILEP(id)) {
741 if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
742 /* try making it first time around */
743 k5_create_secure_file(context, KTFILENAME(id));
744 errno = 0;
745 KTFILEP(id) = fopen(KTFILENAME(id), "rb+");
746 if (!KTFILEP(id))
747 goto report_errno;
748 writevno = 1;
749 } else {
750 report_errno:
751 switch (errno) {
752 case 0:
753 /* XXX */
754 return EMFILE;
755 case ENOENT:
756 k5_setmsg(context, ENOENT,
757 _("Key table file '%s' not found"), KTFILENAME(id));
758 return ENOENT;
759 default:
760 return errno;
761 }
762 }
763 }
764 set_cloexec_file(KTFILEP(id));
765 if ((kerror = krb5_lock_file(context, fileno(KTFILEP(id)), mode))) {
766 (void) fclose(KTFILEP(id));
767 KTFILEP(id) = 0;
768 return kerror;
769 }
770 /* assume ANSI or BSD-style stdio */
771 setbuf(KTFILEP(id), KTFILEBUFP(id));
772
773 /* get the vno and verify it */
774 if (writevno) {
775 kt_vno = htons(krb5_kt_default_vno);
776 KTVERSION(id) = krb5_kt_default_vno;
777 if (!fwrite(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
778 kerror = errno;
779 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
780 (void) fclose(KTFILEP(id));
781 KTFILEP(id) = 0;
782 return kerror;
783 }
784 } else {
785 /* gotta verify it instead... */
786 if (!fread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
787 if (feof(KTFILEP(id)))
788 kerror = KRB5_KEYTAB_BADVNO;
789 else
790 kerror = errno;
791 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
792 (void) fclose(KTFILEP(id));
793 KTFILEP(id) = 0;
794 return kerror;
795 }
796 kt_vno = KTVERSION(id) = ntohs(kt_vno);
797 if ((kt_vno != KRB5_KT_VNO) &&
798 (kt_vno != KRB5_KT_VNO_1)) {
799 (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
800 (void) fclose(KTFILEP(id));
801 KTFILEP(id) = 0;
802 return KRB5_KEYTAB_BADVNO;
803 }
804 }
805 KTSTARTOFF(id) = ftell(KTFILEP(id));
806 return 0;
807 }
808
809 static krb5_error_code
krb5_ktfileint_openr(krb5_context context,krb5_keytab id)810 krb5_ktfileint_openr(krb5_context context, krb5_keytab id)
811 {
812 return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_SHARED);
813 }
814
815 static krb5_error_code
krb5_ktfileint_openw(krb5_context context,krb5_keytab id)816 krb5_ktfileint_openw(krb5_context context, krb5_keytab id)
817 {
818 return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_EXCLUSIVE);
819 }
820
821 static krb5_error_code
krb5_ktfileint_close(krb5_context context,krb5_keytab id)822 krb5_ktfileint_close(krb5_context context, krb5_keytab id)
823 {
824 krb5_error_code kerror;
825
826 KTCHECKLOCK(id);
827 if (!KTFILEP(id))
828 return 0;
829 kerror = krb5_unlock_file(context, fileno(KTFILEP(id)));
830 (void) fclose(KTFILEP(id));
831 KTFILEP(id) = 0;
832 return kerror;
833 }
834
835 static krb5_error_code
krb5_ktfileint_delete_entry(krb5_context context,krb5_keytab id,krb5_int32 delete_point)836 krb5_ktfileint_delete_entry(krb5_context context, krb5_keytab id, krb5_int32 delete_point)
837 {
838 krb5_int32 size;
839 krb5_int32 len;
840 char iobuf[BUFSIZ];
841
842 KTCHECKLOCK(id);
843 if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
844 return errno;
845 }
846 if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
847 return KRB5_KT_END;
848 }
849 if (KTVERSION(id) != KRB5_KT_VNO_1)
850 size = ntohl(size);
851
852 if (size > 0) {
853 krb5_int32 minus_size = -size;
854 if (KTVERSION(id) != KRB5_KT_VNO_1)
855 minus_size = htonl(minus_size);
856
857 if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
858 return errno;
859 }
860
861 if (!fwrite(&minus_size, sizeof(minus_size), 1, KTFILEP(id))) {
862 return KRB5_KT_IOERR;
863 }
864
865 if (size < BUFSIZ) {
866 len = size;
867 } else {
868 len = BUFSIZ;
869 }
870
871 memset(iobuf, 0, (size_t) len);
872 while (size > 0) {
873 if (!fwrite(iobuf, 1, (size_t) len, KTFILEP(id))) {
874 return KRB5_KT_IOERR;
875 }
876 size -= len;
877 if (size < len) {
878 len = size;
879 }
880 }
881
882 return k5_sync_disk_file(context, KTFILEP(id));
883 }
884
885 return 0;
886 }
887
888 static krb5_error_code
krb5_ktfileint_internal_read_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * ret_entry,krb5_int32 * delete_point)889 krb5_ktfileint_internal_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *ret_entry, krb5_int32 *delete_point)
890 {
891 krb5_octet vno;
892 krb5_int16 count;
893 unsigned int u_count, u_princ_size;
894 krb5_int16 enctype;
895 krb5_int16 princ_size;
896 int i;
897 krb5_int32 size;
898 krb5_int32 start_pos, pos;
899 krb5_error_code error;
900 char *tmpdata;
901 krb5_data *princ;
902 uint32_t vno32;
903
904 KTCHECKLOCK(id);
905 memset(ret_entry, 0, sizeof(krb5_keytab_entry));
906 ret_entry->magic = KV5M_KEYTAB_ENTRY;
907
908 /* fseek to synchronise buffered I/O on the key table. */
909
910 if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
911 {
912 return errno;
913 }
914
915 do {
916 *delete_point = ftell(KTFILEP(id));
917 if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
918 return KRB5_KT_END;
919 }
920 if (KTVERSION(id) != KRB5_KT_VNO_1)
921 size = ntohl(size);
922
923 if (size < 0) {
924 if (size == INT32_MIN) /* INT32_MIN inverts to itself. */
925 return KRB5_KT_FORMAT;
926 if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
927 return errno;
928 }
929 }
930 } while (size < 0);
931
932 if (size == 0) {
933 return KRB5_KT_END;
934 }
935
936 start_pos = ftell(KTFILEP(id));
937
938 /* deal with guts of parsing... */
939
940 /* first, int16 with #princ components */
941 if (!fread(&count, sizeof(count), 1, KTFILEP(id)))
942 return KRB5_KT_END;
943 if (KTVERSION(id) == KRB5_KT_VNO_1) {
944 count -= 1; /* V1 includes the realm in the count */
945 } else {
946 count = ntohs(count);
947 }
948 if (!count || (count < 0))
949 return KRB5_KT_END;
950 ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
951 if (!ret_entry->principal)
952 return ENOMEM;
953
954 u_count = count;
955 ret_entry->principal->magic = KV5M_PRINCIPAL;
956 ret_entry->principal->length = u_count;
957 ret_entry->principal->data = (krb5_data *)
958 calloc(u_count, sizeof(krb5_data));
959 if (!ret_entry->principal->data) {
960 free(ret_entry->principal);
961 ret_entry->principal = 0;
962 return ENOMEM;
963 }
964
965 /* Now, get the realm data */
966 if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
967 error = KRB5_KT_END;
968 goto fail;
969 }
970 if (KTVERSION(id) != KRB5_KT_VNO_1)
971 princ_size = ntohs(princ_size);
972 if (!princ_size || (princ_size < 0)) {
973 error = KRB5_KT_END;
974 goto fail;
975 }
976 u_princ_size = princ_size;
977
978 ret_entry->principal->realm.length = u_princ_size;
979 tmpdata = malloc(u_princ_size+1);
980 if (!tmpdata) {
981 error = ENOMEM;
982 goto fail;
983 }
984 if (fread(tmpdata, 1, u_princ_size, KTFILEP(id)) != (size_t) princ_size) {
985 free(tmpdata);
986 error = KRB5_KT_END;
987 goto fail;
988 }
989 tmpdata[princ_size] = 0; /* Some things might be expecting null */
990 /* termination... ``Be conservative in */
991 /* what you send out'' */
992 ret_entry->principal->realm.data = tmpdata;
993
994 for (i = 0; i < count; i++) {
995 princ = &ret_entry->principal->data[i];
996 if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
997 error = KRB5_KT_END;
998 goto fail;
999 }
1000 if (KTVERSION(id) != KRB5_KT_VNO_1)
1001 princ_size = ntohs(princ_size);
1002 if (!princ_size || (princ_size < 0)) {
1003 error = KRB5_KT_END;
1004 goto fail;
1005 }
1006
1007 u_princ_size = princ_size;
1008 princ->length = u_princ_size;
1009 princ->data = malloc(u_princ_size+1);
1010 if (!princ->data) {
1011 error = ENOMEM;
1012 goto fail;
1013 }
1014 if (!fread(princ->data, sizeof(char), u_princ_size, KTFILEP(id))) {
1015 error = KRB5_KT_END;
1016 goto fail;
1017 }
1018 princ->data[princ_size] = 0; /* Null terminate */
1019 }
1020
1021 /* read in the principal type, if we can get it */
1022 if (KTVERSION(id) != KRB5_KT_VNO_1) {
1023 if (!fread(&ret_entry->principal->type,
1024 sizeof(ret_entry->principal->type), 1, KTFILEP(id))) {
1025 error = KRB5_KT_END;
1026 goto fail;
1027 }
1028 ret_entry->principal->type = ntohl(ret_entry->principal->type);
1029 }
1030
1031 /* read in the timestamp */
1032 if (!fread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
1033 error = KRB5_KT_END;
1034 goto fail;
1035 }
1036 if (KTVERSION(id) != KRB5_KT_VNO_1)
1037 ret_entry->timestamp = ntohl(ret_entry->timestamp);
1038
1039 /* read in the version number */
1040 if (!fread(&vno, sizeof(vno), 1, KTFILEP(id))) {
1041 error = KRB5_KT_END;
1042 goto fail;
1043 }
1044 ret_entry->vno = (krb5_kvno)vno;
1045
1046 /* key type */
1047 if (!fread(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1048 error = KRB5_KT_END;
1049 goto fail;
1050 }
1051 if (KTVERSION(id) != KRB5_KT_VNO_1)
1052 enctype = ntohs(enctype);
1053 ret_entry->key.enctype = (krb5_enctype)enctype;
1054
1055 /* key contents */
1056 ret_entry->key.magic = KV5M_KEYBLOCK;
1057
1058 if (!fread(&count, sizeof(count), 1, KTFILEP(id))) {
1059 error = KRB5_KT_END;
1060 goto fail;
1061 }
1062 if (KTVERSION(id) != KRB5_KT_VNO_1)
1063 count = ntohs(count);
1064 if (!count || (count < 0)) {
1065 error = KRB5_KT_END;
1066 goto fail;
1067 }
1068
1069 u_count = count;
1070 ret_entry->key.length = u_count;
1071
1072 ret_entry->key.contents = (krb5_octet *)malloc(u_count);
1073 if (!ret_entry->key.contents) {
1074 error = ENOMEM;
1075 goto fail;
1076 }
1077 if (!fread(ret_entry->key.contents, sizeof(krb5_octet), count,
1078 KTFILEP(id))) {
1079 error = KRB5_KT_END;
1080 goto fail;
1081 }
1082
1083 /* Check for a 32-bit kvno extension if four or more bytes remain. */
1084 pos = ftell(KTFILEP(id));
1085 if (pos - start_pos + 4 <= size) {
1086 if (!fread(&vno32, sizeof(vno32), 1, KTFILEP(id))) {
1087 error = KRB5_KT_END;
1088 goto fail;
1089 }
1090 if (KTVERSION(id) != KRB5_KT_VNO_1)
1091 vno32 = ntohl(vno32);
1092 /* If the value is 0, the bytes are just zero-fill. */
1093 if (vno32)
1094 ret_entry->vno = vno32;
1095 }
1096
1097 /*
1098 * Reposition file pointer to the next inter-record length field.
1099 */
1100 if (fseek(KTFILEP(id), start_pos + size, SEEK_SET) == -1) {
1101 error = errno;
1102 goto fail;
1103 }
1104
1105 return 0;
1106 fail:
1107
1108 for (i = 0; i < ret_entry->principal->length; i++)
1109 free(ret_entry->principal->data[i].data);
1110 free(ret_entry->principal->data);
1111 ret_entry->principal->data = 0;
1112 free(ret_entry->principal);
1113 ret_entry->principal = 0;
1114 return error;
1115 }
1116
1117 static krb5_error_code
krb5_ktfileint_read_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entryp)1118 krb5_ktfileint_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entryp)
1119 {
1120 krb5_int32 delete_point;
1121
1122 return krb5_ktfileint_internal_read_entry(context, id, entryp, &delete_point);
1123 }
1124
1125 static krb5_error_code
krb5_ktfileint_write_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)1126 krb5_ktfileint_write_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
1127 {
1128 krb5_octet vno;
1129 krb5_data *princ;
1130 krb5_int16 count, size, enctype;
1131 krb5_error_code retval = 0;
1132 krb5_timestamp timestamp;
1133 krb5_int32 princ_type;
1134 krb5_int32 size_needed;
1135 krb5_int32 commit_point = -1;
1136 uint32_t vno32;
1137 int i;
1138
1139 KTCHECKLOCK(id);
1140 retval = krb5_ktfileint_size_entry(context, entry, &size_needed);
1141 if (retval)
1142 return retval;
1143 retval = krb5_ktfileint_find_slot(context, id, &size_needed, &commit_point);
1144 if (retval)
1145 return retval;
1146
1147 /* fseek to synchronise buffered I/O on the key table. */
1148 /* XXX Without the weird setbuf crock, can we get rid of this now? */
1149 if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
1150 {
1151 return errno;
1152 }
1153
1154 if (KTVERSION(id) == KRB5_KT_VNO_1) {
1155 count = (krb5_int16)entry->principal->length + 1;
1156 } else {
1157 count = htons((u_short)entry->principal->length);
1158 }
1159
1160 if (!fwrite(&count, sizeof(count), 1, KTFILEP(id))) {
1161 abend:
1162 return KRB5_KT_IOERR;
1163 }
1164 size = entry->principal->realm.length;
1165 if (KTVERSION(id) != KRB5_KT_VNO_1)
1166 size = htons(size);
1167 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1168 goto abend;
1169 }
1170 if (!fwrite(entry->principal->realm.data, sizeof(char),
1171 entry->principal->realm.length, KTFILEP(id))) {
1172 goto abend;
1173 }
1174
1175 count = (krb5_int16)entry->principal->length;
1176 for (i = 0; i < count; i++) {
1177 princ = &entry->principal->data[i];
1178 size = princ->length;
1179 if (KTVERSION(id) != KRB5_KT_VNO_1)
1180 size = htons(size);
1181 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1182 goto abend;
1183 }
1184 if (!fwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
1185 goto abend;
1186 }
1187 }
1188
1189 /*
1190 * Write out the principal type
1191 */
1192 if (KTVERSION(id) != KRB5_KT_VNO_1) {
1193 princ_type = htonl(entry->principal->type);
1194 if (!fwrite(&princ_type, sizeof(princ_type), 1, KTFILEP(id))) {
1195 goto abend;
1196 }
1197 }
1198
1199 /*
1200 * Fill in the time of day the entry was written to the keytab.
1201 */
1202 if (krb5_timeofday(context, &entry->timestamp)) {
1203 entry->timestamp = 0;
1204 }
1205 if (KTVERSION(id) == KRB5_KT_VNO_1)
1206 timestamp = entry->timestamp;
1207 else
1208 timestamp = htonl(entry->timestamp);
1209 if (!fwrite(×tamp, sizeof(timestamp), 1, KTFILEP(id))) {
1210 goto abend;
1211 }
1212
1213 /* key version number */
1214 vno = (krb5_octet)entry->vno;
1215 if (!fwrite(&vno, sizeof(vno), 1, KTFILEP(id))) {
1216 goto abend;
1217 }
1218 /* key type */
1219 if (KTVERSION(id) == KRB5_KT_VNO_1)
1220 enctype = entry->key.enctype;
1221 else
1222 enctype = htons(entry->key.enctype);
1223 if (!fwrite(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
1224 goto abend;
1225 }
1226 /* key length */
1227 if (KTVERSION(id) == KRB5_KT_VNO_1)
1228 size = entry->key.length;
1229 else
1230 size = htons(entry->key.length);
1231 if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
1232 goto abend;
1233 }
1234 if (!fwrite(entry->key.contents, sizeof(krb5_octet),
1235 entry->key.length, KTFILEP(id))) {
1236 goto abend;
1237 }
1238
1239 /* 32-bit key version number */
1240 vno32 = entry->vno;
1241 if (KTVERSION(id) != KRB5_KT_VNO_1)
1242 vno32 = htonl(vno32);
1243 if (!fwrite(&vno32, sizeof(vno32), 1, KTFILEP(id)))
1244 goto abend;
1245
1246 if (fflush(KTFILEP(id)))
1247 goto abend;
1248
1249 retval = k5_sync_disk_file(context, KTFILEP(id));
1250
1251 if (retval) {
1252 return retval;
1253 }
1254
1255 if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
1256 return errno;
1257 }
1258 if (KTVERSION(id) != KRB5_KT_VNO_1)
1259 size_needed = htonl(size_needed);
1260 if (!fwrite(&size_needed, sizeof(size_needed), 1, KTFILEP(id))) {
1261 goto abend;
1262 }
1263 if (fflush(KTFILEP(id)))
1264 goto abend;
1265 retval = k5_sync_disk_file(context, KTFILEP(id));
1266
1267 return retval;
1268 }
1269
1270 /*
1271 * Determine the size needed for a file entry for the given
1272 * keytab entry.
1273 */
1274 static krb5_error_code
krb5_ktfileint_size_entry(krb5_context context,krb5_keytab_entry * entry,krb5_int32 * size_needed)1275 krb5_ktfileint_size_entry(krb5_context context, krb5_keytab_entry *entry, krb5_int32 *size_needed)
1276 {
1277 krb5_int16 count;
1278 krb5_int32 total_size, i;
1279 krb5_error_code retval = 0;
1280
1281 count = (krb5_int16)entry->principal->length;
1282
1283 total_size = sizeof(count);
1284 total_size += entry->principal->realm.length + sizeof(krb5_int16);
1285
1286 for (i = 0; i < count; i++)
1287 total_size += entry->principal->data[i].length + sizeof(krb5_int16);
1288
1289 total_size += sizeof(entry->principal->type);
1290 total_size += sizeof(entry->timestamp);
1291 total_size += sizeof(krb5_octet);
1292 total_size += sizeof(krb5_int16);
1293 total_size += sizeof(krb5_int16) + entry->key.length;
1294 total_size += sizeof(uint32_t);
1295
1296 *size_needed = total_size;
1297 return retval;
1298 }
1299
1300 /*
1301 * Find and reserve a slot in the file for an entry of the needed size.
1302 * The commit point will be set to the position in the file where the
1303 * the length (sizeof(krb5_int32) bytes) of this node should be written
1304 * when committing the write. The file position left as a result of this
1305 * call is the position where the actual data should be written.
1306 *
1307 * The size_needed argument may be adjusted if we find a hole that is
1308 * larger than the size needed. (Recall that size_needed will be used
1309 * to commit the write, but that this field must indicate the size of the
1310 * block in the file rather than the size of the actual entry)
1311 */
1312 static krb5_error_code
krb5_ktfileint_find_slot(krb5_context context,krb5_keytab id,krb5_int32 * size_needed,krb5_int32 * commit_point_ptr)1313 krb5_ktfileint_find_slot(krb5_context context, krb5_keytab id, krb5_int32 *size_needed, krb5_int32 *commit_point_ptr)
1314 {
1315 FILE *fp;
1316 krb5_int32 size, zero_point, commit_point;
1317 krb5_kt_vno kt_vno;
1318
1319 KTCHECKLOCK(id);
1320 fp = KTFILEP(id);
1321 /* Skip over file version number. */
1322 if (fseek(fp, 0, SEEK_SET))
1323 return errno;
1324 if (!fread(&kt_vno, sizeof(kt_vno), 1, fp))
1325 return errno;
1326
1327 for (;;) {
1328 commit_point = ftell(fp);
1329 if (commit_point == -1)
1330 return errno;
1331 if (!fread(&size, sizeof(size), 1, fp)) {
1332 /* Hit the end of file, reserve this slot. */
1333 /* Necessary to avoid a later fseek failing on Solaris 10. */
1334 if (fseek(fp, 0, SEEK_CUR))
1335 return errno;
1336 /* htonl(0) is 0, so no need to worry about byte order */
1337 size = 0;
1338 if (!fwrite(&size, sizeof(size), 1, fp))
1339 return errno;
1340 break;
1341 }
1342
1343 if (KTVERSION(id) != KRB5_KT_VNO_1)
1344 size = ntohl(size);
1345
1346 if (size > 0) {
1347 /* Non-empty record; seek past it. */
1348 if (fseek(fp, size, SEEK_CUR))
1349 return errno;
1350 } else if (size < 0) {
1351 /* Empty record; use if it's big enough, seek past otherwise. */
1352 if (size == INT32_MIN) /* INT32_MIN inverts to itself. */
1353 return KRB5_KT_FORMAT;
1354 size = -size;
1355 if (size >= *size_needed) {
1356 *size_needed = size;
1357 break;
1358 } else {
1359 if (fseek(fp, size, SEEK_CUR))
1360 return errno;
1361 }
1362 } else {
1363 /* Empty record at end of file; use it. */
1364 /* Ensure the new record will be followed by another 0. */
1365 zero_point = ftell(fp);
1366 if (zero_point == -1)
1367 return errno;
1368 if (fseek(fp, *size_needed, SEEK_CUR))
1369 return errno;
1370 /* htonl(0) is 0, so no need to worry about byte order */
1371 if (!fwrite(&size, sizeof(size), 1, fp))
1372 return errno;
1373 if (fseek(fp, zero_point, SEEK_SET))
1374 return errno;
1375 break;
1376 }
1377 }
1378
1379 *commit_point_ptr = commit_point;
1380 return 0;
1381 }
1382 #endif /* LEAN_CLIENT */
1383