diff options
Diffstat (limited to 'source4/heimdal/lib/hdb')
-rw-r--r-- | source4/heimdal/lib/hdb/db.c | 337 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/dbinfo.c | 266 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/ext.c | 421 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb.asn1 | 127 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb.c | 413 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb.h | 144 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb_err.et | 28 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb_locl.h | 70 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/keys.c | 401 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/keytab.c | 272 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/mkey.c | 604 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/ndbm.c | 370 |
12 files changed, 3453 insertions, 0 deletions
diff --git a/source4/heimdal/lib/hdb/db.c b/source4/heimdal/lib/hdb/db.c new file mode 100644 index 0000000000..a598e9e1a4 --- /dev/null +++ b/source4/heimdal/lib/hdb/db.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hdb_locl.h" + +RCSID("$Id$"); + +#if HAVE_DB1 + +#if defined(HAVE_DB_185_H) +#include <db_185.h> +#elif defined(HAVE_DB_H) +#include <db.h> +#endif + +static krb5_error_code +DB_close(krb5_context context, HDB *db) +{ + DB *d = (DB*)db->hdb_db; + (*d->close)(d); + return 0; +} + +static krb5_error_code +DB_destroy(krb5_context context, HDB *db) +{ + krb5_error_code ret; + + ret = hdb_clear_master_key (context, db); + free(db->hdb_name); + free(db); + return ret; +} + +static krb5_error_code +DB_lock(krb5_context context, HDB *db, int operation) +{ + DB *d = (DB*)db->hdb_db; + int fd = (*d->fd)(d); + if(fd < 0) { + krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB, + "Can't lock database: %s", db->hdb_name); + return HDB_ERR_CANT_LOCK_DB; + } + return hdb_lock(fd, operation); +} + +static krb5_error_code +DB_unlock(krb5_context context, HDB *db) +{ + DB *d = (DB*)db->hdb_db; + int fd = (*d->fd)(d); + if(fd < 0) { + krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB, + "Can't unlock database: %s", db->hdb_name); + return HDB_ERR_CANT_LOCK_DB; + } + return hdb_unlock(fd); +} + + +static krb5_error_code +DB_seq(krb5_context context, HDB *db, + unsigned flags, hdb_entry_ex *entry, int flag) +{ + DB *d = (DB*)db->hdb_db; + DBT key, value; + krb5_data key_data, data; + int code; + + code = db->hdb_lock(context, db, HDB_RLOCK); + if(code == -1) { + krb5_set_error_message(context, HDB_ERR_DB_INUSE, "Database %s in use", db->hdb_name); + return HDB_ERR_DB_INUSE; + } + code = (*d->seq)(d, &key, &value, flag); + db->hdb_unlock(context, db); /* XXX check value */ + if(code == -1) { + code = errno; + krb5_set_error_message(context, code, "Database %s seq error: %s", + db->hdb_name, strerror(code)); + return code; + } + if(code == 1) { + krb5_clear_error_string(context); + return HDB_ERR_NOENTRY; + } + + key_data.data = key.data; + key_data.length = key.size; + data.data = value.data; + data.length = value.size; + memset(entry, 0, sizeof(*entry)); + if (hdb_value2entry(context, &data, &entry->entry)) + return DB_seq(context, db, flags, entry, R_NEXT); + if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) { + code = hdb_unseal_keys (context, db, &entry->entry); + if (code) + hdb_free_entry (context, entry); + } + if (code == 0 && entry->entry.principal == NULL) { + entry->entry.principal = malloc(sizeof(*entry->entry.principal)); + if (entry->entry.principal == NULL) { + code = ENOMEM; + krb5_set_error_message(context, code, "malloc: out of memory"); + hdb_free_entry (context, entry); + } else { + hdb_key2principal(context, &key_data, entry->entry.principal); + } + } + return code; +} + + +static krb5_error_code +DB_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry) +{ + return DB_seq(context, db, flags, entry, R_FIRST); +} + + +static krb5_error_code +DB_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry) +{ + return DB_seq(context, db, flags, entry, R_NEXT); +} + +static krb5_error_code +DB_rename(krb5_context context, HDB *db, const char *new_name) +{ + int ret; + char *old, *new; + + asprintf(&old, "%s.db", db->hdb_name); + asprintf(&new, "%s.db", new_name); + ret = rename(old, new); + free(old); + free(new); + if(ret) + return errno; + + free(db->hdb_name); + db->hdb_name = strdup(new_name); + return 0; +} + +static krb5_error_code +DB__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply) +{ + DB *d = (DB*)db->hdb_db; + DBT k, v; + int code; + + k.data = key.data; + k.size = key.length; + code = db->hdb_lock(context, db, HDB_RLOCK); + if(code) + return code; + code = (*d->get)(d, &k, &v, 0); + db->hdb_unlock(context, db); + if(code < 0) { + code = errno; + krb5_set_error_message(context, code, "Database %s get error: %s", + db->hdb_name, strerror(code)); + return code; + } + if(code == 1) { + krb5_clear_error_string(context); + return HDB_ERR_NOENTRY; + } + + krb5_data_copy(reply, v.data, v.size); + return 0; +} + +static krb5_error_code +DB__put(krb5_context context, HDB *db, int replace, + krb5_data key, krb5_data value) +{ + DB *d = (DB*)db->hdb_db; + DBT k, v; + int code; + + k.data = key.data; + k.size = key.length; + v.data = value.data; + v.size = value.length; + code = db->hdb_lock(context, db, HDB_WLOCK); + if(code) + return code; + code = (*d->put)(d, &k, &v, replace ? 0 : R_NOOVERWRITE); + db->hdb_unlock(context, db); + if(code < 0) { + code = errno; + krb5_set_error_message(context, code, "Database %s put error: %s", + db->hdb_name, strerror(code)); + return code; + } + if(code == 1) { + krb5_clear_error_string(context); + return HDB_ERR_EXISTS; + } + return 0; +} + +static krb5_error_code +DB__del(krb5_context context, HDB *db, krb5_data key) +{ + DB *d = (DB*)db->hdb_db; + DBT k; + krb5_error_code code; + k.data = key.data; + k.size = key.length; + code = db->hdb_lock(context, db, HDB_WLOCK); + if(code) + return code; + code = (*d->del)(d, &k, 0); + db->hdb_unlock(context, db); + if(code == 1) { + code = errno; + krb5_set_error_message(context, code, "Database %s put error: %s", + db->hdb_name, strerror(code)); + return code; + } + if(code < 0) + return errno; + return 0; +} + +static krb5_error_code +DB_open(krb5_context context, HDB *db, int flags, mode_t mode) +{ + char *fn; + krb5_error_code ret; + + asprintf(&fn, "%s.db", db->hdb_name); + if (fn == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + db->hdb_db = dbopen(fn, flags, mode, DB_BTREE, NULL); + free(fn); + /* try to open without .db extension */ + if(db->hdb_db == NULL && errno == ENOENT) + db->hdb_db = dbopen(db->hdb_name, flags, mode, DB_BTREE, NULL); + if(db->hdb_db == NULL) { + ret = errno; + krb5_set_error_message(context, ret, "dbopen (%s): %s", + db->hdb_name, strerror(ret)); + return ret; + } + if((flags & O_ACCMODE) == O_RDONLY) + ret = hdb_check_db_format(context, db); + else + ret = hdb_init_db(context, db); + if(ret == HDB_ERR_NOENTRY) { + krb5_clear_error_string(context); + return 0; + } + if (ret) { + DB_close(context, db); + krb5_set_error_message(context, ret, "hdb_open: failed %s database %s", + (flags & O_ACCMODE) == O_RDONLY ? + "checking format of" : "initialize", + db->hdb_name); + } + return ret; +} + +krb5_error_code +hdb_db_create(krb5_context context, HDB **db, + const char *filename) +{ + *db = calloc(1, sizeof(**db)); + if (*db == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + + (*db)->hdb_db = NULL; + (*db)->hdb_name = strdup(filename); + if ((*db)->hdb_name == NULL) { + free(*db); + *db = NULL; + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + (*db)->hdb_master_key_set = 0; + (*db)->hdb_openp = 0; + (*db)->hdb_open = DB_open; + (*db)->hdb_close = DB_close; + (*db)->hdb_fetch = _hdb_fetch; + (*db)->hdb_store = _hdb_store; + (*db)->hdb_remove = _hdb_remove; + (*db)->hdb_firstkey = DB_firstkey; + (*db)->hdb_nextkey= DB_nextkey; + (*db)->hdb_lock = DB_lock; + (*db)->hdb_unlock = DB_unlock; + (*db)->hdb_rename = DB_rename; + (*db)->hdb__get = DB__get; + (*db)->hdb__put = DB__put; + (*db)->hdb__del = DB__del; + (*db)->hdb_destroy = DB_destroy; + return 0; +} + +#endif /* HAVE_DB1 */ diff --git a/source4/heimdal/lib/hdb/dbinfo.c b/source4/heimdal/lib/hdb/dbinfo.c new file mode 100644 index 0000000000..67b9fc6ecf --- /dev/null +++ b/source4/heimdal/lib/hdb/dbinfo.c @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hdb_locl.h" + +RCSID("$Id$"); + +struct hdb_dbinfo { + char *label; + char *realm; + char *dbname; + char *mkey_file; + char *acl_file; + char *log_file; + const krb5_config_binding *binding; + struct hdb_dbinfo *next; +}; + +static int +get_dbinfo(krb5_context context, + const krb5_config_binding *db_binding, + const char *label, + struct hdb_dbinfo **db) +{ + struct hdb_dbinfo *di; + const char *p; + + *db = NULL; + + p = krb5_config_get_string(context, db_binding, "dbname", NULL); + if(p == NULL) + return 0; + + di = calloc(1, sizeof(*di)); + if (di == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + di->label = strdup(label); + di->dbname = strdup(p); + + p = krb5_config_get_string(context, db_binding, "realm", NULL); + if(p) + di->realm = strdup(p); + p = krb5_config_get_string(context, db_binding, "mkey_file", NULL); + if(p) + di->mkey_file = strdup(p); + p = krb5_config_get_string(context, db_binding, "acl_file", NULL); + if(p) + di->acl_file = strdup(p); + p = krb5_config_get_string(context, db_binding, "log_file", NULL); + if(p) + di->log_file = strdup(p); + + di->binding = db_binding; + + *db = di; + return 0; +} + + +int +hdb_get_dbinfo(krb5_context context, struct hdb_dbinfo **dbp) +{ + const krb5_config_binding *db_binding; + struct hdb_dbinfo *di, **dt, *databases; + const char *default_dbname = HDB_DEFAULT_DB; + const char *default_mkey = HDB_DB_DIR "/m-key"; + const char *default_acl = HDB_DB_DIR "/kadmind.acl"; + const char *p; + int ret; + + *dbp = NULL; + dt = NULL; + databases = NULL; + + db_binding = krb5_config_get(context, NULL, krb5_config_list, + "kdc", + "database", + NULL); + if (db_binding) { + + ret = get_dbinfo(context, db_binding, "default", &di); + if (ret == 0 && di) { + databases = di; + dt = &di->next; + } + + for ( ; db_binding != NULL; db_binding = db_binding->next) { + + if (db_binding->type != krb5_config_list) + continue; + + ret = get_dbinfo(context, db_binding->u.list, + db_binding->name, &di); + if (ret) + krb5_err(context, 1, ret, "failed getting realm"); + + if (di == NULL) + continue; + + if (dt) + *dt = di; + else + databases = di; + dt = &di->next; + + } + } + + if(databases == NULL) { + /* if there are none specified, create one and use defaults */ + di = calloc(1, sizeof(*di)); + databases = di; + di->label = strdup("default"); + } + + for(di = databases; di; di = di->next) { + if(di->dbname == NULL) { + di->dbname = strdup(default_dbname); + if (di->mkey_file == NULL) + di->mkey_file = strdup(default_mkey); + } + if(di->mkey_file == NULL) { + p = strrchr(di->dbname, '.'); + if(p == NULL || strchr(p, '/') != NULL) + /* final pathname component does not contain a . */ + asprintf(&di->mkey_file, "%s.mkey", di->dbname); + else + /* the filename is something.else, replace .else with + .mkey */ + asprintf(&di->mkey_file, "%.*s.mkey", + (int)(p - di->dbname), di->dbname); + } + if(di->acl_file == NULL) + di->acl_file = strdup(default_acl); + } + *dbp = databases; + return 0; +} + + +struct hdb_dbinfo * +hdb_dbinfo_get_next(struct hdb_dbinfo *dbp, struct hdb_dbinfo *dbprevp) +{ + if (dbprevp == NULL) + return dbp; + else + return dbprevp->next; +} + +const char * +hdb_dbinfo_get_label(krb5_context context, struct hdb_dbinfo *dbp) +{ + return dbp->label; +} + +const char * +hdb_dbinfo_get_realm(krb5_context context, struct hdb_dbinfo *dbp) +{ + return dbp->realm; +} + +const char * +hdb_dbinfo_get_dbname(krb5_context context, struct hdb_dbinfo *dbp) +{ + return dbp->dbname; +} + +const char * +hdb_dbinfo_get_mkey_file(krb5_context context, struct hdb_dbinfo *dbp) +{ + return dbp->mkey_file; +} + +const char * +hdb_dbinfo_get_acl_file(krb5_context context, struct hdb_dbinfo *dbp) +{ + return dbp->acl_file; +} + +const char * +hdb_dbinfo_get_log_file(krb5_context context, struct hdb_dbinfo *dbp) +{ + return dbp->log_file; +} + +const krb5_config_binding * +hdb_dbinfo_get_binding(krb5_context context, struct hdb_dbinfo *dbp) +{ + return dbp->binding; +} + +void +hdb_free_dbinfo(krb5_context context, struct hdb_dbinfo **dbp) +{ + struct hdb_dbinfo *di, *ndi; + + for(di = *dbp; di != NULL; di = ndi) { + ndi = di->next; + free (di->realm); + free (di->dbname); + if (di->mkey_file) + free (di->mkey_file); + free(di); + } + *dbp = NULL; +} + +/** + * Return the directory where the hdb database resides. + * + * @param context Kerberos 5 context. + * + * @return string pointing to directory. + */ + +const char * +hdb_db_dir(krb5_context context) +{ + return HDB_DB_DIR; +} + +/** + * Return the default hdb database resides. + * + * @param context Kerberos 5 context. + * + * @return string pointing to directory. + */ + +const char * +hdb_default_db(krb5_context context) +{ + return HDB_DEFAULT_DB; +} diff --git a/source4/heimdal/lib/hdb/ext.c b/source4/heimdal/lib/hdb/ext.c new file mode 100644 index 0000000000..92147254ee --- /dev/null +++ b/source4/heimdal/lib/hdb/ext.c @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2004 - 2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hdb_locl.h" +#include <der.h> + +RCSID("$Id$"); + +krb5_error_code +hdb_entry_check_mandatory(krb5_context context, const hdb_entry *ent) +{ + int i; + + if (ent->extensions == NULL) + return 0; + + /* + * check for unknown extensions and if they where tagged mandatory + */ + + for (i = 0; i < ent->extensions->len; i++) { + if (ent->extensions->val[i].data.element != + choice_HDB_extension_data_asn1_ellipsis) + continue; + if (ent->extensions->val[i].mandatory) { + krb5_set_error_message(context, HDB_ERR_MANDATORY_OPTION, + "Principal have unknown " + "mandatory extension"); + return HDB_ERR_MANDATORY_OPTION; + } + } + return 0; +} + +HDB_extension * +hdb_find_extension(const hdb_entry *entry, int type) +{ + int i; + + if (entry->extensions == NULL) + return NULL; + + for (i = 0; i < entry->extensions->len; i++) + if (entry->extensions->val[i].data.element == type) + return &entry->extensions->val[i]; + return NULL; +} + +/* + * Replace the extension `ext' in `entry'. Make a copy of the + * extension, so the caller must still free `ext' on both success and + * failure. Returns 0 or error code. + */ + +krb5_error_code +hdb_replace_extension(krb5_context context, + hdb_entry *entry, + const HDB_extension *ext) +{ + HDB_extension *ext2; + HDB_extension *es; + int ret; + + ext2 = NULL; + + if (entry->extensions == NULL) { + entry->extensions = calloc(1, sizeof(*entry->extensions)); + if (entry->extensions == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + } else if (ext->data.element != choice_HDB_extension_data_asn1_ellipsis) { + ext2 = hdb_find_extension(entry, ext->data.element); + } else { + /* + * This is an unknown extention, and we are asked to replace a + * possible entry in `entry' that is of the same type. This + * might seem impossible, but ASN.1 CHOICE comes to our + * rescue. The first tag in each branch in the CHOICE is + * unique, so just find the element in the list that have the + * same tag was we are putting into the list. + */ + Der_class replace_class, list_class; + Der_type replace_type, list_type; + unsigned int replace_tag, list_tag; + size_t size; + int i; + + ret = der_get_tag(ext->data.u.asn1_ellipsis.data, + ext->data.u.asn1_ellipsis.length, + &replace_class, &replace_type, &replace_tag, + &size); + if (ret) { + krb5_set_error_message(context, ret, "hdb: failed to decode " + "replacement hdb extention"); + return ret; + } + + for (i = 0; i < entry->extensions->len; i++) { + HDB_extension *ext3 = &entry->extensions->val[i]; + + if (ext3->data.element != choice_HDB_extension_data_asn1_ellipsis) + continue; + + ret = der_get_tag(ext3->data.u.asn1_ellipsis.data, + ext3->data.u.asn1_ellipsis.length, + &list_class, &list_type, &list_tag, + &size); + if (ret) { + krb5_set_error_message(context, ret, "hdb: failed to decode " + "present hdb extention"); + return ret; + } + + if (MAKE_TAG(replace_class,replace_type,replace_type) == + MAKE_TAG(list_class,list_type,list_type)) { + ext2 = ext3; + break; + } + } + } + + if (ext2) { + free_HDB_extension(ext2); + ret = copy_HDB_extension(ext, ext2); + if (ret) + krb5_set_error_message(context, ret, "hdb: failed to copy replacement " + "hdb extention"); + return ret; + } + + es = realloc(entry->extensions->val, + (entry->extensions->len+1)*sizeof(entry->extensions->val[0])); + if (es == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + entry->extensions->val = es; + + ret = copy_HDB_extension(ext, + &entry->extensions->val[entry->extensions->len]); + if (ret == 0) + entry->extensions->len++; + else + krb5_set_error_message(context, ret, "hdb: failed to copy new extension"); + + return ret; +} + +krb5_error_code +hdb_clear_extension(krb5_context context, + hdb_entry *entry, + int type) +{ + int i; + + if (entry->extensions == NULL) + return 0; + + for (i = 0; i < entry->extensions->len; i++) { + if (entry->extensions->val[i].data.element == type) { + free_HDB_extension(&entry->extensions->val[i]); + memmove(&entry->extensions->val[i], + &entry->extensions->val[i + 1], + sizeof(entry->extensions->val[i]) * (entry->extensions->len - i - 1)); + entry->extensions->len--; + } + } + if (entry->extensions->len == 0) { + free(entry->extensions->val); + free(entry->extensions); + entry->extensions = NULL; + } + + return 0; +} + + +krb5_error_code +hdb_entry_get_pkinit_acl(const hdb_entry *entry, const HDB_Ext_PKINIT_acl **a) +{ + const HDB_extension *ext; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_acl); + if (ext) + *a = &ext->data.u.pkinit_acl; + else + *a = NULL; + + return 0; +} + +krb5_error_code +hdb_entry_get_pkinit_hash(const hdb_entry *entry, const HDB_Ext_PKINIT_hash **a) +{ + const HDB_extension *ext; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_cert_hash); + if (ext) + *a = &ext->data.u.pkinit_cert_hash; + else + *a = NULL; + + return 0; +} + +krb5_error_code +hdb_entry_get_pw_change_time(const hdb_entry *entry, time_t *t) +{ + const HDB_extension *ext; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_last_pw_change); + if (ext) + *t = ext->data.u.last_pw_change; + else + *t = 0; + + return 0; +} + +krb5_error_code +hdb_entry_set_pw_change_time(krb5_context context, + hdb_entry *entry, + time_t t) +{ + HDB_extension ext; + + ext.mandatory = FALSE; + ext.data.element = choice_HDB_extension_data_last_pw_change; + if (t == 0) + t = time(NULL); + ext.data.u.last_pw_change = t; + + return hdb_replace_extension(context, entry, &ext); +} + +int +hdb_entry_get_password(krb5_context context, HDB *db, + const hdb_entry *entry, char **p) +{ + HDB_extension *ext; + char *str; + int ret; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_password); + if (ext) { + heim_utf8_string str; + heim_octet_string pw; + + if (db->hdb_master_key_set && ext->data.u.password.mkvno) { + hdb_master_key key; + + key = _hdb_find_master_key(ext->data.u.password.mkvno, + db->hdb_master_key); + + if (key == NULL) { + krb5_set_error_message(context, HDB_ERR_NO_MKEY, + "master key %d missing", + *ext->data.u.password.mkvno); + return HDB_ERR_NO_MKEY; + } + + ret = _hdb_mkey_decrypt(context, key, HDB_KU_MKEY, + ext->data.u.password.password.data, + ext->data.u.password.password.length, + &pw); + } else { + ret = der_copy_octet_string(&ext->data.u.password.password, &pw); + } + if (ret) { + krb5_clear_error_string(context); + return ret; + } + + str = pw.data; + if (str[pw.length - 1] != '\0') { + krb5_set_error_message(context, EINVAL, "password malformated"); + return EINVAL; + } + + *p = strdup(str); + + der_free_octet_string(&pw); + if (*p == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + return 0; + } + + ret = krb5_unparse_name(context, entry->principal, &str); + if (ret == 0) { + krb5_set_error_message(context, ENOENT, "no password attributefor %s", str); + free(str); + } else + krb5_clear_error_string(context); + + return ENOENT; +} + +int +hdb_entry_set_password(krb5_context context, HDB *db, + hdb_entry *entry, const char *p) +{ + HDB_extension ext; + hdb_master_key key; + int ret; + + ext.mandatory = FALSE; + ext.data.element = choice_HDB_extension_data_password; + + if (db->hdb_master_key_set) { + + key = _hdb_find_master_key(NULL, db->hdb_master_key); + if (key == NULL) { + krb5_set_error_message(context, HDB_ERR_NO_MKEY, + "hdb_entry_set_password: " + "failed to find masterkey"); + return HDB_ERR_NO_MKEY; + } + + ret = _hdb_mkey_encrypt(context, key, HDB_KU_MKEY, + p, strlen(p) + 1, + &ext.data.u.password.password); + if (ret) + return ret; + + ext.data.u.password.mkvno = + malloc(sizeof(*ext.data.u.password.mkvno)); + if (ext.data.u.password.mkvno == NULL) { + free_HDB_extension(&ext); + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + *ext.data.u.password.mkvno = _hdb_mkey_version(key); + + } else { + ext.data.u.password.mkvno = NULL; + + ret = krb5_data_copy(&ext.data.u.password.password, + p, strlen(p) + 1); + if (ret) { + krb5_set_error_message(context, ret, "malloc: out of memory"); + free_HDB_extension(&ext); + return ret; + } + } + + ret = hdb_replace_extension(context, entry, &ext); + + free_HDB_extension(&ext); + + return ret; +} + +int +hdb_entry_clear_password(krb5_context context, hdb_entry *entry) +{ + return hdb_clear_extension(context, entry, + choice_HDB_extension_data_password); +} + +krb5_error_code +hdb_entry_get_ConstrainedDelegACL(const hdb_entry *entry, + const HDB_Ext_Constrained_delegation_acl **a) +{ + const HDB_extension *ext; + + ext = hdb_find_extension(entry, + choice_HDB_extension_data_allowed_to_delegate_to); + if (ext) + *a = &ext->data.u.allowed_to_delegate_to; + else + *a = NULL; + + return 0; +} + +krb5_error_code +hdb_entry_get_aliases(const hdb_entry *entry, const HDB_Ext_Aliases **a) +{ + const HDB_extension *ext; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_aliases); + if (ext) + *a = &ext->data.u.aliases; + else + *a = NULL; + + return 0; +} diff --git a/source4/heimdal/lib/hdb/hdb.asn1 b/source4/heimdal/lib/hdb/hdb.asn1 new file mode 100644 index 0000000000..5cddf8f1d0 --- /dev/null +++ b/source4/heimdal/lib/hdb/hdb.asn1 @@ -0,0 +1,127 @@ +-- $Id$ +HDB DEFINITIONS ::= +BEGIN + +IMPORTS EncryptionKey, KerberosTime, Principal FROM krb5; + +HDB_DB_FORMAT INTEGER ::= 2 -- format of database, + -- update when making changes + +-- these must have the same value as the pa-* counterparts +hdb-pw-salt INTEGER ::= 3 +hdb-afs3-salt INTEGER ::= 10 + +Salt ::= SEQUENCE { + type[0] INTEGER (0..4294967295), + salt[1] OCTET STRING +} + +Key ::= SEQUENCE { + mkvno[0] INTEGER (0..4294967295) OPTIONAL, -- master key version number + key[1] EncryptionKey, + salt[2] Salt OPTIONAL +} + +Event ::= SEQUENCE { + time[0] KerberosTime, + principal[1] Principal OPTIONAL +} + +HDBFlags ::= BIT STRING { + initial(0), -- require as-req + forwardable(1), -- may issue forwardable + proxiable(2), -- may issue proxiable + renewable(3), -- may issue renewable + postdate(4), -- may issue postdatable + server(5), -- may be server + client(6), -- may be client + invalid(7), -- entry is invalid + require-preauth(8), -- must use preauth + change-pw(9), -- change password service + require-hwauth(10), -- must use hwauth + ok-as-delegate(11), -- as in TicketFlags + user-to-user(12), -- may use user-to-user auth + immutable(13), -- may not be deleted + trusted-for-delegation(14), -- Trusted to print forwardabled tickets + allow-kerberos4(15), -- Allow Kerberos 4 requests + allow-digest(16) -- Allow digest requests +} + +GENERATION ::= SEQUENCE { + time[0] KerberosTime, -- timestamp + usec[1] INTEGER (0..4294967295), -- microseconds + gen[2] INTEGER (0..4294967295) -- generation number +} + +HDB-Ext-PKINIT-acl ::= SEQUENCE OF SEQUENCE { + subject[0] UTF8String, + issuer[1] UTF8String OPTIONAL, + anchor[2] UTF8String OPTIONAL +} + +HDB-Ext-PKINIT-hash ::= SEQUENCE OF SEQUENCE { + digest-type[0] OBJECT IDENTIFIER, + digest[1] OCTET STRING +} + +HDB-Ext-Constrained-delegation-acl ::= SEQUENCE OF Principal + +-- hdb-ext-referrals ::= PA-SERVER-REFERRAL-DATA + +HDB-Ext-Lan-Manager-OWF ::= OCTET STRING + +HDB-Ext-Password ::= SEQUENCE { + mkvno[0] INTEGER (0..4294967295) OPTIONAL, -- master key version number + password OCTET STRING +} + +HDB-Ext-Aliases ::= SEQUENCE { + case-insensitive[0] BOOLEAN, -- case insensitive name allowed + aliases[1] SEQUENCE OF Principal -- all names, inc primary +} + + +HDB-extension ::= SEQUENCE { + mandatory[0] BOOLEAN, -- kdc MUST understand this extension, + -- if not the whole entry must + -- be rejected + data[1] CHOICE { + pkinit-acl[0] HDB-Ext-PKINIT-acl, + pkinit-cert-hash[1] HDB-Ext-PKINIT-hash, + allowed-to-delegate-to[2] HDB-Ext-Constrained-delegation-acl, +-- referral-info[3] HDB-Ext-Referrals, + lm-owf[4] HDB-Ext-Lan-Manager-OWF, + password[5] HDB-Ext-Password, + aliases[6] HDB-Ext-Aliases, + last-pw-change[7] KerberosTime, + ... + }, + ... +} + +HDB-extensions ::= SEQUENCE OF HDB-extension + + +hdb_entry ::= SEQUENCE { + principal[0] Principal OPTIONAL, -- this is optional only + -- for compatibility with libkrb5 + kvno[1] INTEGER (0..4294967295), + keys[2] SEQUENCE OF Key, + created-by[3] Event, + modified-by[4] Event OPTIONAL, + valid-start[5] KerberosTime OPTIONAL, + valid-end[6] KerberosTime OPTIONAL, + pw-end[7] KerberosTime OPTIONAL, + max-life[8] INTEGER (0..4294967295) OPTIONAL, + max-renew[9] INTEGER (0..4294967295) OPTIONAL, + flags[10] HDBFlags, + etypes[11] SEQUENCE OF INTEGER (0..4294967295) OPTIONAL, + generation[12] GENERATION OPTIONAL, + extensions[13] HDB-extensions OPTIONAL +} + +hdb_entry_alias ::= [APPLICATION 0] SEQUENCE { + principal[0] Principal OPTIONAL +} + +END diff --git a/source4/heimdal/lib/hdb/hdb.c b/source4/heimdal/lib/hdb/hdb.c new file mode 100644 index 0000000000..3fddabb2d0 --- /dev/null +++ b/source4/heimdal/lib/hdb/hdb.c @@ -0,0 +1,413 @@ +/* + * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hdb_locl.h" + +RCSID("$Id$"); + +#ifdef HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +struct hdb_method { + const char *prefix; + krb5_error_code (*create)(krb5_context, HDB **, const char *filename); +}; + +static struct hdb_method methods[] = { +#if HAVE_DB1 || HAVE_DB3 + {"db:", hdb_db_create}, +#endif +#if HAVE_NDBM + {"ndbm:", hdb_ndbm_create}, +#endif +#if defined(OPENLDAP) && !defined(OPENLDAP_MODULE) + {"ldap:", hdb_ldap_create}, + {"ldapi:", hdb_ldapi_create}, +#endif +#ifdef HAVE_LDB /* Used for integrated samba build */ + {"ldb:", hdb_ldb_create}, +#endif + {NULL, NULL} +}; + +#if HAVE_DB1 || HAVE_DB3 +static struct hdb_method dbmetod = {"", hdb_db_create }; +#elif defined(HAVE_NDBM) +static struct hdb_method dbmetod = {"", hdb_ndbm_create }; +#endif + + +krb5_error_code +hdb_next_enctype2key(krb5_context context, + const hdb_entry *e, + krb5_enctype enctype, + Key **key) +{ + Key *k; + + for (k = *key ? (*key) + 1 : e->keys.val; + k < e->keys.val + e->keys.len; + k++) + { + if(k->key.keytype == enctype){ + *key = k; + return 0; + } + } + krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP, + "No next enctype %d for hdb-entry", + (int)enctype); + return KRB5_PROG_ETYPE_NOSUPP; /* XXX */ +} + +krb5_error_code +hdb_enctype2key(krb5_context context, + hdb_entry *e, + krb5_enctype enctype, + Key **key) +{ + *key = NULL; + return hdb_next_enctype2key(context, e, enctype, key); +} + +void +hdb_free_key(Key *key) +{ + memset(key->key.keyvalue.data, + 0, + key->key.keyvalue.length); + free_Key(key); + free(key); +} + + +krb5_error_code +hdb_lock(int fd, int operation) +{ + int i, code = 0; + + for(i = 0; i < 3; i++){ + code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB); + if(code == 0 || errno != EWOULDBLOCK) + break; + sleep(1); + } + if(code == 0) + return 0; + if(errno == EWOULDBLOCK) + return HDB_ERR_DB_INUSE; + return HDB_ERR_CANT_LOCK_DB; +} + +krb5_error_code +hdb_unlock(int fd) +{ + int code; + code = flock(fd, LOCK_UN); + if(code) + return 4711 /* XXX */; + return 0; +} + +void +hdb_free_entry(krb5_context context, hdb_entry_ex *ent) +{ + int i; + + if (ent->free_entry) + (*ent->free_entry)(context, ent); + + for(i = 0; i < ent->entry.keys.len; ++i) { + Key *k = &ent->entry.keys.val[i]; + + memset (k->key.keyvalue.data, 0, k->key.keyvalue.length); + } + free_hdb_entry(&ent->entry); +} + +krb5_error_code +hdb_foreach(krb5_context context, + HDB *db, + unsigned flags, + hdb_foreach_func_t func, + void *data) +{ + krb5_error_code ret; + hdb_entry_ex entry; + ret = db->hdb_firstkey(context, db, flags, &entry); + if (ret == 0) + krb5_clear_error_string(context); + while(ret == 0){ + ret = (*func)(context, db, &entry, data); + hdb_free_entry(context, &entry); + if(ret == 0) + ret = db->hdb_nextkey(context, db, flags, &entry); + } + if(ret == HDB_ERR_NOENTRY) + ret = 0; + return ret; +} + +krb5_error_code +hdb_check_db_format(krb5_context context, HDB *db) +{ + krb5_data tag; + krb5_data version; + krb5_error_code ret, ret2; + unsigned ver; + int foo; + + ret = db->hdb_lock(context, db, HDB_RLOCK); + if (ret) + return ret; + + tag.data = HDB_DB_FORMAT_ENTRY; + tag.length = strlen(tag.data); + ret = (*db->hdb__get)(context, db, tag, &version); + ret2 = db->hdb_unlock(context, db); + if(ret) + return ret; + if (ret2) + return ret2; + foo = sscanf(version.data, "%u", &ver); + krb5_data_free (&version); + if (foo != 1) + return HDB_ERR_BADVERSION; + if(ver != HDB_DB_FORMAT) + return HDB_ERR_BADVERSION; + return 0; +} + +krb5_error_code +hdb_init_db(krb5_context context, HDB *db) +{ + krb5_error_code ret, ret2; + krb5_data tag; + krb5_data version; + char ver[32]; + + ret = hdb_check_db_format(context, db); + if(ret != HDB_ERR_NOENTRY) + return ret; + + ret = db->hdb_lock(context, db, HDB_WLOCK); + if (ret) + return ret; + + tag.data = HDB_DB_FORMAT_ENTRY; + tag.length = strlen(tag.data); + snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT); + version.data = ver; + version.length = strlen(version.data) + 1; /* zero terminated */ + ret = (*db->hdb__put)(context, db, 0, tag, version); + ret2 = db->hdb_unlock(context, db); + if (ret) { + if (ret2) + krb5_clear_error_string(context); + return ret; + } + return ret2; +} + +#ifdef HAVE_DLOPEN + + /* + * Load a dynamic backend from /usr/heimdal/lib/hdb_NAME.so, + * looking for the hdb_NAME_create symbol. + */ + +static const struct hdb_method * +find_dynamic_method (krb5_context context, + const char *filename, + const char **rest) +{ + static struct hdb_method method; + struct hdb_so_method *mso; + char *prefix, *path, *symbol; + const char *p; + void *dl; + size_t len; + + p = strchr(filename, ':'); + + /* if no prefix, don't know what module to load, just ignore it */ + if (p == NULL) + return NULL; + + len = p - filename; + *rest = filename + len + 1; + + prefix = strndup(filename, len); + if (prefix == NULL) + krb5_errx(context, 1, "out of memory"); + + if (asprintf(&path, LIBDIR "/hdb_%s.so", prefix) == -1) + krb5_errx(context, 1, "out of memory"); + +#ifndef RTLD_NOW +#define RTLD_NOW 0 +#endif +#ifndef RTLD_GLOBAL +#define RTLD_GLOBAL 0 +#endif + + dl = dlopen(path, RTLD_NOW | RTLD_GLOBAL); + if (dl == NULL) { + krb5_warnx(context, "error trying to load dynamic module %s: %s\n", + path, dlerror()); + free(prefix); + free(path); + return NULL; + } + + if (asprintf(&symbol, "hdb_%s_interface", prefix) == -1) + krb5_errx(context, 1, "out of memory"); + + mso = dlsym(dl, symbol); + if (mso == NULL) { + krb5_warnx(context, "error finding symbol %s in %s: %s\n", + symbol, path, dlerror()); + dlclose(dl); + free(symbol); + free(prefix); + free(path); + return NULL; + } + free(path); + free(symbol); + + if (mso->version != HDB_INTERFACE_VERSION) { + krb5_warnx(context, + "error wrong version in shared module %s " + "version: %d should have been %d\n", + prefix, mso->version, HDB_INTERFACE_VERSION); + dlclose(dl); + free(prefix); + return NULL; + } + + if (mso->create == NULL) { + krb5_errx(context, 1, + "no entry point function in shared mod %s ", + prefix); + dlclose(dl); + free(prefix); + return NULL; + } + + method.create = mso->create; + method.prefix = prefix; + + return &method; +} +#endif /* HAVE_DLOPEN */ + +/* + * find the relevant method for `filename', returning a pointer to the + * rest in `rest'. + * return NULL if there's no such method. + */ + +static const struct hdb_method * +find_method (const char *filename, const char **rest) +{ + const struct hdb_method *h; + + for (h = methods; h->prefix != NULL; ++h) { + if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0) { + *rest = filename + strlen(h->prefix); + return h; + } + } +#if defined(HAVE_DB1) || defined(HAVE_DB3) || defined(HAVE_NDBM) + if (strncmp(filename, "/", 1) == 0 + || strncmp(filename, "./", 2) == 0 + || strncmp(filename, "../", 3) == 0) + { + *rest = filename; + return &dbmetod; + } +#endif + + return NULL; +} + +krb5_error_code +hdb_list_builtin(krb5_context context, char **list) +{ + const struct hdb_method *h; + size_t len = 0; + char *buf = NULL; + + for (h = methods; h->prefix != NULL; ++h) { + if (h->prefix[0] == '\0') + continue; + len += strlen(h->prefix) + 2; + } + + len += 1; + buf = malloc(len); + if (buf == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + buf[0] = '\0'; + + for (h = methods; h->prefix != NULL; ++h) { + if (h != methods) + strlcat(buf, ", ", len); + strlcat(buf, h->prefix, len); + } + *list = buf; + return 0; +} + +krb5_error_code +hdb_create(krb5_context context, HDB **db, const char *filename) +{ + const struct hdb_method *h; + const char *residual; + + if(filename == NULL) + filename = HDB_DEFAULT_DB; + krb5_add_et_list(context, initialize_hdb_error_table_r); + h = find_method (filename, &residual); +#ifdef HAVE_DLOPEN + if (h == NULL) + h = find_dynamic_method (context, filename, &residual); +#endif + if (h == NULL) + krb5_errx(context, 1, "No database support for %s", filename); + return (*h->create)(context, db, residual); +} diff --git a/source4/heimdal/lib/hdb/hdb.h b/source4/heimdal/lib/hdb/hdb.h new file mode 100644 index 0000000000..bc1b744015 --- /dev/null +++ b/source4/heimdal/lib/hdb/hdb.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* $Id$ */ + +#ifndef __HDB_H__ +#define __HDB_H__ + +#include <hdb_err.h> + +#include <heim_asn1.h> +#include <hdb_asn1.h> + +struct hdb_dbinfo; + +enum hdb_lockop{ HDB_RLOCK, HDB_WLOCK }; + +/* flags for various functions */ +#define HDB_F_DECRYPT 1 /* decrypt keys */ +#define HDB_F_REPLACE 2 /* replace entry */ +#define HDB_F_GET_CLIENT 4 /* fetch client */ +#define HDB_F_GET_SERVER 8 /* fetch server */ +#define HDB_F_GET_KRBTGT 16 /* fetch krbtgt */ +#define HDB_F_GET_ANY 28 /* fetch any of client,server,krbtgt */ +#define HDB_F_CANON 32 /* want canonicalition */ + +/* key usage for master key */ +#define HDB_KU_MKEY 0x484442 + +typedef struct hdb_master_key_data *hdb_master_key; + +typedef struct hdb_entry_ex { + void *ctx; + hdb_entry entry; + void (*free_entry)(krb5_context, struct hdb_entry_ex *); +} hdb_entry_ex; + + +typedef struct HDB{ + void *hdb_db; + void *hdb_dbc; + char *hdb_name; + int hdb_master_key_set; + hdb_master_key hdb_master_key; + int hdb_openp; + + krb5_error_code (*hdb_open)(krb5_context, + struct HDB*, + int, + mode_t); + krb5_error_code (*hdb_close)(krb5_context, + struct HDB*); + void (*hdb_free)(krb5_context, + struct HDB*, + hdb_entry_ex*); + krb5_error_code (*hdb_fetch)(krb5_context, + struct HDB*, + krb5_const_principal, + unsigned, + hdb_entry_ex*); + krb5_error_code (*hdb_store)(krb5_context, + struct HDB*, + unsigned, + hdb_entry_ex*); + krb5_error_code (*hdb_remove)(krb5_context, + struct HDB*, + krb5_const_principal); + krb5_error_code (*hdb_firstkey)(krb5_context, + struct HDB*, + unsigned, + hdb_entry_ex*); + krb5_error_code (*hdb_nextkey)(krb5_context, + struct HDB*, + unsigned, + hdb_entry_ex*); + krb5_error_code (*hdb_lock)(krb5_context, + struct HDB*, + int operation); + krb5_error_code (*hdb_unlock)(krb5_context, + struct HDB*); + krb5_error_code (*hdb_rename)(krb5_context, + struct HDB*, + const char*); + krb5_error_code (*hdb__get)(krb5_context, + struct HDB*, + krb5_data, + krb5_data*); + krb5_error_code (*hdb__put)(krb5_context, + struct HDB*, + int, + krb5_data, + krb5_data); + krb5_error_code (*hdb__del)(krb5_context, + struct HDB*, + krb5_data); + krb5_error_code (*hdb_destroy)(krb5_context, + struct HDB*); +}HDB; + +#define HDB_INTERFACE_VERSION 4 + +struct hdb_so_method { + int version; + const char *prefix; + krb5_error_code (*create)(krb5_context, HDB **, const char *filename); +}; + +typedef krb5_error_code (*hdb_foreach_func_t)(krb5_context, HDB*, + hdb_entry_ex*, void*); +extern krb5_kt_ops hdb_kt_ops; + +#include <hdb-protos.h> + +#endif /* __HDB_H__ */ diff --git a/source4/heimdal/lib/hdb/hdb_err.et b/source4/heimdal/lib/hdb/hdb_err.et new file mode 100644 index 0000000000..64f79fc84e --- /dev/null +++ b/source4/heimdal/lib/hdb/hdb_err.et @@ -0,0 +1,28 @@ +# +# Error messages for the hdb library +# +# This might look like a com_err file, but is not +# +id "$Id$" + +error_table hdb + +prefix HDB_ERR + +index 1 +#error_code INUSE, "Entry already exists in database" +error_code UK_SERROR, "Database store error" +error_code UK_RERROR, "Database read error" +error_code NOENTRY, "No such entry in the database" +error_code DB_INUSE, "Database is locked or in use--try again later" +error_code DB_CHANGED, "Database was modified during read" +error_code RECURSIVELOCK, "Attempt to lock database twice" +error_code NOTLOCKED, "Attempt to unlock database when not locked" +error_code BADLOCKMODE, "Invalid kdb lock mode" +error_code CANT_LOCK_DB, "Insufficient access to lock database" +error_code EXISTS, "Entry already exists in database" +error_code BADVERSION, "Wrong database version" +error_code NO_MKEY, "No correct master key" +error_code MANDATORY_OPTION, "Entry contains unknown mandatory extension" + +end diff --git a/source4/heimdal/lib/hdb/hdb_locl.h b/source4/heimdal/lib/hdb/hdb_locl.h new file mode 100644 index 0000000000..9229146d04 --- /dev/null +++ b/source4/heimdal/lib/hdb/hdb_locl.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 1997-2001 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* $Id$ */ + +#ifndef __HDB_LOCL_H__ +#define __HDB_LOCL_H__ + +#include <config.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_SYS_FILE_H +#include <sys/file.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#include <roken.h> + +#include "crypto-headers.h" +#include <krb5.h> +#include <hdb.h> +#include <hdb-private.h> + +#define HDB_DEFAULT_DB HDB_DB_DIR "/heimdal" +#define HDB_DB_FORMAT_ENTRY "hdb/db-format" + +#endif /* __HDB_LOCL_H__ */ diff --git a/source4/heimdal/lib/hdb/keys.c b/source4/heimdal/lib/hdb/keys.c new file mode 100644 index 0000000000..e649f445e0 --- /dev/null +++ b/source4/heimdal/lib/hdb/keys.c @@ -0,0 +1,401 @@ +/* + * Copyright (c) 1997 - 2001, 2003 - 2004 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hdb_locl.h" + +RCSID("$Id$"); + +/* + * free all the memory used by (len, keys) + */ + +void +hdb_free_keys (krb5_context context, int len, Key *keys) +{ + int i; + + for (i = 0; i < len; i++) { + free(keys[i].mkvno); + keys[i].mkvno = NULL; + if (keys[i].salt != NULL) { + free_Salt(keys[i].salt); + free(keys[i].salt); + keys[i].salt = NULL; + } + krb5_free_keyblock_contents(context, &keys[i].key); + } + free (keys); +} + +/* + * for each entry in `default_keys' try to parse it as a sequence + * of etype:salttype:salt, syntax of this if something like: + * [(des|des3|etype):](pw-salt|afs3)[:string], if etype is omitted it + * means all etypes, and if string is omitted is means the default + * string (for that principal). Additional special values: + * v5 == pw-salt, and + * v4 == des:pw-salt: + * afs or afs3 == des:afs3-salt + */ + +static const krb5_enctype des_etypes[] = { + ETYPE_DES_CBC_MD5, + ETYPE_DES_CBC_MD4, + ETYPE_DES_CBC_CRC +}; + +static const krb5_enctype all_etypes[] = { + ETYPE_AES256_CTS_HMAC_SHA1_96, + ETYPE_ARCFOUR_HMAC_MD5, + ETYPE_DES3_CBC_SHA1 +}; + +static krb5_error_code +parse_key_set(krb5_context context, const char *key, + krb5_enctype **ret_enctypes, size_t *ret_num_enctypes, + krb5_salt *salt, krb5_principal principal) +{ + const char *p; + char buf[3][256]; + int num_buf = 0; + int i, num_enctypes = 0; + krb5_enctype e; + const krb5_enctype *enctypes = NULL; + krb5_error_code ret; + + p = key; + + *ret_enctypes = NULL; + *ret_num_enctypes = 0; + + /* split p in a list of :-separated strings */ + for(num_buf = 0; num_buf < 3; num_buf++) + if(strsep_copy(&p, ":", buf[num_buf], sizeof(buf[num_buf])) == -1) + break; + + salt->saltvalue.data = NULL; + salt->saltvalue.length = 0; + + for(i = 0; i < num_buf; i++) { + if(enctypes == NULL && num_buf > 1) { + /* this might be a etype specifier */ + /* XXX there should be a string_to_etypes handling + special cases like `des' and `all' */ + if(strcmp(buf[i], "des") == 0) { + enctypes = des_etypes; + num_enctypes = sizeof(des_etypes)/sizeof(des_etypes[0]); + } else if(strcmp(buf[i], "des3") == 0) { + e = ETYPE_DES3_CBC_SHA1; + enctypes = &e; + num_enctypes = 1; + } else { + ret = krb5_string_to_enctype(context, buf[i], &e); + if (ret == 0) { + enctypes = &e; + num_enctypes = 1; + } else + return ret; + } + continue; + } + if(salt->salttype == 0) { + /* interpret string as a salt specifier, if no etype + is set, this sets default values */ + /* XXX should perhaps use string_to_salttype, but that + interface sucks */ + if(strcmp(buf[i], "pw-salt") == 0) { + if(enctypes == NULL) { + enctypes = all_etypes; + num_enctypes = sizeof(all_etypes)/sizeof(all_etypes[0]); + } + salt->salttype = KRB5_PW_SALT; + } else if(strcmp(buf[i], "afs3-salt") == 0) { + if(enctypes == NULL) { + enctypes = des_etypes; + num_enctypes = sizeof(des_etypes)/sizeof(des_etypes[0]); + } + salt->salttype = KRB5_AFS3_SALT; + } + continue; + } + + { + /* if there is a final string, use it as the string to + salt with, this is mostly useful with null salt for + v4 compat, and a cell name for afs compat */ + salt->saltvalue.data = strdup(buf[i]); + if (salt->saltvalue.data == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + salt->saltvalue.length = strlen(buf[i]); + } + } + + if(enctypes == NULL || salt->salttype == 0) { + krb5_set_error_message(context, EINVAL, "bad value for default_keys `%s'", key); + return EINVAL; + } + + /* if no salt was specified make up default salt */ + if(salt->saltvalue.data == NULL) { + if(salt->salttype == KRB5_PW_SALT) + ret = krb5_get_pw_salt(context, principal, salt); + else if(salt->salttype == KRB5_AFS3_SALT) { + krb5_realm *realm = krb5_princ_realm(context, principal); + salt->saltvalue.data = strdup(*realm); + if(salt->saltvalue.data == NULL) { + krb5_set_error_message(context, ENOMEM, + "out of memory while " + "parsing salt specifiers"); + return ENOMEM; + } + strlwr(salt->saltvalue.data); + salt->saltvalue.length = strlen(*realm); + } + } + + *ret_enctypes = malloc(sizeof(enctypes[0]) * num_enctypes); + if (*ret_enctypes == NULL) { + krb5_free_salt(context, *salt); + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + memcpy(*ret_enctypes, enctypes, sizeof(enctypes[0]) * num_enctypes); + *ret_num_enctypes = num_enctypes; + + return 0; +} + +static krb5_error_code +add_enctype_to_key_set(Key **key_set, size_t *nkeyset, + krb5_enctype enctype, krb5_salt *salt) +{ + krb5_error_code ret; + Key key, *tmp; + + memset(&key, 0, sizeof(key)); + + tmp = realloc(*key_set, (*nkeyset + 1) * sizeof((*key_set)[0])); + if (tmp == NULL) + return ENOMEM; + + *key_set = tmp; + + key.key.keytype = enctype; + key.key.keyvalue.length = 0; + key.key.keyvalue.data = NULL; + + if (salt) { + key.salt = malloc(sizeof(*key.salt)); + if (key.salt == NULL) { + free_Key(&key); + return ENOMEM; + } + + key.salt->type = salt->salttype; + krb5_data_zero (&key.salt->salt); + + ret = krb5_data_copy(&key.salt->salt, + salt->saltvalue.data, + salt->saltvalue.length); + if (ret) { + free_Key(&key); + return ret; + } + } else + key.salt = NULL; + + (*key_set)[*nkeyset] = key; + + *nkeyset += 1; + + return 0; +} + + +/* + * Generate the `key_set' from the [kadmin]default_keys statement. If + * `no_salt' is set, salt is not important (and will not be set) since + * it's random keys that is going to be created. + */ + +krb5_error_code +hdb_generate_key_set(krb5_context context, krb5_principal principal, + Key **ret_key_set, size_t *nkeyset, int no_salt) +{ + char **ktypes, **kp; + krb5_error_code ret; + Key *k, *key_set; + int i, j; + char *default_keytypes[] = { + "des:pw-salt", + "aes256-cts-hmac-sha1-96:pw-salt", + "des3-cbc-sha1:pw-salt", + "arcfour-hmac-md5:pw-salt", + NULL + }; + + ktypes = krb5_config_get_strings(context, NULL, "kadmin", + "default_keys", NULL); + if (ktypes == NULL) + ktypes = default_keytypes; + + if (ktypes == NULL) + abort(); + + *ret_key_set = key_set = NULL; + *nkeyset = 0; + + ret = 0; + + for(kp = ktypes; kp && *kp; kp++) { + const char *p; + krb5_salt salt; + krb5_enctype *enctypes; + size_t num_enctypes; + + p = *kp; + /* check alias */ + if(strcmp(p, "v5") == 0) + p = "pw-salt"; + else if(strcmp(p, "v4") == 0) + p = "des:pw-salt:"; + else if(strcmp(p, "afs") == 0 || strcmp(p, "afs3") == 0) + p = "des:afs3-salt"; + else if (strcmp(p, "arcfour-hmac-md5") == 0) + p = "arcfour-hmac-md5:pw-salt"; + + memset(&salt, 0, sizeof(salt)); + + ret = parse_key_set(context, p, + &enctypes, &num_enctypes, &salt, principal); + if (ret) { + krb5_warn(context, ret, "bad value for default_keys `%s'", *kp); + ret = 0; + continue; + } + + for (i = 0; i < num_enctypes; i++) { + /* find duplicates */ + for (j = 0; j < *nkeyset; j++) { + + k = &key_set[j]; + + if (k->key.keytype == enctypes[i]) { + if (no_salt) + break; + if (k->salt == NULL && salt.salttype == KRB5_PW_SALT) + break; + if (k->salt->type == salt.salttype && + k->salt->salt.length == salt.saltvalue.length && + memcmp(k->salt->salt.data, salt.saltvalue.data, + salt.saltvalue.length) == 0) + break; + } + } + /* not a duplicate, lets add it */ + if (j == *nkeyset) { + ret = add_enctype_to_key_set(&key_set, nkeyset, enctypes[i], + no_salt ? NULL : &salt); + if (ret) { + free(enctypes); + krb5_free_salt(context, salt); + goto out; + } + } + } + free(enctypes); + krb5_free_salt(context, salt); + } + + *ret_key_set = key_set; + + out: + if (ktypes != default_keytypes) + krb5_config_free_strings(ktypes); + + if (ret) { + krb5_warn(context, ret, + "failed to parse the [kadmin]default_keys values"); + + for (i = 0; i < *nkeyset; i++) + free_Key(&key_set[i]); + free(key_set); + } else if (*nkeyset == 0) { + krb5_warnx(context, + "failed to parse any of the [kadmin]default_keys values"); + ret = EINVAL; /* XXX */ + } + + return ret; +} + + +krb5_error_code +hdb_generate_key_set_password(krb5_context context, + krb5_principal principal, + const char *password, + Key **keys, size_t *num_keys) +{ + krb5_error_code ret; + int i; + + ret = hdb_generate_key_set(context, principal, + keys, num_keys, 0); + if (ret) + return ret; + + for (i = 0; i < (*num_keys); i++) { + krb5_salt salt; + + salt.salttype = (*keys)[i].salt->type; + salt.saltvalue.length = (*keys)[i].salt->salt.length; + salt.saltvalue.data = (*keys)[i].salt->salt.data; + + ret = krb5_string_to_key_salt (context, + (*keys)[i].key.keytype, + password, + salt, + &(*keys)[i].key); + + if(ret) + break; + } + + if(ret) { + hdb_free_keys (context, *num_keys, *keys); + return ret; + } + return ret; +} diff --git a/source4/heimdal/lib/hdb/keytab.c b/source4/heimdal/lib/hdb/keytab.c new file mode 100644 index 0000000000..b2d1fec3d2 --- /dev/null +++ b/source4/heimdal/lib/hdb/keytab.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 1999 - 2002 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hdb_locl.h" + +/* keytab backend for HDB databases */ + +RCSID("$Id$"); + +struct hdb_data { + char *dbname; + char *mkey; +}; + +/* + * the format for HDB keytabs is: + * HDB:[database:file:mkey] + */ + +static krb5_error_code +hdb_resolve(krb5_context context, const char *name, krb5_keytab id) +{ + struct hdb_data *d; + const char *db, *mkey; + + d = malloc(sizeof(*d)); + if(d == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + db = name; + mkey = strrchr(name, ':'); + if(mkey == NULL || mkey[1] == '\0') { + if(*name == '\0') + d->dbname = NULL; + else { + d->dbname = strdup(name); + if(d->dbname == NULL) { + free(d); + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + } + d->mkey = NULL; + } else { + if((mkey - db) == 0) { + d->dbname = NULL; + } else { + d->dbname = malloc(mkey - db + 1); + if(d->dbname == NULL) { + free(d); + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + memmove(d->dbname, db, mkey - db); + d->dbname[mkey - db] = '\0'; + } + d->mkey = strdup(mkey + 1); + if(d->mkey == NULL) { + free(d->dbname); + free(d); + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + } + id->data = d; + return 0; +} + +static krb5_error_code +hdb_close(krb5_context context, krb5_keytab id) +{ + struct hdb_data *d = id->data; + + free(d->dbname); + free(d->mkey); + free(d); + return 0; +} + +static krb5_error_code +hdb_get_name(krb5_context context, + krb5_keytab id, + char *name, + size_t namesize) +{ + struct hdb_data *d = id->data; + + snprintf(name, namesize, "%s%s%s", + d->dbname ? d->dbname : "", + (d->dbname || d->mkey) ? ":" : "", + d->mkey ? d->mkey : ""); + return 0; +} + +static void +set_config (krb5_context context, + const krb5_config_binding *binding, + const char **dbname, + const char **mkey) +{ + *dbname = krb5_config_get_string(context, binding, "dbname", NULL); + *mkey = krb5_config_get_string(context, binding, "mkey_file", NULL); +} + +/* + * try to figure out the database (`dbname') and master-key (`mkey') + * that should be used for `principal'. + */ + +static void +find_db (krb5_context context, + const char **dbname, + const char **mkey, + krb5_const_principal principal) +{ + const krb5_config_binding *top_bind = NULL; + const krb5_config_binding *default_binding = NULL; + const krb5_config_binding *db; + krb5_realm *prealm = krb5_princ_realm(context, rk_UNCONST(principal)); + + *dbname = *mkey = NULL; + + while ((db = + krb5_config_get_next(context, + NULL, + &top_bind, + krb5_config_list, + "kdc", + "database", + NULL)) != NULL) { + const char *p; + + p = krb5_config_get_string (context, db, "realm", NULL); + if (p == NULL) { + if(default_binding) { + krb5_warnx(context, "WARNING: more than one realm-less " + "database specification"); + krb5_warnx(context, "WARNING: using the first encountered"); + } else + default_binding = db; + } else if (strcmp (*prealm, p) == 0) { + set_config (context, db, dbname, mkey); + break; + } + } + if (*dbname == NULL && default_binding != NULL) + set_config (context, default_binding, dbname, mkey); + if (*dbname == NULL) + *dbname = HDB_DEFAULT_DB; +} + +/* + * find the keytab entry in `id' for `principal, kvno, enctype' and return + * it in `entry'. return 0 or an error code + */ + +static krb5_error_code +hdb_get_entry(krb5_context context, + krb5_keytab id, + krb5_const_principal principal, + krb5_kvno kvno, + krb5_enctype enctype, + krb5_keytab_entry *entry) +{ + hdb_entry_ex ent; + krb5_error_code ret; + struct hdb_data *d = id->data; + int i; + HDB *db; + const char *dbname = d->dbname; + const char *mkey = d->mkey; + + memset(&ent, 0, sizeof(ent)); + + if (dbname == NULL) + find_db (context, &dbname, &mkey, principal); + + ret = hdb_create (context, &db, dbname); + if (ret) + return ret; + ret = hdb_set_master_keyfile (context, db, mkey); + if (ret) { + (*db->hdb_destroy)(context, db); + return ret; + } + + ret = (*db->hdb_open)(context, db, O_RDONLY, 0); + if (ret) { + (*db->hdb_destroy)(context, db); + return ret; + } + ret = (*db->hdb_fetch)(context, db, principal, + HDB_F_DECRYPT| + HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT, + &ent); + + if(ret == HDB_ERR_NOENTRY) { + ret = KRB5_KT_NOTFOUND; + goto out; + }else if(ret) + goto out; + + if(kvno && ent.entry.kvno != kvno) { + hdb_free_entry(context, &ent); + ret = KRB5_KT_NOTFOUND; + goto out; + } + if(enctype == 0) + if(ent.entry.keys.len > 0) + enctype = ent.entry.keys.val[0].key.keytype; + ret = KRB5_KT_NOTFOUND; + for(i = 0; i < ent.entry.keys.len; i++) { + if(ent.entry.keys.val[i].key.keytype == enctype) { + krb5_copy_principal(context, principal, &entry->principal); + entry->vno = ent.entry.kvno; + krb5_copy_keyblock_contents(context, + &ent.entry.keys.val[i].key, + &entry->keyblock); + ret = 0; + break; + } + } + hdb_free_entry(context, &ent); +out: + (*db->hdb_close)(context, db); + (*db->hdb_destroy)(context, db); + return ret; +} + +krb5_kt_ops hdb_kt_ops = { + "HDB", + hdb_resolve, + hdb_get_name, + hdb_close, + hdb_get_entry, + NULL, /* start_seq_get */ + NULL, /* next_entry */ + NULL, /* end_seq_get */ + NULL, /* add */ + NULL /* remove */ +}; diff --git a/source4/heimdal/lib/hdb/mkey.c b/source4/heimdal/lib/hdb/mkey.c new file mode 100644 index 0000000000..7d2958b4ac --- /dev/null +++ b/source4/heimdal/lib/hdb/mkey.c @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2000 - 2004 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hdb_locl.h" +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +RCSID("$Id$"); + +struct hdb_master_key_data { + krb5_keytab_entry keytab; + krb5_crypto crypto; + struct hdb_master_key_data *next; +}; + +void +hdb_free_master_key(krb5_context context, hdb_master_key mkey) +{ + struct hdb_master_key_data *ptr; + while(mkey) { + krb5_kt_free_entry(context, &mkey->keytab); + if (mkey->crypto) + krb5_crypto_destroy(context, mkey->crypto); + ptr = mkey; + mkey = mkey->next; + free(ptr); + } +} + +krb5_error_code +hdb_process_master_key(krb5_context context, + int kvno, krb5_keyblock *key, krb5_enctype etype, + hdb_master_key *mkey) +{ + krb5_error_code ret; + + *mkey = calloc(1, sizeof(**mkey)); + if(*mkey == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + (*mkey)->keytab.vno = kvno; + ret = krb5_parse_name(context, "K/M", &(*mkey)->keytab.principal); + if(ret) + goto fail; + ret = krb5_copy_keyblock_contents(context, key, &(*mkey)->keytab.keyblock); + if(ret) + goto fail; + if(etype != 0) + (*mkey)->keytab.keyblock.keytype = etype; + (*mkey)->keytab.timestamp = time(NULL); + ret = krb5_crypto_init(context, key, etype, &(*mkey)->crypto); + if(ret) + goto fail; + return 0; + fail: + hdb_free_master_key(context, *mkey); + *mkey = NULL; + return ret; +} + +krb5_error_code +hdb_add_master_key(krb5_context context, krb5_keyblock *key, + hdb_master_key *inout) +{ + int vno = 0; + hdb_master_key p; + krb5_error_code ret; + + for(p = *inout; p; p = p->next) + vno = max(vno, p->keytab.vno); + vno++; + ret = hdb_process_master_key(context, vno, key, 0, &p); + if(ret) + return ret; + p->next = *inout; + *inout = p; + return 0; +} + +static krb5_error_code +read_master_keytab(krb5_context context, const char *filename, + hdb_master_key *mkey) +{ + krb5_error_code ret; + krb5_keytab id; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + hdb_master_key p; + + ret = krb5_kt_resolve(context, filename, &id); + if(ret) + return ret; + + ret = krb5_kt_start_seq_get(context, id, &cursor); + if(ret) + goto out; + *mkey = NULL; + while(krb5_kt_next_entry(context, id, &entry, &cursor) == 0) { + p = calloc(1, sizeof(*p)); + if(p == NULL) { + krb5_kt_end_seq_get(context, id, &cursor); + ret = ENOMEM; + goto out; + } + p->keytab = entry; + ret = krb5_crypto_init(context, &p->keytab.keyblock, 0, &p->crypto); + p->next = *mkey; + *mkey = p; + } + krb5_kt_end_seq_get(context, id, &cursor); + out: + krb5_kt_close(context, id); + return ret; +} + +/* read a MIT master keyfile */ +static krb5_error_code +read_master_mit(krb5_context context, const char *filename, + hdb_master_key *mkey) +{ + int fd; + krb5_error_code ret; + krb5_storage *sp; + int16_t enctype; + krb5_keyblock key; + + fd = open(filename, O_RDONLY | O_BINARY); + if(fd < 0) { + int save_errno = errno; + krb5_set_error_message(context, save_errno, "failed to open %s: %s", + filename, strerror(save_errno)); + return save_errno; + } + sp = krb5_storage_from_fd(fd); + if(sp == NULL) { + close(fd); + return errno; + } + krb5_storage_set_flags(sp, KRB5_STORAGE_HOST_BYTEORDER); +#if 0 + /* could possibly use ret_keyblock here, but do it with more + checks for now */ + ret = krb5_ret_keyblock(sp, &key); +#else + ret = krb5_ret_int16(sp, &enctype); + if((htons(enctype) & 0xff00) == 0x3000) { + ret = HEIM_ERR_BAD_MKEY; + krb5_set_error_message(context, ret, "unknown keytype in %s: %#x, expected %#x", + filename, htons(enctype), 0x3000); + goto out; + } + key.keytype = enctype; + ret = krb5_ret_data(sp, &key.keyvalue); + if(ret) + goto out; +#endif + ret = hdb_process_master_key(context, 0, &key, 0, mkey); + krb5_free_keyblock_contents(context, &key); + out: + krb5_storage_free(sp); + close(fd); + return ret; +} + +/* read an old master key file */ +static krb5_error_code +read_master_encryptionkey(krb5_context context, const char *filename, + hdb_master_key *mkey) +{ + int fd; + krb5_keyblock key; + krb5_error_code ret; + unsigned char buf[256]; + ssize_t len; + size_t ret_len; + + fd = open(filename, O_RDONLY | O_BINARY); + if(fd < 0) { + int save_errno = errno; + krb5_set_error_message(context, save_errno, "failed to open %s: %s", + filename, strerror(save_errno)); + return save_errno; + } + + len = read(fd, buf, sizeof(buf)); + close(fd); + if(len < 0) { + int save_errno = errno; + krb5_set_error_message(context, save_errno, "error reading %s: %s", + filename, strerror(save_errno)); + return save_errno; + } + + ret = decode_EncryptionKey(buf, len, &key, &ret_len); + memset(buf, 0, sizeof(buf)); + if(ret) + return ret; + + /* Originally, the keytype was just that, and later it got changed + to des-cbc-md5, but we always used des in cfb64 mode. This + should cover all cases, but will break if someone has hacked + this code to really use des-cbc-md5 -- but then that's not my + problem. */ + if(key.keytype == KEYTYPE_DES || key.keytype == ETYPE_DES_CBC_MD5) + key.keytype = ETYPE_DES_CFB64_NONE; + + ret = hdb_process_master_key(context, 0, &key, 0, mkey); + krb5_free_keyblock_contents(context, &key); + return ret; +} + +/* read a krb4 /.k style file */ +static krb5_error_code +read_master_krb4(krb5_context context, const char *filename, + hdb_master_key *mkey) +{ + int fd; + krb5_keyblock key; + krb5_error_code ret; + unsigned char buf[256]; + ssize_t len; + + fd = open(filename, O_RDONLY | O_BINARY); + if(fd < 0) { + int save_errno = errno; + krb5_set_error_message(context, save_errno, "failed to open %s: %s", + filename, strerror(save_errno)); + return save_errno; + } + + len = read(fd, buf, sizeof(buf)); + close(fd); + if(len < 0) { + int save_errno = errno; + krb5_set_error_message(context, save_errno, "error reading %s: %s", + filename, strerror(save_errno)); + return save_errno; + } + if(len != 8) { + krb5_set_error_message(context, HEIM_ERR_EOF, + "bad contents of %s", filename); + return HEIM_ERR_EOF; /* XXX file might be too large */ + } + + memset(&key, 0, sizeof(key)); + key.keytype = ETYPE_DES_PCBC_NONE; + ret = krb5_data_copy(&key.keyvalue, buf, len); + memset(buf, 0, sizeof(buf)); + if(ret) + return ret; + + ret = hdb_process_master_key(context, 0, &key, 0, mkey); + krb5_free_keyblock_contents(context, &key); + return ret; +} + +krb5_error_code +hdb_read_master_key(krb5_context context, const char *filename, + hdb_master_key *mkey) +{ + FILE *f; + unsigned char buf[16]; + krb5_error_code ret; + + off_t len; + + *mkey = NULL; + + if(filename == NULL) + filename = HDB_DB_DIR "/m-key"; + + f = fopen(filename, "r"); + if(f == NULL) { + int save_errno = errno; + krb5_set_error_message(context, save_errno, "failed to open %s: %s", + filename, strerror(save_errno)); + return save_errno; + } + + if(fread(buf, 1, 2, f) != 2) { + fclose(f); + krb5_set_error_message(context, HEIM_ERR_EOF, "end of file reading %s", filename); + return HEIM_ERR_EOF; + } + + fseek(f, 0, SEEK_END); + len = ftell(f); + + if(fclose(f) != 0) + return errno; + + if(len < 0) + return errno; + + if(len == 8) { + ret = read_master_krb4(context, filename, mkey); + } else if(buf[0] == 0x30 && len <= 127 && buf[1] == len - 2) { + ret = read_master_encryptionkey(context, filename, mkey); + } else if(buf[0] == 5 && buf[1] >= 1 && buf[1] <= 2) { + ret = read_master_keytab(context, filename, mkey); + } else { + ret = read_master_mit(context, filename, mkey); + } + return ret; +} + +krb5_error_code +hdb_write_master_key(krb5_context context, const char *filename, + hdb_master_key mkey) +{ + krb5_error_code ret; + hdb_master_key p; + krb5_keytab kt; + + if(filename == NULL) + filename = HDB_DB_DIR "/m-key"; + + ret = krb5_kt_resolve(context, filename, &kt); + if(ret) + return ret; + + for(p = mkey; p; p = p->next) { + ret = krb5_kt_add_entry(context, kt, &p->keytab); + } + + krb5_kt_close(context, kt); + + return ret; +} + +hdb_master_key +_hdb_find_master_key(uint32_t *mkvno, hdb_master_key mkey) +{ + hdb_master_key ret = NULL; + while(mkey) { + if(ret == NULL && mkey->keytab.vno == 0) + ret = mkey; + if(mkvno == NULL) { + if(ret == NULL || mkey->keytab.vno > ret->keytab.vno) + ret = mkey; + } else if(mkey->keytab.vno == *mkvno) + return mkey; + mkey = mkey->next; + } + return ret; +} + +int +_hdb_mkey_version(hdb_master_key mkey) +{ + return mkey->keytab.vno; +} + +int +_hdb_mkey_decrypt(krb5_context context, hdb_master_key key, + krb5_key_usage usage, + void *ptr, size_t size, krb5_data *res) +{ + return krb5_decrypt(context, key->crypto, usage, + ptr, size, res); +} + +int +_hdb_mkey_encrypt(krb5_context context, hdb_master_key key, + krb5_key_usage usage, + const void *ptr, size_t size, krb5_data *res) +{ + return krb5_encrypt(context, key->crypto, usage, + ptr, size, res); +} + +krb5_error_code +hdb_unseal_key_mkey(krb5_context context, Key *k, hdb_master_key mkey) +{ + + krb5_error_code ret; + krb5_data res; + size_t keysize; + + hdb_master_key key; + + if(k->mkvno == NULL) + return 0; + + key = _hdb_find_master_key(k->mkvno, mkey); + + if (key == NULL) + return HDB_ERR_NO_MKEY; + + ret = _hdb_mkey_decrypt(context, key, HDB_KU_MKEY, + k->key.keyvalue.data, + k->key.keyvalue.length, + &res); + if(ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) { + /* try to decrypt with MIT key usage */ + ret = _hdb_mkey_decrypt(context, key, 0, + k->key.keyvalue.data, + k->key.keyvalue.length, + &res); + } + if (ret) + return ret; + + /* fixup keylength if the key got padded when encrypting it */ + ret = krb5_enctype_keysize(context, k->key.keytype, &keysize); + if (ret) { + krb5_data_free(&res); + return ret; + } + if (keysize > res.length) { + krb5_data_free(&res); + return KRB5_BAD_KEYSIZE; + } + + memset(k->key.keyvalue.data, 0, k->key.keyvalue.length); + free(k->key.keyvalue.data); + k->key.keyvalue = res; + k->key.keyvalue.length = keysize; + free(k->mkvno); + k->mkvno = NULL; + + return 0; +} + +krb5_error_code +hdb_unseal_keys_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey) +{ + int i; + + for(i = 0; i < ent->keys.len; i++){ + krb5_error_code ret; + + ret = hdb_unseal_key_mkey(context, &ent->keys.val[i], mkey); + if (ret) + return ret; + } + return 0; +} + +krb5_error_code +hdb_unseal_keys(krb5_context context, HDB *db, hdb_entry *ent) +{ + if (db->hdb_master_key_set == 0) + return 0; + return hdb_unseal_keys_mkey(context, ent, db->hdb_master_key); +} + +krb5_error_code +hdb_unseal_key(krb5_context context, HDB *db, Key *k) +{ + if (db->hdb_master_key_set == 0) + return 0; + return hdb_unseal_key_mkey(context, k, db->hdb_master_key); +} + +krb5_error_code +hdb_seal_key_mkey(krb5_context context, Key *k, hdb_master_key mkey) +{ + krb5_error_code ret; + krb5_data res; + hdb_master_key key; + + if(k->mkvno != NULL) + return 0; + + key = _hdb_find_master_key(k->mkvno, mkey); + + if (key == NULL) + return HDB_ERR_NO_MKEY; + + ret = _hdb_mkey_encrypt(context, key, HDB_KU_MKEY, + k->key.keyvalue.data, + k->key.keyvalue.length, + &res); + if (ret) + return ret; + + memset(k->key.keyvalue.data, 0, k->key.keyvalue.length); + free(k->key.keyvalue.data); + k->key.keyvalue = res; + + if (k->mkvno == NULL) { + k->mkvno = malloc(sizeof(*k->mkvno)); + if (k->mkvno == NULL) + return ENOMEM; + } + *k->mkvno = key->keytab.vno; + + return 0; +} + +krb5_error_code +hdb_seal_keys_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey) +{ + int i; + for(i = 0; i < ent->keys.len; i++){ + krb5_error_code ret; + + ret = hdb_seal_key_mkey(context, &ent->keys.val[i], mkey); + if (ret) + return ret; + } + return 0; +} + +krb5_error_code +hdb_seal_keys(krb5_context context, HDB *db, hdb_entry *ent) +{ + if (db->hdb_master_key_set == 0) + return 0; + + return hdb_seal_keys_mkey(context, ent, db->hdb_master_key); +} + +krb5_error_code +hdb_seal_key(krb5_context context, HDB *db, Key *k) +{ + if (db->hdb_master_key_set == 0) + return 0; + + return hdb_seal_key_mkey(context, k, db->hdb_master_key); +} + +krb5_error_code +hdb_set_master_key (krb5_context context, + HDB *db, + krb5_keyblock *key) +{ + krb5_error_code ret; + hdb_master_key mkey; + + ret = hdb_process_master_key(context, 0, key, 0, &mkey); + if (ret) + return ret; + db->hdb_master_key = mkey; +#if 0 /* XXX - why? */ + des_set_random_generator_seed(key.keyvalue.data); +#endif + db->hdb_master_key_set = 1; + return 0; +} + +krb5_error_code +hdb_set_master_keyfile (krb5_context context, + HDB *db, + const char *keyfile) +{ + hdb_master_key key; + krb5_error_code ret; + + ret = hdb_read_master_key(context, keyfile, &key); + if (ret) { + if (ret != ENOENT) + return ret; + krb5_clear_error_string(context); + return 0; + } + db->hdb_master_key = key; + db->hdb_master_key_set = 1; + return ret; +} + +krb5_error_code +hdb_clear_master_key (krb5_context context, + HDB *db) +{ + if (db->hdb_master_key_set) { + hdb_free_master_key(context, db->hdb_master_key); + db->hdb_master_key_set = 0; + } + return 0; +} diff --git a/source4/heimdal/lib/hdb/ndbm.c b/source4/heimdal/lib/hdb/ndbm.c new file mode 100644 index 0000000000..c4fc52e17f --- /dev/null +++ b/source4/heimdal/lib/hdb/ndbm.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hdb_locl.h" + +RCSID("$Id$"); + +#if HAVE_NDBM + +#if defined(HAVE_GDBM_NDBM_H) +#include <gdbm/ndbm.h> +#elif defined(HAVE_NDBM_H) +#include <ndbm.h> +#elif defined(HAVE_DBM_H) +#include <dbm.h> +#endif + +struct ndbm_db { + DBM *db; + int lock_fd; +}; + +static krb5_error_code +NDBM_destroy(krb5_context context, HDB *db) +{ + krb5_error_code ret; + + ret = hdb_clear_master_key (context, db); + free(db->hdb_name); + free(db); + return 0; +} + +static krb5_error_code +NDBM_lock(krb5_context context, HDB *db, int operation) +{ + struct ndbm_db *d = db->hdb_db; + return hdb_lock(d->lock_fd, operation); +} + +static krb5_error_code +NDBM_unlock(krb5_context context, HDB *db) +{ + struct ndbm_db *d = db->hdb_db; + return hdb_unlock(d->lock_fd); +} + +static krb5_error_code +NDBM_seq(krb5_context context, HDB *db, + unsigned flags, hdb_entry_ex *entry, int first) + +{ + struct ndbm_db *d = (struct ndbm_db *)db->hdb_db; + datum key, value; + krb5_data key_data, data; + krb5_error_code ret = 0; + + if(first) + key = dbm_firstkey(d->db); + else + key = dbm_nextkey(d->db); + if(key.dptr == NULL) + return HDB_ERR_NOENTRY; + key_data.data = key.dptr; + key_data.length = key.dsize; + ret = db->hdb_lock(context, db, HDB_RLOCK); + if(ret) return ret; + value = dbm_fetch(d->db, key); + db->hdb_unlock(context, db); + data.data = value.dptr; + data.length = value.dsize; + memset(entry, 0, sizeof(*entry)); + if(hdb_value2entry(context, &data, &entry->entry)) + return NDBM_seq(context, db, flags, entry, 0); + if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) { + ret = hdb_unseal_keys (context, db, &entry->entry); + if (ret) + hdb_free_entry (context, entry); + } + if (ret == 0 && entry->entry.principal == NULL) { + entry->entry.principal = malloc (sizeof(*entry->entry.principal)); + if (entry->entry.principal == NULL) { + hdb_free_entry (context, entry); + ret = ENOMEM; + krb5_set_error_message(context, ret, "malloc: out of memory"); + } else { + hdb_key2principal (context, &key_data, entry->entry.principal); + } + } + return ret; +} + + +static krb5_error_code +NDBM_firstkey(krb5_context context, HDB *db,unsigned flags,hdb_entry_ex *entry) +{ + return NDBM_seq(context, db, flags, entry, 1); +} + + +static krb5_error_code +NDBM_nextkey(krb5_context context, HDB *db, unsigned flags,hdb_entry_ex *entry) +{ + return NDBM_seq(context, db, flags, entry, 0); +} + +static krb5_error_code +NDBM_rename(krb5_context context, HDB *db, const char *new_name) +{ + /* XXX this function will break */ + struct ndbm_db *d = db->hdb_db; + + int ret; + char *old_dir, *old_pag, *new_dir, *new_pag; + char *new_lock; + int lock_fd; + + /* lock old and new databases */ + ret = db->hdb_lock(context, db, HDB_WLOCK); + if(ret) + return ret; + asprintf(&new_lock, "%s.lock", new_name); + if(new_lock == NULL) { + db->hdb_unlock(context, db); + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + lock_fd = open(new_lock, O_RDWR | O_CREAT, 0600); + if(lock_fd < 0) { + ret = errno; + db->hdb_unlock(context, db); + krb5_set_error_message(context, ret, "open(%s): %s", new_lock, + strerror(ret)); + free(new_lock); + return ret; + } + free(new_lock); + ret = hdb_lock(lock_fd, HDB_WLOCK); + if(ret) { + db->hdb_unlock(context, db); + close(lock_fd); + return ret; + } + + asprintf(&old_dir, "%s.dir", db->hdb_name); + asprintf(&old_pag, "%s.pag", db->hdb_name); + asprintf(&new_dir, "%s.dir", new_name); + asprintf(&new_pag, "%s.pag", new_name); + + ret = rename(old_dir, new_dir) || rename(old_pag, new_pag); + free(old_dir); + free(old_pag); + free(new_dir); + free(new_pag); + hdb_unlock(lock_fd); + db->hdb_unlock(context, db); + + if(ret) { + ret = errno; + close(lock_fd); + krb5_set_error_message(context, ret, "rename: %s", strerror(ret)); + return ret; + } + + close(d->lock_fd); + d->lock_fd = lock_fd; + + free(db->hdb_name); + db->hdb_name = strdup(new_name); + return 0; +} + +static krb5_error_code +NDBM__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply) +{ + struct ndbm_db *d = (struct ndbm_db *)db->hdb_db; + datum k, v; + int code; + + k.dptr = key.data; + k.dsize = key.length; + code = db->hdb_lock(context, db, HDB_RLOCK); + if(code) + return code; + v = dbm_fetch(d->db, k); + db->hdb_unlock(context, db); + if(v.dptr == NULL) + return HDB_ERR_NOENTRY; + + krb5_data_copy(reply, v.dptr, v.dsize); + return 0; +} + +static krb5_error_code +NDBM__put(krb5_context context, HDB *db, int replace, + krb5_data key, krb5_data value) +{ + struct ndbm_db *d = (struct ndbm_db *)db->hdb_db; + datum k, v; + int code; + + k.dptr = key.data; + k.dsize = key.length; + v.dptr = value.data; + v.dsize = value.length; + + code = db->hdb_lock(context, db, HDB_WLOCK); + if(code) + return code; + code = dbm_store(d->db, k, v, replace ? DBM_REPLACE : DBM_INSERT); + db->hdb_unlock(context, db); + if(code == 1) + return HDB_ERR_EXISTS; + if (code < 0) + return code; + return 0; +} + +static krb5_error_code +NDBM__del(krb5_context context, HDB *db, krb5_data key) +{ + struct ndbm_db *d = (struct ndbm_db *)db->hdb_db; + datum k; + int code; + krb5_error_code ret; + + k.dptr = key.data; + k.dsize = key.length; + ret = db->hdb_lock(context, db, HDB_WLOCK); + if(ret) return ret; + code = dbm_delete(d->db, k); + db->hdb_unlock(context, db); + if(code < 0) + return errno; + return 0; +} + + +static krb5_error_code +NDBM_close(krb5_context context, HDB *db) +{ + struct ndbm_db *d = db->hdb_db; + dbm_close(d->db); + close(d->lock_fd); + free(d); + return 0; +} + +static krb5_error_code +NDBM_open(krb5_context context, HDB *db, int flags, mode_t mode) +{ + krb5_error_code ret; + struct ndbm_db *d = malloc(sizeof(*d)); + char *lock_file; + + if(d == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + asprintf(&lock_file, "%s.lock", (char*)db->hdb_name); + if(lock_file == NULL) { + free(d); + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + d->db = dbm_open((char*)db->hdb_name, flags, mode); + if(d->db == NULL){ + ret = errno; + free(d); + free(lock_file); + krb5_set_error_message(context, ret, "dbm_open(%s): %s", db->hdb_name, + strerror(ret)); + return ret; + } + d->lock_fd = open(lock_file, O_RDWR | O_CREAT, 0600); + if(d->lock_fd < 0){ + ret = errno; + dbm_close(d->db); + free(d); + krb5_set_error_message(context, ret, "open(%s): %s", lock_file, + strerror(ret)); + free(lock_file); + return ret; + } + free(lock_file); + db->hdb_db = d; + if((flags & O_ACCMODE) == O_RDONLY) + ret = hdb_check_db_format(context, db); + else + ret = hdb_init_db(context, db); + if(ret == HDB_ERR_NOENTRY) + return 0; + if (ret) { + NDBM_close(context, db); + krb5_set_error_message(context, ret, "hdb_open: failed %s database %s", + (flags & O_ACCMODE) == O_RDONLY ? + "checking format of" : "initialize", + db->hdb_name); + } + return ret; +} + +krb5_error_code +hdb_ndbm_create(krb5_context context, HDB **db, + const char *filename) +{ + *db = calloc(1, sizeof(**db)); + if (*db == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + + (*db)->hdb_db = NULL; + (*db)->hdb_name = strdup(filename); + if ((*db)->hdb_name == NULL) { + free(*db); + *db = NULL; + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + (*db)->hdb_master_key_set = 0; + (*db)->hdb_openp = 0; + (*db)->hdb_open = NDBM_open; + (*db)->hdb_close = NDBM_close; + (*db)->hdb_fetch = _hdb_fetch; + (*db)->hdb_store = _hdb_store; + (*db)->hdb_remove = _hdb_remove; + (*db)->hdb_firstkey = NDBM_firstkey; + (*db)->hdb_nextkey= NDBM_nextkey; + (*db)->hdb_lock = NDBM_lock; + (*db)->hdb_unlock = NDBM_unlock; + (*db)->hdb_rename = NDBM_rename; + (*db)->hdb__get = NDBM__get; + (*db)->hdb__put = NDBM__put; + (*db)->hdb__del = NDBM__del; + (*db)->hdb_destroy = NDBM_destroy; + return 0; +} + +#endif /* HAVE_NDBM */ |