Home | History | Annotate | Line # | Download | only in hdb
      1 /*	$NetBSD: hdb.c,v 1.5 2023/06/19 21:41:43 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997 - 2008 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  *
     14  * 1. Redistributions of source code must retain the above copyright
     15  *    notice, this list of conditions and the following disclaimer.
     16  *
     17  * 2. Redistributions in binary form must reproduce the above copyright
     18  *    notice, this list of conditions and the following disclaimer in the
     19  *    documentation and/or other materials provided with the distribution.
     20  *
     21  * 3. Neither the name of the Institute nor the names of its contributors
     22  *    may be used to endorse or promote products derived from this software
     23  *    without specific prior written permission.
     24  *
     25  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     35  * SUCH DAMAGE.
     36  */
     37 
     38 #include "krb5_locl.h"
     39 #include "hdb_locl.h"
     40 
     41 #ifdef HAVE_DLFCN_H
     42 #include <dlfcn.h>
     43 #endif
     44 
     45 /*! @mainpage Heimdal database backend library
     46  *
     47  * @section intro Introduction
     48  *
     49  * Heimdal libhdb library provides the backend support for Heimdal kdc
     50  * and kadmind. Its here where plugins for diffrent database engines
     51  * can be pluged in and extend support for here Heimdal get the
     52  * principal and policy data from.
     53  *
     54  * Example of Heimdal backend are:
     55  * - Berkeley DB 1.85
     56  * - Berkeley DB 3.0
     57  * - Berkeley DB 4.0
     58  * - New Berkeley DB
     59  * - LDAP
     60  *
     61  *
     62  * The project web page: http://www.h5l.org/
     63  *
     64  */
     65 
     66 const int hdb_interface_version = HDB_INTERFACE_VERSION;
     67 
     68 static struct hdb_method methods[] = {
     69     /* "db:" should be db3 if we have db3, or db1 if we have db1 */
     70 #if HAVE_DB3
     71     { HDB_INTERFACE_VERSION, NULL, NULL, "db:",	hdb_db3_create},
     72 #elif HAVE_DB1
     73     { HDB_INTERFACE_VERSION, NULL, NULL, "db:",	hdb_db1_create},
     74 #endif
     75 #if HAVE_DB1
     76     { HDB_INTERFACE_VERSION, NULL, NULL, "db1:",	hdb_db1_create},
     77 #endif
     78 #if HAVE_DB3
     79     { HDB_INTERFACE_VERSION, NULL, NULL, "db3:",	hdb_db3_create},
     80 #endif
     81 #if HAVE_DB1
     82     { HDB_INTERFACE_VERSION, NULL, NULL, "mit-db:",	hdb_mitdb_create},
     83 #endif
     84 #if HAVE_LMDB
     85     { HDB_INTERFACE_VERSION, NULL, NULL, "mdb:",	hdb_mdb_create},
     86     { HDB_INTERFACE_VERSION, NULL, NULL, "lmdb:",	hdb_mdb_create},
     87 #endif
     88 #if HAVE_NDBM
     89     { HDB_INTERFACE_VERSION, NULL, NULL, "ndbm:",	hdb_ndbm_create},
     90 #endif
     91     { HDB_INTERFACE_VERSION, NULL, NULL, "keytab:",	hdb_keytab_create},
     92 #if defined(OPENLDAP) && !defined(OPENLDAP_MODULE)
     93     { HDB_INTERFACE_VERSION, NULL, NULL, "ldap:",	hdb_ldap_create},
     94     { HDB_INTERFACE_VERSION, NULL, NULL, "ldapi:",	hdb_ldapi_create},
     95 #elif defined(OPENLDAP)
     96     { HDB_INTERFACE_VERSION, NULL, NULL, "ldap:",	NULL},
     97     { HDB_INTERFACE_VERSION, NULL, NULL, "ldapi:",	NULL},
     98 #endif
     99 #ifdef HAVE_SQLITE3
    100     { HDB_INTERFACE_VERSION, NULL, NULL, "sqlite:", hdb_sqlite_create},
    101 #endif
    102     { 0, NULL, NULL, NULL, NULL}
    103 };
    104 
    105 /*
    106  * It'd be nice if we could try opening an HDB with each supported
    107  * backend until one works or all fail.  It may not be possible for all
    108  * flavors, but where it's possible we should.
    109  */
    110 #if defined(HAVE_LMDB)
    111 static struct hdb_method default_dbmethod =
    112     { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_mdb_create };
    113 #elif defined(HAVE_DB3)
    114 static struct hdb_method default_dbmethod =
    115     { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_db3_create };
    116 #elif defined(HAVE_DB1)
    117 static struct hdb_method default_dbmethod =
    118     { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_db1_create };
    119 #elif defined(HAVE_NDBM)
    120 static struct hdb_method default_dbmethod =
    121     { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_ndbm_create };
    122 #endif
    123 
    124 const Keys *
    125 hdb_kvno2keys(krb5_context context,
    126 	      const hdb_entry *e,
    127 	      krb5_kvno kvno)
    128 {
    129     HDB_Ext_KeySet *hist_keys;
    130     HDB_extension *extp;
    131     size_t i;
    132 
    133     if (kvno == 0)
    134 	return &e->keys;
    135 
    136     extp = hdb_find_extension(e, choice_HDB_extension_data_hist_keys);
    137     if (extp == NULL)
    138 	return 0;
    139 
    140     hist_keys = &extp->data.u.hist_keys;
    141     for (i = 0; i < hist_keys->len; i++) {
    142 	if (hist_keys->val[i].kvno == kvno)
    143 	    return &hist_keys->val[i].keys;
    144     }
    145 
    146     return NULL;
    147 }
    148 
    149 krb5_error_code
    150 hdb_next_enctype2key(krb5_context context,
    151 		     const hdb_entry *e,
    152 		     const Keys *keyset,
    153 		     krb5_enctype enctype,
    154 		     Key **key)
    155 {
    156     const Keys *keys = keyset ? keyset : &e->keys;
    157     Key *k;
    158 
    159     for (k = *key ? (*key) + 1 : keys->val; k < keys->val + keys->len; k++) {
    160 	if(k->key.keytype == enctype){
    161 	    *key = k;
    162 	    return 0;
    163 	}
    164     }
    165     krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP,
    166 			   "No next enctype %d for hdb-entry",
    167 			  (int)enctype);
    168     return KRB5_PROG_ETYPE_NOSUPP; /* XXX */
    169 }
    170 
    171 krb5_error_code
    172 hdb_enctype2key(krb5_context context,
    173 		hdb_entry *e,
    174 		const Keys *keyset,
    175 		krb5_enctype enctype,
    176 		Key **key)
    177 {
    178     *key = NULL;
    179     return hdb_next_enctype2key(context, e, keyset, enctype, key);
    180 }
    181 
    182 void
    183 hdb_free_key(Key *key)
    184 {
    185     memset(key->key.keyvalue.data,
    186 	   0,
    187 	   key->key.keyvalue.length);
    188     free_Key(key);
    189     free(key);
    190 }
    191 
    192 
    193 krb5_error_code
    194 hdb_lock(int fd, int operation)
    195 {
    196     int i, code = 0;
    197 
    198     for(i = 0; i < 3; i++){
    199 	code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB);
    200 	if(code == 0 || errno != EWOULDBLOCK)
    201 	    break;
    202 	sleep(1);
    203     }
    204     if(code == 0)
    205 	return 0;
    206     if(errno == EWOULDBLOCK)
    207 	return HDB_ERR_DB_INUSE;
    208     return HDB_ERR_CANT_LOCK_DB;
    209 }
    210 
    211 krb5_error_code
    212 hdb_unlock(int fd)
    213 {
    214     int code;
    215     code = flock(fd, LOCK_UN);
    216     if(code)
    217 	return 4711 /* XXX */;
    218     return 0;
    219 }
    220 
    221 void
    222 hdb_free_entry(krb5_context context, hdb_entry_ex *ent)
    223 {
    224     Key *k;
    225     size_t i;
    226 
    227     if (ent->free_entry)
    228 	(*ent->free_entry)(context, ent);
    229 
    230     for(i = 0; i < ent->entry.keys.len; i++) {
    231 	k = &ent->entry.keys.val[i];
    232 
    233 	memset (k->key.keyvalue.data, 0, k->key.keyvalue.length);
    234     }
    235     free_hdb_entry(&ent->entry);
    236 }
    237 
    238 krb5_error_code
    239 hdb_foreach(krb5_context context,
    240 	    HDB *db,
    241 	    unsigned flags,
    242 	    hdb_foreach_func_t func,
    243 	    void *data)
    244 {
    245     krb5_error_code ret;
    246     hdb_entry_ex entry;
    247     ret = db->hdb_firstkey(context, db, flags, &entry);
    248     if (ret == 0)
    249 	krb5_clear_error_message(context);
    250     while(ret == 0){
    251 	ret = (*func)(context, db, &entry, data);
    252 	hdb_free_entry(context, &entry);
    253 	if(ret == 0)
    254 	    ret = db->hdb_nextkey(context, db, flags, &entry);
    255     }
    256     if(ret == HDB_ERR_NOENTRY)
    257 	ret = 0;
    258     return ret;
    259 }
    260 
    261 krb5_error_code
    262 hdb_check_db_format(krb5_context context, HDB *db)
    263 {
    264     krb5_data tag;
    265     krb5_data version;
    266     krb5_error_code ret, ret2;
    267     unsigned ver;
    268     int foo;
    269 
    270     ret = db->hdb_lock(context, db, HDB_RLOCK);
    271     if (ret)
    272 	return ret;
    273 
    274     tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
    275     tag.length = strlen(tag.data);
    276     ret = (*db->hdb__get)(context, db, tag, &version);
    277     ret2 = db->hdb_unlock(context, db);
    278     if(ret)
    279 	return ret;
    280     if (ret2)
    281 	return ret2;
    282     foo = sscanf(version.data, "%u", &ver);
    283     krb5_data_free (&version);
    284     if (foo != 1)
    285 	return HDB_ERR_BADVERSION;
    286     if(ver != HDB_DB_FORMAT)
    287 	return HDB_ERR_BADVERSION;
    288     return 0;
    289 }
    290 
    291 krb5_error_code
    292 hdb_init_db(krb5_context context, HDB *db)
    293 {
    294     krb5_error_code ret, ret2;
    295     krb5_data tag;
    296     krb5_data version;
    297     char ver[32];
    298 
    299     ret = hdb_check_db_format(context, db);
    300     if(ret != HDB_ERR_NOENTRY)
    301 	return ret;
    302 
    303     ret = db->hdb_lock(context, db, HDB_WLOCK);
    304     if (ret)
    305 	return ret;
    306 
    307     tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
    308     tag.length = strlen(tag.data);
    309     snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT);
    310     version.data = ver;
    311     version.length = strlen(version.data) + 1; /* zero terminated */
    312     ret = (*db->hdb__put)(context, db, 0, tag, version);
    313     ret2 = db->hdb_unlock(context, db);
    314     if (ret) {
    315 	if (ret2)
    316 	    krb5_clear_error_message(context);
    317 	return ret;
    318     }
    319     return ret2;
    320 }
    321 
    322 /*
    323  * find the relevant method for `filename', returning a pointer to the
    324  * rest in `rest'.
    325  * return NULL if there's no such method.
    326  */
    327 
    328 static const struct hdb_method *
    329 find_method (const char *filename, const char **rest)
    330 {
    331     const struct hdb_method *h;
    332 
    333     for (h = methods; h->prefix != NULL; ++h) {
    334 	if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0) {
    335 	    *rest = filename + strlen(h->prefix);
    336 	    return h;
    337 	}
    338     }
    339 #if defined(HAVE_DB1) || defined(HAVE_DB3) || defined(HAVE_LMDB) || defined(HAVE_NDBM)
    340     if (strncmp(filename, "/", sizeof("/") - 1) == 0
    341 	|| strncmp(filename, "./", sizeof("./") - 1) == 0
    342 	|| strncmp(filename, "../", sizeof("../") - 1) == 0
    343 #ifdef WIN32
    344         || strncmp(filename, "\\\\", sizeof("\\\\") - 1)
    345         || (isalpha(filename[0]) && filename[1] == ':')
    346 #endif
    347         )
    348     {
    349 	*rest = filename;
    350 	return &default_dbmethod;
    351     }
    352 #endif
    353 
    354     return NULL;
    355 }
    356 
    357 struct cb_s {
    358     const char *residual;
    359     const char *filename;
    360     const struct hdb_method *h;
    361 };
    362 
    363 static krb5_error_code KRB5_LIB_CALL
    364 callback(krb5_context context, const void *plug, void *plugctx, void *userctx)
    365 {
    366     const struct hdb_method *h = (const struct hdb_method *)plug;
    367     struct cb_s *cb_ctx = (struct cb_s *)userctx;
    368 
    369     if (strncmp(cb_ctx->filename, h->prefix, strlen(h->prefix)) == 0) {
    370 	cb_ctx->residual = cb_ctx->filename + strlen(h->prefix) + 1;
    371 	cb_ctx->h = h;
    372 	return 0;
    373     }
    374    return KRB5_PLUGIN_NO_HANDLE;
    375 }
    376 
    377 static char *
    378 make_sym(const char *prefix)
    379 {
    380     char *s, *sym;
    381 
    382     errno = 0;
    383     if (prefix == NULL || prefix[0] == '\0')
    384         return NULL;
    385     if ((s = strdup(prefix)) == NULL)
    386         return NULL;
    387     if (strchr(s, ':') != NULL)
    388         *strchr(s, ':') = '\0';
    389     if (asprintf(&sym, "hdb_%s_interface", s) == -1)
    390         sym = NULL;
    391     free(s);
    392     return sym;
    393 }
    394 
    395 krb5_error_code
    396 hdb_list_builtin(krb5_context context, char **list)
    397 {
    398     const struct hdb_method *h;
    399     size_t len = 0;
    400     char *buf = NULL;
    401 
    402     for (h = methods; h->prefix != NULL; ++h) {
    403 	if (h->prefix[0] == '\0')
    404 	    continue;
    405 	len += strlen(h->prefix) + 2;
    406     }
    407 
    408     len += 1;
    409     buf = malloc(len);
    410     if (buf == NULL) {
    411 	return krb5_enomem(context);
    412     }
    413     buf[0] = '\0';
    414 
    415     for (h = methods; h->prefix != NULL; ++h) {
    416         if (h->create == NULL) {
    417             struct cb_s cb_ctx;
    418             char *f;
    419             char *sym;
    420 
    421             /* Try loading the plugin */
    422             if (asprintf(&f, "%sfoo", h->prefix) == -1)
    423                 f = NULL;
    424             if ((sym = make_sym(h->prefix)) == NULL) {
    425                 free(buf);
    426                 free(f);
    427                 return krb5_enomem(context);
    428             }
    429             cb_ctx.filename = f;
    430             cb_ctx.residual = NULL;
    431             cb_ctx.h = NULL;
    432             (void)_krb5_plugin_run_f(context, "krb5", sym,
    433                                      HDB_INTERFACE_VERSION, 0, &cb_ctx,
    434                                      callback);
    435             free(f);
    436             free(sym);
    437             if (cb_ctx.h == NULL || cb_ctx.h->create == NULL)
    438                 continue;
    439         }
    440 	if (h != methods)
    441 	    strlcat(buf, ", ", len);
    442 	strlcat(buf, h->prefix, len);
    443     }
    444     *list = buf;
    445     return 0;
    446 }
    447 
    448 krb5_error_code
    449 _hdb_keytab2hdb_entry(krb5_context context,
    450 		      const krb5_keytab_entry *ktentry,
    451 		      hdb_entry_ex *entry)
    452 {
    453     entry->entry.kvno = ktentry->vno;
    454     entry->entry.created_by.time = ktentry->timestamp;
    455 
    456     entry->entry.keys.val = calloc(1, sizeof(entry->entry.keys.val[0]));
    457     if (entry->entry.keys.val == NULL)
    458 	return ENOMEM;
    459     entry->entry.keys.len = 1;
    460 
    461     entry->entry.keys.val[0].mkvno = NULL;
    462     entry->entry.keys.val[0].salt = NULL;
    463 
    464     return krb5_copy_keyblock_contents(context,
    465 				       &ktentry->keyblock,
    466 				       &entry->entry.keys.val[0].key);
    467 }
    468 
    469 /**
    470  * Create a handle for a Kerberos database
    471  *
    472  * Create a handle for a Kerberos database backend specified by a
    473  * filename.  Doesn't create a file if its doesn't exists, you have to
    474  * use O_CREAT to tell the backend to create the file.
    475  */
    476 
    477 krb5_error_code
    478 hdb_create(krb5_context context, HDB **db, const char *filename)
    479 {
    480     struct cb_s cb_ctx;
    481 
    482     if (filename == NULL)
    483 	filename = HDB_DEFAULT_DB;
    484     cb_ctx.h = find_method (filename, &cb_ctx.residual);
    485     cb_ctx.filename = filename;
    486 
    487     if (cb_ctx.h == NULL || cb_ctx.h->create == NULL) {
    488         char *sym;
    489 
    490         if ((sym = make_sym(filename)) == NULL)
    491             return krb5_enomem(context);
    492 
    493         (void)_krb5_plugin_run_f(context, "krb5", sym, HDB_INTERFACE_VERSION,
    494                                  0, &cb_ctx, callback);
    495 
    496         free(sym);
    497     }
    498     if (cb_ctx.h == NULL)
    499 	krb5_errx(context, 1, "No database support for %s", cb_ctx.filename);
    500     return (*cb_ctx.h->create)(context, db, cb_ctx.residual);
    501 }
    502