--- /dev/null
+/*\r
+ * lib/krb5/keytab/kt_memory.c\r
+ *\r
+ * Copyright 2007 by Secure Endpoints Inc.\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person\r
+ * obtaining a copy of this software and associated documentation files\r
+ * (the "Software"), to deal in the Software without restriction,\r
+ * including without limitation the rights to use, copy, modify, merge,\r
+ * publish, distribute, sublicense, and/or sell copies of the Software,\r
+ * and to permit persons to whom the Software is furnished to do so,\r
+ * subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be\r
+ * included in all copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\r
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\r
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
+ * SOFTWARE.\r
+ */\r
+\r
+#include "k5-int.h"\r
+#include <stdio.h>\r
+\r
+#define HEIMDAL_COMPATIBLE\r
+\r
+/*\r
+ * Information needed by internal routines of the file-based ticket\r
+ * cache implementation.\r
+ */\r
+\r
+\r
+/*\r
+ * Constants\r
+ */\r
+#define IGNORE_VNO 0\r
+#define IGNORE_ENCTYPE 0\r
+\r
+/* \r
+ * Types\r
+ */\r
+/* From krb5.h: \r
+ * typedef struct krb5_keytab_entry_st {\r
+ * krb5_magic magic;\r
+ * krb5_principal principal; principal of this key\r
+ * krb5_timestamp timestamp; time entry written to keytable \r
+ * krb5_kvno vno; key version number \r
+ * krb5_keyblock key; the secret key\r
+ *} krb5_keytab_entry;\r
+ */\r
+\r
+/* Individual key entries within a table, in a linked list */\r
+typedef struct _krb5_mkt_link {\r
+ struct _krb5_mkt_link *next;\r
+ krb5_keytab_entry *entry;\r
+} krb5_mkt_link, *krb5_mkt_cursor;\r
+\r
+/* Per-keytab data header */\r
+typedef struct _krb5_mkt_data {\r
+ char *name; /* Name of the keytab */\r
+ k5_mutex_t lock; /* Thread-safety - all but link */\r
+ krb5_int32 refcount; \r
+ krb5_mkt_cursor link;\r
+} krb5_mkt_data;\r
+\r
+/* List of memory key tables */\r
+typedef struct _krb5_mkt_list_node {\r
+ struct _krb5_mkt_list_node *next;\r
+ krb5_keytab keytab;\r
+} krb5_mkt_list_node;\r
+\r
+/* Iterator over memory key tables */\r
+typedef struct _krb5_mkt_ptcursor_data {\r
+ struct _krb5_mkt_list_node *cur;\r
+} krb5_mkt_ptcursor_data;\r
+\r
+/* \r
+ * Globals \r
+ */\r
+static krb5_mkt_list_node * krb5int_mkt_list = NULL;\r
+static k5_mutex_t krb5int_mkt_mutex = K5_MUTEX_PARTIAL_INITIALIZER;\r
+\r
+/*\r
+ * Macros\r
+ */\r
+#define KTLOCK(id) k5_mutex_lock(&(((krb5_mkt_data *)(id)->data)->lock))\r
+#define KTUNLOCK(id) k5_mutex_unlock(&(((krb5_mkt_data *)(id)->data)->lock))\r
+#define KTCHECKLOCK(id) k5_mutex_assert_locked(&(((krb5_mkt_data *)(id)->data)->lock))\r
+\r
+#define KTGLOCK k5_mutex_lock(&krb5int_mkt_mutex)\r
+#define KTGUNLOCK k5_mutex_unlock(&krb5int_mkt_mutex)\r
+#define KTGCHECKLOCK k5_mutex_assert_locked(&krb5int_mkt_mutex)\r
+\r
+#define KTLINK(id) (((krb5_mkt_data *)(id)->data)->link)\r
+#define KTREFCNT(id) (((krb5_mkt_data *)(id)->data)->refcount)\r
+#define KTNAME(id) (((krb5_mkt_data *)(id)->data)->name)\r
+\r
+extern const struct _krb5_kt_ops krb5_mkt_ops;\r
+\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_resolve \r
+ (krb5_context,\r
+ const char *,\r
+ krb5_keytab *);\r
+\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_get_name \r
+ (krb5_context,\r
+ krb5_keytab,\r
+ char *,\r
+ unsigned int);\r
+\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_close \r
+ (krb5_context,\r
+ krb5_keytab);\r
+\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_get_entry \r
+ (krb5_context,\r
+ krb5_keytab,\r
+ krb5_const_principal,\r
+ krb5_kvno,\r
+ krb5_enctype,\r
+ krb5_keytab_entry *);\r
+\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_start_seq_get \r
+ (krb5_context,\r
+ krb5_keytab,\r
+ krb5_kt_cursor *);\r
+\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_get_next \r
+ (krb5_context,\r
+ krb5_keytab,\r
+ krb5_keytab_entry *,\r
+ krb5_kt_cursor *);\r
+\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_end_get \r
+ (krb5_context,\r
+ krb5_keytab,\r
+ krb5_kt_cursor *);\r
+\r
+/* routines to be included on extended version (write routines) */\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_add \r
+ (krb5_context,\r
+ krb5_keytab,\r
+ krb5_keytab_entry *);\r
+\r
+krb5_error_code KRB5_CALLCONV krb5_mkt_remove \r
+ (krb5_context,\r
+ krb5_keytab,\r
+ krb5_keytab_entry *);\r
+\r
+int krb5int_mkt_initialize(void) {\r
+ return k5_mutex_finish_init(&krb5int_mkt_mutex);\r
+}\r
+\r
+void krb5int_mkt_finalize(void) {\r
+ krb5_mkt_list_node *node, *next_node;\r
+ krb5_mkt_cursor cursor, next_cursor;\r
+\r
+ k5_mutex_destroy(&krb5int_mkt_mutex);\r
+\r
+ for (node = krb5int_mkt_list; node; node = next_node) {\r
+ next_node = node->next;\r
+\r
+ /* destroy the contents of node->keytab */\r
+ krb5_xfree(KTNAME(node->keytab));\r
+\r
+ /* free the keytab entries */\r
+ for (cursor = KTLINK(node->keytab); cursor; cursor = next_cursor) {\r
+ next_cursor = cursor->next;\r
+ /* the call to krb5_kt_free_entry uses a NULL in place of the\r
+ * krb5_context since we know that the context isn't used by\r
+ * krb5_kt_free_entry or krb5_free_principal. */\r
+ krb5_kt_free_entry(NULL, cursor->entry);\r
+ krb5_xfree(cursor);\r
+ }\r
+\r
+ /* destroy the lock */\r
+ k5_mutex_destroy(&(((krb5_mkt_data *)node->keytab->data)->lock));\r
+\r
+ /* free the private data */\r
+ krb5_xfree(node->keytab->data);\r
+\r
+ /* and the keytab */\r
+ krb5_xfree(node->keytab);\r
+\r
+ /* and finally the node */\r
+ krb5_xfree(node);\r
+ }\r
+}\r
+/*\r
+ * This is an implementation specific resolver. It returns a keytab \r
+ * initialized with memory keytab routines.\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV \r
+krb5_mkt_resolve(krb5_context context, const char *name, krb5_keytab *id)\r
+{\r
+ krb5_mkt_data *data;\r
+ krb5_mkt_list_node *list;\r
+ krb5_error_code err = 0;\r
+\r
+ /* First determine if a memory keytab of this name already exists */\r
+ err = KTGLOCK;\r
+ if (err)\r
+ return(err);\r
+\r
+ for (list = krb5int_mkt_list; list; list = list->next)\r
+ {\r
+ if (strcmp(name,KTNAME(list->keytab)) == 0) {\r
+ /* Found */\r
+ *id = list->keytab;\r
+ goto done;\r
+ }\r
+ }\r
+\r
+ /* We will now create the new key table with the specified name.\r
+ * We do not drop the global lock, therefore the name will indeed\r
+ * be unique when we add it.\r
+ */\r
+\r
+ if ((list = (krb5_mkt_list_node *)malloc(sizeof(krb5_mkt_list_node))) == NULL) {\r
+ err = ENOMEM;\r
+ goto done;\r
+ }\r
+\r
+ if ((list->keytab = (krb5_keytab)malloc(sizeof(struct _krb5_kt))) == NULL) {\r
+ krb5_xfree(list);\r
+ err = ENOMEM;\r
+ goto done; \r
+ }\r
+\r
+ list->keytab->ops = &krb5_mkt_ops;\r
+ if ((data = (krb5_mkt_data *)malloc(sizeof(krb5_mkt_data))) == NULL) {\r
+ krb5_xfree(list->keytab);\r
+ krb5_xfree(list);\r
+ err = ENOMEM;\r
+ goto done;\r
+ }\r
+\r
+ err = k5_mutex_init(&data->lock);\r
+ if (err) {\r
+ krb5_xfree(data);\r
+ krb5_xfree(list->keytab);\r
+ krb5_xfree(list);\r
+ goto done;\r
+ }\r
+\r
+ if ((data->name = (char *)calloc(strlen(name) + 1, sizeof(char))) == NULL) {\r
+ k5_mutex_destroy(&data->lock);\r
+ krb5_xfree(data);\r
+ krb5_xfree(list->keytab);\r
+ krb5_xfree(list);\r
+ err = ENOMEM;\r
+ goto done;\r
+ }\r
+\r
+ (void) strcpy(data->name, name);\r
+\r
+ data->link = NULL;\r
+ data->refcount = 0;\r
+ list->keytab->data = (krb5_pointer)data;\r
+ list->keytab->magic = KV5M_KEYTAB;\r
+\r
+ list->next = krb5int_mkt_list;\r
+ krb5int_mkt_list = list;\r
+\r
+ *id = list->keytab;\r
+\r
+ done:\r
+ err = KTLOCK(*id);\r
+ if (err) {\r
+ k5_mutex_destroy(&data->lock);\r
+ krb5_xfree(data->name);\r
+ krb5_xfree(data);\r
+ krb5_xfree(list->keytab);\r
+ krb5_xfree(list);\r
+ } else {\r
+ KTREFCNT(*id)++;\r
+ KTUNLOCK(*id);\r
+ }\r
+\r
+ KTGUNLOCK;\r
+ return(err);\r
+}\r
+\r
+\r
+/*\r
+ * "Close" a memory-based keytab. This is effectively a no-op.\r
+ * We check to see if the keytab exists and that is about it.\r
+ * Closing a file keytab does not destroy the contents. Closing\r
+ * a memory keytab shouldn't either.\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV \r
+krb5_mkt_close(krb5_context context, krb5_keytab id)\r
+{\r
+ krb5_mkt_list_node **listp;\r
+#ifdef HEIMDAL_COMPATIBLE\r
+ krb5_mkt_list_node *node;\r
+ krb5_mkt_data * data;\r
+#endif\r
+ krb5_error_code err = 0;\r
+\r
+ /* First determine if a memory keytab of this name already exists */\r
+ err = KTGLOCK;\r
+ if (err)\r
+ return(err);\r
+ \r
+ for (listp = &krb5int_mkt_list; *listp; listp = &((*listp)->next))\r
+ {\r
+ if (id == (*listp)->keytab) {\r
+ /* Found */\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (*listp == NULL) {\r
+ /* The specified keytab could not be found */\r
+ err = KRB5_KT_NOTFOUND;\r
+ goto done;\r
+ }\r
+\r
+ /* reduce the refcount and return */\r
+ err = KTLOCK(id);\r
+ if (err)\r
+ goto done;\r
+\r
+ KTREFCNT(id)--;\r
+ KTUNLOCK(id);\r
+\r
+#ifdef HEIMDAL_COMPATIBLE\r
+ /* In Heimdal if the refcount hits 0, the MEMORY keytab is \r
+ * destroyed since there is no krb5_kt_destroy function.\r
+ * There is no need to lock the entry while performing \r
+ * these operations as the refcount will be 0 and we are\r
+ * holding the global lock.\r
+ */\r
+ data = (krb5_mkt_data *)id->data;\r
+ if (data->refcount == 0) {\r
+ krb5_mkt_cursor cursor, next_cursor;\r
+\r
+ node = *listp;\r
+ *listp = node->next;\r
+\r
+ /* destroy the contents of node->keytab (aka id) */\r
+ krb5_xfree(data->name);\r
+\r
+ /* free the keytab entries */\r
+ for (cursor = KTLINK(node->keytab); cursor; cursor = next_cursor) {\r
+ next_cursor = cursor->next;\r
+\r
+ krb5_kt_free_entry(context, cursor->entry);\r
+ krb5_xfree(cursor);\r
+ }\r
+\r
+ /* destroy the lock */\r
+ k5_mutex_destroy(&(data->lock));\r
+\r
+ /* free the private data */\r
+ krb5_xfree(data);\r
+\r
+ /* and the keytab */\r
+ krb5_xfree(node->keytab);\r
+\r
+ /* and finally the node */\r
+ krb5_xfree(node);\r
+ }\r
+#endif /* HEIMDAL_COMPATIBLE */\r
+\r
+ done:\r
+ KTGUNLOCK;\r
+ return(err);\r
+}\r
+\r
+/*\r
+ * This is the get_entry routine for the memory based keytab implementation.\r
+ * It either retrieves the entry or returns an error.\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV\r
+krb5_mkt_get_entry(krb5_context context, krb5_keytab id,\r
+ krb5_const_principal principal, krb5_kvno kvno,\r
+ krb5_enctype enctype, krb5_keytab_entry *out_entry)\r
+{\r
+ krb5_mkt_cursor cursor;\r
+ krb5_keytab_entry *entry, *match = NULL;\r
+ krb5_error_code err = 0;\r
+ int found_wrong_kvno = 0;\r
+ krb5_boolean similar = 0;\r
+\r
+ err = KTLOCK(id);\r
+ if (err)\r
+ return err;\r
+\r
+ for (cursor = KTLINK(id); cursor && cursor->entry; cursor = cursor->next) {\r
+ entry = cursor->entry;\r
+\r
+ /* if the principal isn't the one requested, free new_entry\r
+ and continue to the next. */\r
+\r
+ if (!krb5_principal_compare(context, principal, entry->principal))\r
+ continue;\r
+\r
+ /* if the enctype is not ignored and doesn't match, free new_entry\r
+ and continue to the next */\r
+ if (enctype != IGNORE_ENCTYPE) {\r
+ if ((err = krb5_c_enctype_compare(context, enctype, \r
+ entry->key.enctype,\r
+ &similar))) {\r
+ /* we can't determine the enctype of the entry */\r
+ continue;\r
+ }\r
+\r
+ if (!similar)\r
+ continue;\r
+ }\r
+\r
+ if (kvno == IGNORE_VNO) {\r
+ if (match == NULL)\r
+ match = entry;\r
+ else if (entry->vno > match->vno)\r
+ match = entry;\r
+ } else {\r
+ if (entry->vno == kvno) {\r
+ match = entry;\r
+ break;\r
+ } else {\r
+ found_wrong_kvno++;\r
+ }\r
+ }\r
+ }\r
+\r
+ /* if we found an entry that matches, ... */\r
+ if (match) { \r
+ out_entry->magic = entry->magic;\r
+ out_entry->timestamp = entry->timestamp;\r
+ out_entry->vno = entry->vno;\r
+ out_entry->key = entry->key; \r
+ /*\r
+ * Coerce the enctype of the output keyblock in case we\r
+ * got an inexact match on the enctype.\r
+ */\r
+ out_entry->key.enctype = enctype;\r
+ err = krb5_copy_principal(context, entry->principal, &(out_entry->principal));\r
+ } else {\r
+ if (!err)\r
+ err = found_wrong_kvno ? KRB5_KT_KVNONOTFOUND : KRB5_KT_NOTFOUND;\r
+ }\r
+\r
+ KTUNLOCK(id);\r
+ return(err);\r
+}\r
+\r
+/*\r
+ * Get the name of the memory-based keytab.\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV\r
+krb5_mkt_get_name(krb5_context context, krb5_keytab id, char *name, unsigned int len)\r
+{\r
+ memset(name, 0, len);\r
+\r
+ if (len < strlen(id->ops->prefix)+2)\r
+ return(KRB5_KT_NAME_TOOLONG);\r
+ strcpy(name, id->ops->prefix);\r
+ name += strlen(id->ops->prefix);\r
+ name[0] = ':';\r
+ name++;\r
+ len -= strlen(id->ops->prefix)+1;\r
+\r
+ if (len < strlen(KTNAME(id)+1))\r
+ return(KRB5_KT_NAME_TOOLONG);\r
+ strcpy(name, KTNAME(id));\r
+ /* strcpy will NUL-terminate the destination */\r
+\r
+ return(0);\r
+}\r
+\r
+/*\r
+ * krb5_mkt_start_seq_get()\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV\r
+krb5_mkt_start_seq_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursorp)\r
+{\r
+ krb5_error_code err = 0;\r
+\r
+ err = KTLOCK(id);\r
+ if (err)\r
+ return(err);\r
+\r
+ *cursorp = (krb5_kt_cursor)KTLINK(id);\r
+ KTUNLOCK(id);\r
+\r
+ return(0);\r
+}\r
+\r
+/*\r
+ * krb5_mkt_get_next()\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV \r
+krb5_mkt_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)\r
+{\r
+ krb5_mkt_cursor mkt_cursor = (krb5_mkt_cursor)*cursor;\r
+ krb5_error_code err = 0;\r
+\r
+ err = KTLOCK(id);\r
+ if (err)\r
+ return err;\r
+\r
+ if (mkt_cursor == NULL) {\r
+ KTUNLOCK(id);\r
+ return KRB5_KT_END;\r
+ }\r
+\r
+ entry->magic = mkt_cursor->entry->magic;\r
+ entry->timestamp = mkt_cursor->entry->timestamp;\r
+ entry->vno = mkt_cursor->entry->vno;\r
+ entry->key = mkt_cursor->entry->key; \r
+ err = krb5_copy_principal(context, mkt_cursor->entry->principal, &(entry->principal));\r
+ if (!err)\r
+ *cursor = (krb5_kt_cursor *)mkt_cursor->next;\r
+ KTUNLOCK(id);\r
+ return(err);\r
+}\r
+\r
+/*\r
+ * krb5_mkt_end_get()\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV \r
+krb5_mkt_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)\r
+{\r
+ *cursor = NULL;\r
+ return(0);\r
+}\r
+\r
+\r
+/*\r
+ * krb5_mkt_add()\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV \r
+krb5_mkt_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)\r
+{\r
+ krb5_error_code err = 0;\r
+ krb5_mkt_cursor cursor;\r
+\r
+ err = KTLOCK(id);\r
+ if (err)\r
+ return err;\r
+\r
+ cursor = (krb5_mkt_cursor)malloc(sizeof(krb5_mkt_link));\r
+ if (cursor == NULL) {\r
+ err = ENOMEM;\r
+ goto done;\r
+ }\r
+ cursor->entry = (krb5_keytab_entry *)malloc(sizeof(krb5_keytab_entry));\r
+ if (cursor->entry == NULL) {\r
+ krb5_xfree(cursor);\r
+ err = ENOMEM;\r
+ goto done;\r
+ }\r
+ cursor->entry->magic = entry->magic;\r
+ cursor->entry->timestamp = entry->timestamp;\r
+ cursor->entry->vno = entry->vno;\r
+ cursor->entry->key = entry->key; \r
+ err = krb5_copy_principal(context, entry->principal, &(cursor->entry->principal));\r
+ if (err) {\r
+ krb5_xfree(cursor->entry);\r
+ krb5_xfree(cursor);\r
+ goto done;\r
+ }\r
+\r
+ if (KTLINK(id) == NULL) {\r
+ cursor->next = NULL;\r
+ KTLINK(id) = cursor;\r
+ } else {\r
+ cursor->next = KTLINK(id);\r
+ KTLINK(id) = cursor;\r
+ }\r
+\r
+ done:\r
+ KTUNLOCK(id);\r
+ return err;\r
+}\r
+\r
+/*\r
+ * krb5_mkt_remove()\r
+ */\r
+\r
+krb5_error_code KRB5_CALLCONV \r
+krb5_mkt_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)\r
+{\r
+ krb5_mkt_cursor *pcursor, next;\r
+ krb5_error_code err = 0;\r
+\r
+ err = KTLOCK(id);\r
+ if (err)\r
+ return err;\r
+\r
+ if ( KTLINK(id) == NULL ) {\r
+ err = KRB5_KT_NOTFOUND;\r
+ goto done;\r
+ }\r
+ \r
+ for ( pcursor = &KTLINK(id); *pcursor; pcursor = &(*pcursor)->next ) {\r
+ if ( (*pcursor)->entry->vno == entry->vno &&\r
+ (*pcursor)->entry->key.enctype == entry->key.enctype &&\r
+ krb5_principal_compare(context, (*pcursor)->entry->principal, entry->principal))\r
+ break;\r
+ }\r
+\r
+ if (!*pcursor) {\r
+ err = KRB5_KT_NOTFOUND;\r
+ goto done;\r
+ }\r
+\r
+ krb5_kt_free_entry(context, (*pcursor)->entry);\r
+ krb5_xfree((*pcursor)->entry);\r
+ next = (*pcursor)->next;\r
+ krb5_xfree(*pcursor);\r
+ (*pcursor) = next;\r
+\r
+ done:\r
+ KTUNLOCK(id);\r
+ return err;\r
+}\r
+\r
+\r
+/*\r
+ * krb5_mkt_ops\r
+ */\r
+\r
+const struct _krb5_kt_ops krb5_mkt_ops = {\r
+ 0,\r
+ "MEMORY", /* Prefix -- this string should not appear anywhere else! */\r
+ krb5_mkt_resolve,\r
+ krb5_mkt_get_name, \r
+ krb5_mkt_close,\r
+ krb5_mkt_get_entry,\r
+ krb5_mkt_start_seq_get,\r
+ krb5_mkt_get_next,\r
+ krb5_mkt_end_get,\r
+ krb5_mkt_add,\r
+ krb5_mkt_remove,\r
+ NULL\r
+};\r
+\r