xref: /netbsd/crypto/external/bsd/heimdal/dist/lib/hdb/hdb.c (revision 25596981)
1 /*	$NetBSD: hdb.c,v 1.5 2023/06/19 21:41:43 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
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 *
hdb_kvno2keys(krb5_context context,const hdb_entry * e,krb5_kvno kvno)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
hdb_next_enctype2key(krb5_context context,const hdb_entry * e,const Keys * keyset,krb5_enctype enctype,Key ** key)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
hdb_enctype2key(krb5_context context,hdb_entry * e,const Keys * keyset,krb5_enctype enctype,Key ** key)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
hdb_free_key(Key * key)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
hdb_lock(int fd,int operation)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
hdb_unlock(int fd)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
hdb_free_entry(krb5_context context,hdb_entry_ex * ent)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
hdb_foreach(krb5_context context,HDB * db,unsigned flags,hdb_foreach_func_t func,void * data)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
hdb_check_db_format(krb5_context context,HDB * db)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
hdb_init_db(krb5_context context,HDB * db)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 *
find_method(const char * filename,const char ** rest)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
callback(krb5_context context,const void * plug,void * plugctx,void * userctx)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 *
make_sym(const char * prefix)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
hdb_list_builtin(krb5_context context,char ** list)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
_hdb_keytab2hdb_entry(krb5_context context,const krb5_keytab_entry * ktentry,hdb_entry_ex * entry)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
hdb_create(krb5_context context,HDB ** db,const char * filename)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