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