xref: /freebsd/crypto/krb5/src/lib/krb5/keytab/kt_memory.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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