1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/keytab/kt_memory.c */
3 /*
4 * Copyright 2007 by Secure Endpoints Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person
7 * obtaining a copy of this software and associated documentation files
8 * (the "Software"), to deal in the Software without restriction,
9 * including without limitation the rights to use, copy, modify, merge,
10 * publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so,
12 * subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27 #include "k5-int.h"
28 #include "kt-int.h"
29 #include <stdio.h>
30
31 #ifndef LEAN_CLIENT
32
33 #define HEIMDAL_COMPATIBLE
34
35 /*
36 * Information needed by internal routines of the file-based ticket
37 * cache implementation.
38 */
39
40
41 /*
42 * Constants
43 */
44
45 /*
46 * Types
47 */
48 /* From krb5.h:
49 * typedef struct krb5_keytab_entry_st {
50 * krb5_magic magic;
51 * krb5_principal principal; principal of this key
52 * krb5_timestamp timestamp; time entry written to keytable
53 * krb5_kvno vno; key version number
54 * krb5_keyblock key; the secret key
55 *} krb5_keytab_entry;
56 */
57
58 /* Individual key entries within a table, in a linked list */
59 typedef struct _krb5_mkt_link {
60 struct _krb5_mkt_link *next;
61 krb5_keytab_entry *entry;
62 } krb5_mkt_link, *krb5_mkt_cursor;
63
64 /* Per-keytab data header */
65 typedef struct _krb5_mkt_data {
66 char *name; /* Name of the keytab */
67 k5_mutex_t lock; /* Thread-safety - all but link */
68 krb5_int32 refcount;
69 krb5_mkt_cursor link;
70 } krb5_mkt_data;
71
72 /* List of memory key tables */
73 typedef struct _krb5_mkt_list_node {
74 struct _krb5_mkt_list_node *next;
75 krb5_keytab keytab;
76 } krb5_mkt_list_node;
77
78 /* Iterator over memory key tables */
79 typedef struct _krb5_mkt_ptcursor_data {
80 struct _krb5_mkt_list_node *cur;
81 } krb5_mkt_ptcursor_data;
82
83 /*
84 * Globals
85 */
86 static krb5_mkt_list_node * krb5int_mkt_list = NULL;
87 static k5_mutex_t krb5int_mkt_mutex = K5_MUTEX_PARTIAL_INITIALIZER;
88
89 /*
90 * Macros
91 */
92 #define KTLOCK(id) k5_mutex_lock(&(((krb5_mkt_data *)(id)->data)->lock))
93 #define KTUNLOCK(id) k5_mutex_unlock(&(((krb5_mkt_data *)(id)->data)->lock))
94 #define KTCHECKLOCK(id) k5_mutex_assert_locked(&(((krb5_mkt_data *)(id)->data)->lock))
95
96 #define KTGLOCK k5_mutex_lock(&krb5int_mkt_mutex)
97 #define KTGUNLOCK k5_mutex_unlock(&krb5int_mkt_mutex)
98 #define KTGCHECKLOCK k5_mutex_assert_locked(&krb5int_mkt_mutex)
99
100 #define KTLINK(id) (((krb5_mkt_data *)(id)->data)->link)
101 #define KTREFCNT(id) (((krb5_mkt_data *)(id)->data)->refcount)
102 #define KTNAME(id) (((krb5_mkt_data *)(id)->data)->name)
103
104 extern const struct _krb5_kt_ops krb5_mkt_ops;
105
106 krb5_error_code KRB5_CALLCONV
107 krb5_mkt_resolve(krb5_context, const char *, krb5_keytab *);
108
109 krb5_error_code KRB5_CALLCONV
110 krb5_mkt_get_name(krb5_context, krb5_keytab, char *, unsigned int);
111
112 krb5_error_code KRB5_CALLCONV
113 krb5_mkt_close(krb5_context, krb5_keytab);
114
115 krb5_error_code KRB5_CALLCONV
116 krb5_mkt_get_entry(krb5_context, krb5_keytab, krb5_const_principal, krb5_kvno,
117 krb5_enctype, krb5_keytab_entry *);
118
119 krb5_error_code KRB5_CALLCONV
120 krb5_mkt_start_seq_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
121
122 krb5_error_code KRB5_CALLCONV
123 krb5_mkt_get_next(krb5_context, krb5_keytab, krb5_keytab_entry *,
124 krb5_kt_cursor *);
125
126 krb5_error_code KRB5_CALLCONV
127 krb5_mkt_end_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
128
129 /* routines to be included on extended version (write routines) */
130 krb5_error_code KRB5_CALLCONV
131 krb5_mkt_add(krb5_context, krb5_keytab, krb5_keytab_entry *);
132
133 krb5_error_code KRB5_CALLCONV
134 krb5_mkt_remove(krb5_context, krb5_keytab, krb5_keytab_entry *);
135
136 int
krb5int_mkt_initialize(void)137 krb5int_mkt_initialize(void)
138 {
139 return k5_mutex_finish_init(&krb5int_mkt_mutex);
140 }
141
142 void
krb5int_mkt_finalize(void)143 krb5int_mkt_finalize(void)
144 {
145 krb5_mkt_list_node *node, *next_node;
146 krb5_mkt_cursor cursor, next_cursor;
147
148 k5_mutex_destroy(&krb5int_mkt_mutex);
149
150 for (node = krb5int_mkt_list; node; node = next_node) {
151 next_node = node->next;
152
153 /* destroy the contents of node->keytab */
154 free(KTNAME(node->keytab));
155
156 /* free the keytab entries */
157 for (cursor = KTLINK(node->keytab); cursor; cursor = next_cursor) {
158 next_cursor = cursor->next;
159 /* the call to krb5_kt_free_entry uses a NULL in place of the
160 * krb5_context since we know that the context isn't used by
161 * krb5_kt_free_entry or krb5_free_principal. */
162 krb5_kt_free_entry(NULL, cursor->entry);
163 free(cursor->entry);
164 free(cursor);
165 }
166
167 /* destroy the lock */
168 k5_mutex_destroy(&(((krb5_mkt_data *)node->keytab->data)->lock));
169
170 /* free the private data */
171 free(node->keytab->data);
172
173 /* and the keytab */
174 free(node->keytab);
175
176 /* and finally the node */
177 free(node);
178 }
179 }
180
181 static krb5_error_code
create_list_node(const char * name,krb5_mkt_list_node ** listp)182 create_list_node(const char *name, krb5_mkt_list_node **listp)
183 {
184 krb5_mkt_list_node *list;
185 krb5_mkt_data *data = NULL;
186 krb5_error_code err;
187
188 *listp = NULL;
189
190 list = calloc(1, sizeof(krb5_mkt_list_node));
191 if (list == NULL) {
192 err = ENOMEM;
193 goto cleanup;
194 }
195
196 list->keytab = calloc(1, sizeof(struct _krb5_kt));
197 if (list->keytab == NULL) {
198 err = ENOMEM;
199 goto cleanup;
200 }
201 list->keytab->ops = &krb5_mkt_ops;
202
203 data = calloc(1, sizeof(krb5_mkt_data));
204 if (data == NULL) {
205 err = ENOMEM;
206 goto cleanup;
207 }
208 data->link = NULL;
209 data->refcount = 0;
210
211 data->name = strdup(name);
212 if (data->name == NULL) {
213 err = ENOMEM;
214 goto cleanup;
215 }
216
217 err = k5_mutex_init(&data->lock);
218 if (err)
219 goto cleanup;
220
221 list->keytab->data = data;
222 list->keytab->magic = KV5M_KEYTAB;
223 list->next = NULL;
224 *listp = list;
225 return 0;
226
227 cleanup:
228 /* data->lock was initialized last, so no need to destroy. */
229 if (data)
230 free(data->name);
231 free(data);
232 if (list)
233 free(list->keytab);
234 free(list);
235 return err;
236 }
237
238 /*
239 * This is an implementation specific resolver. It returns a keytab
240 * initialized with memory keytab routines.
241 */
242
243 krb5_error_code KRB5_CALLCONV
krb5_mkt_resolve(krb5_context context,const char * name,krb5_keytab * id)244 krb5_mkt_resolve(krb5_context context, const char *name, krb5_keytab *id)
245 {
246 krb5_mkt_list_node *list;
247 krb5_error_code err = 0;
248
249 *id = NULL;
250
251 /* First determine if a memory keytab of this name already exists */
252 KTGLOCK;
253
254 for (list = krb5int_mkt_list; list; list = list->next) {
255 if (strcmp(name,KTNAME(list->keytab)) == 0)
256 break;
257 }
258
259 if (!list) {
260 /* We will now create the new key table with the specified name.
261 * We do not drop the global lock, therefore the name will indeed
262 * be unique when we add it.
263 */
264 err = create_list_node(name, &list);
265 if (err)
266 goto done;
267 list->next = krb5int_mkt_list;
268 krb5int_mkt_list = list;
269 }
270
271 /* Increment the reference count on the keytab we found or created. */
272 KTLOCK(list->keytab);
273 KTREFCNT(list->keytab)++;
274 KTUNLOCK(list->keytab);
275 *id = list->keytab;
276 done:
277 KTGUNLOCK;
278 return err;
279 }
280
281
282 /*
283 * "Close" a memory-based keytab. This is effectively a no-op.
284 * We check to see if the keytab exists and that is about it.
285 * Closing a file keytab does not destroy the contents. Closing
286 * a memory keytab shouldn't either.
287 */
288
289 krb5_error_code KRB5_CALLCONV
krb5_mkt_close(krb5_context context,krb5_keytab id)290 krb5_mkt_close(krb5_context context, krb5_keytab id)
291 {
292 krb5_mkt_list_node **listp;
293 #ifdef HEIMDAL_COMPATIBLE
294 krb5_mkt_list_node *node;
295 krb5_mkt_data * data;
296 #endif
297 krb5_error_code err = 0;
298
299 /* First determine if a memory keytab of this name already exists */
300 KTGLOCK;
301
302 for (listp = &krb5int_mkt_list; *listp; listp = &((*listp)->next))
303 {
304 if (id == (*listp)->keytab) {
305 /* Found */
306 break;
307 }
308 }
309
310 if (*listp == NULL) {
311 /* The specified keytab could not be found */
312 err = KRB5_KT_NOTFOUND;
313 goto done;
314 }
315
316 /* reduce the refcount and return */
317 KTLOCK(id);
318 KTREFCNT(id)--;
319 KTUNLOCK(id);
320
321 #ifdef HEIMDAL_COMPATIBLE
322 /* In Heimdal if the refcount hits 0, the MEMORY keytab is
323 * destroyed since there is no krb5_kt_destroy function.
324 * There is no need to lock the entry while performing
325 * these operations as the refcount will be 0 and we are
326 * holding the global lock.
327 */
328 data = (krb5_mkt_data *)id->data;
329 if (data->refcount == 0) {
330 krb5_mkt_cursor cursor, next_cursor;
331
332 node = *listp;
333 *listp = node->next;
334
335 /* destroy the contents of node->keytab (aka id) */
336 free(data->name);
337
338 /* free the keytab entries */
339 for (cursor = KTLINK(node->keytab); cursor; cursor = next_cursor) {
340 next_cursor = cursor->next;
341
342 krb5_kt_free_entry(context, cursor->entry);
343 free(cursor->entry);
344 free(cursor);
345 }
346
347 /* destroy the lock */
348 k5_mutex_destroy(&(data->lock));
349
350 /* free the private data */
351 free(data);
352
353 /* and the keytab */
354 free(node->keytab);
355
356 /* and finally the node */
357 free(node);
358 }
359 #endif /* HEIMDAL_COMPATIBLE */
360
361 done:
362 KTGUNLOCK;
363 return(err);
364 }
365
366 /*
367 * This is the get_entry routine for the memory based keytab implementation.
368 * It either retrieves the entry or returns an error.
369 */
370
371 krb5_error_code KRB5_CALLCONV
krb5_mkt_get_entry(krb5_context context,krb5_keytab id,krb5_const_principal principal,krb5_kvno kvno,krb5_enctype enctype,krb5_keytab_entry * out_entry)372 krb5_mkt_get_entry(krb5_context context, krb5_keytab id,
373 krb5_const_principal principal, krb5_kvno kvno,
374 krb5_enctype enctype, krb5_keytab_entry *out_entry)
375 {
376 krb5_mkt_cursor cursor;
377 krb5_keytab_entry *entry, *match = NULL;
378 krb5_error_code err = 0;
379 int found_wrong_kvno = 0;
380 krb5_boolean similar = 0;
381
382 KTLOCK(id);
383
384 for (cursor = KTLINK(id); cursor && cursor->entry; cursor = cursor->next) {
385 entry = cursor->entry;
386
387 /* if the principal isn't the one requested, continue to the next. */
388
389 if (!krb5_principal_compare(context, principal, entry->principal))
390 continue;
391
392 /* if the enctype is not ignored and doesn't match,
393 and continue to the next */
394 if (enctype != IGNORE_ENCTYPE) {
395 if ((err = krb5_c_enctype_compare(context, enctype,
396 entry->key.enctype,
397 &similar))) {
398 /* we can't determine the enctype of the entry */
399 continue;
400 }
401
402 if (!similar)
403 continue;
404 }
405
406 if (kvno == IGNORE_VNO || entry->vno == IGNORE_VNO) {
407 if (match == NULL)
408 match = entry;
409 else if (entry->vno > match->vno)
410 match = entry;
411 } else {
412 if (entry->vno == kvno) {
413 match = entry;
414 break;
415 } else {
416 found_wrong_kvno++;
417 }
418 }
419 }
420
421 /* if we found an entry that matches, ... */
422 if (match) {
423 out_entry->magic = match->magic;
424 out_entry->timestamp = match->timestamp;
425 out_entry->vno = match->vno;
426 out_entry->key = match->key;
427 err = krb5_copy_keyblock_contents(context, &(match->key),
428 &(out_entry->key));
429 /*
430 * Coerce the enctype of the output keyblock in case we
431 * got an inexact match on the enctype.
432 */
433 if(enctype != IGNORE_ENCTYPE)
434 out_entry->key.enctype = enctype;
435 if(!err) {
436 err = krb5_copy_principal(context,
437 match->principal,
438 &(out_entry->principal));
439 }
440 } else {
441 if (!err)
442 err = found_wrong_kvno ? KRB5_KT_KVNONOTFOUND : KRB5_KT_NOTFOUND;
443 }
444
445 KTUNLOCK(id);
446 return(err);
447 }
448
449 /*
450 * Get the name of the memory-based keytab.
451 */
452
453 krb5_error_code KRB5_CALLCONV
krb5_mkt_get_name(krb5_context context,krb5_keytab id,char * name,unsigned int len)454 krb5_mkt_get_name(krb5_context context, krb5_keytab id, char *name, unsigned int len)
455 {
456 int result;
457
458 memset(name, 0, len);
459 result = snprintf(name, len, "%s:%s", id->ops->prefix, KTNAME(id));
460 if (SNPRINTF_OVERFLOW(result, len))
461 return(KRB5_KT_NAME_TOOLONG);
462 return(0);
463 }
464
465 /*
466 * krb5_mkt_start_seq_get()
467 */
468
469 krb5_error_code KRB5_CALLCONV
krb5_mkt_start_seq_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursorp)470 krb5_mkt_start_seq_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursorp)
471 {
472 KTLOCK(id);
473 *cursorp = (krb5_kt_cursor)KTLINK(id);
474 KTUNLOCK(id);
475
476 return(0);
477 }
478
479 /*
480 * krb5_mkt_get_next()
481 */
482
483 krb5_error_code KRB5_CALLCONV
krb5_mkt_get_next(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry,krb5_kt_cursor * cursor)484 krb5_mkt_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)
485 {
486 krb5_mkt_cursor mkt_cursor = (krb5_mkt_cursor)*cursor;
487 krb5_error_code err = 0;
488
489 KTLOCK(id);
490
491 if (mkt_cursor == NULL) {
492 KTUNLOCK(id);
493 return KRB5_KT_END;
494 }
495
496 entry->magic = mkt_cursor->entry->magic;
497 entry->timestamp = mkt_cursor->entry->timestamp;
498 entry->vno = mkt_cursor->entry->vno;
499 entry->key = mkt_cursor->entry->key;
500 err = krb5_copy_keyblock_contents(context, &(mkt_cursor->entry->key),
501 &(entry->key));
502 if (!err)
503 err = krb5_copy_principal(context, mkt_cursor->entry->principal,
504 &(entry->principal));
505 if (!err)
506 *cursor = (krb5_kt_cursor *)mkt_cursor->next;
507 KTUNLOCK(id);
508 return(err);
509 }
510
511 /*
512 * krb5_mkt_end_get()
513 */
514
515 krb5_error_code KRB5_CALLCONV
krb5_mkt_end_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursor)516 krb5_mkt_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
517 {
518 *cursor = NULL;
519 return(0);
520 }
521
522
523 /*
524 * krb5_mkt_add()
525 */
526
527 krb5_error_code KRB5_CALLCONV
krb5_mkt_add(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)528 krb5_mkt_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
529 {
530 krb5_error_code err = 0;
531 krb5_mkt_cursor cursor;
532
533 KTLOCK(id);
534
535 cursor = (krb5_mkt_cursor)malloc(sizeof(krb5_mkt_link));
536 if (cursor == NULL) {
537 err = ENOMEM;
538 goto done;
539 }
540 cursor->entry = (krb5_keytab_entry *)malloc(sizeof(krb5_keytab_entry));
541 if (cursor->entry == NULL) {
542 free(cursor);
543 err = ENOMEM;
544 goto done;
545 }
546 cursor->entry->magic = entry->magic;
547 cursor->entry->timestamp = entry->timestamp;
548 cursor->entry->vno = entry->vno;
549 err = krb5_copy_keyblock_contents(context, &(entry->key),
550 &(cursor->entry->key));
551 if (err) {
552 free(cursor->entry);
553 free(cursor);
554 goto done;
555 }
556
557 err = krb5_copy_principal(context, entry->principal, &(cursor->entry->principal));
558 if (err) {
559 krb5_free_keyblock_contents(context, &(cursor->entry->key));
560 free(cursor->entry);
561 free(cursor);
562 goto done;
563 }
564
565 if (KTLINK(id) == NULL) {
566 cursor->next = NULL;
567 KTLINK(id) = cursor;
568 } else {
569 cursor->next = KTLINK(id);
570 KTLINK(id) = cursor;
571 }
572
573 done:
574 KTUNLOCK(id);
575 return err;
576 }
577
578 /*
579 * krb5_mkt_remove()
580 */
581
582 krb5_error_code KRB5_CALLCONV
krb5_mkt_remove(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)583 krb5_mkt_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
584 {
585 krb5_mkt_cursor *pcursor, next;
586 krb5_error_code err = 0;
587
588 KTLOCK(id);
589
590 if ( KTLINK(id) == NULL ) {
591 err = KRB5_KT_NOTFOUND;
592 goto done;
593 }
594
595 for ( pcursor = &KTLINK(id); *pcursor; pcursor = &(*pcursor)->next ) {
596 if ( (*pcursor)->entry->vno == entry->vno &&
597 (*pcursor)->entry->key.enctype == entry->key.enctype &&
598 krb5_principal_compare(context, (*pcursor)->entry->principal, entry->principal))
599 break;
600 }
601
602 if (!*pcursor) {
603 err = KRB5_KT_NOTFOUND;
604 goto done;
605 }
606
607 krb5_kt_free_entry(context, (*pcursor)->entry);
608 free((*pcursor)->entry);
609 next = (*pcursor)->next;
610 free(*pcursor);
611 (*pcursor) = next;
612
613 done:
614 KTUNLOCK(id);
615 return err;
616 }
617
618
619 /*
620 * krb5_mkt_ops
621 */
622
623 const struct _krb5_kt_ops krb5_mkt_ops = {
624 0,
625 "MEMORY", /* Prefix -- this string should not appear anywhere else! */
626 krb5_mkt_resolve,
627 krb5_mkt_get_name,
628 krb5_mkt_close,
629 krb5_mkt_get_entry,
630 krb5_mkt_start_seq_get,
631 krb5_mkt_get_next,
632 krb5_mkt_end_get,
633 krb5_mkt_add,
634 krb5_mkt_remove
635 };
636
637 #endif /* LEAN_CLIENT */
638