b533fc5952bd7288ecc9348228ce8b1824fc25ec
[metze/samba/wip.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 RCSID("$Id$");
37
38 #ifdef HAVE_DLFCN_H
39 #include <dlfcn.h>
40 #endif
41
42 static struct hdb_method methods[] = {
43 #if HAVE_DB1 || HAVE_DB3
44     { HDB_INTERFACE_VERSION, "db:",     hdb_db_create},
45 #endif
46 #if HAVE_NDBM
47     { HDB_INTERFACE_VERSION, "ndbm:",   hdb_ndbm_create},
48 #endif
49 #if defined(OPENLDAP) && !defined(OPENLDAP_MODULE)
50     { HDB_INTERFACE_VERSION, "ldap:",   hdb_ldap_create},
51     { HDB_INTERFACE_VERSION, "ldapi:",  hdb_ldapi_create},
52 #endif
53     {0, NULL,   NULL}
54 };
55
56 #if HAVE_DB1 || HAVE_DB3
57 static struct hdb_method dbmetod =
58     { HDB_INTERFACE_VERSION, "", hdb_db_create };
59 #elif defined(HAVE_NDBM)
60 static struct hdb_method dbmetod =
61     { HDB_INTERFACE_VERSION, "", hdb_ndbm_create };
62 #endif
63
64
65 krb5_error_code
66 hdb_next_enctype2key(krb5_context context,
67                      const hdb_entry *e,
68                      krb5_enctype enctype,
69                      Key **key)
70 {
71     Key *k;
72
73     for (k = *key ? (*key) + 1 : e->keys.val;
74          k < e->keys.val + e->keys.len;
75          k++)
76     {
77         if(k->key.keytype == enctype){
78             *key = k;
79             return 0;
80         }
81     }
82     krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP,
83                            "No next enctype %d for hdb-entry",
84                           (int)enctype);
85     return KRB5_PROG_ETYPE_NOSUPP; /* XXX */
86 }
87
88 krb5_error_code
89 hdb_enctype2key(krb5_context context,
90                 hdb_entry *e,
91                 krb5_enctype enctype,
92                 Key **key)
93 {
94     *key = NULL;
95     return hdb_next_enctype2key(context, e, enctype, key);
96 }
97
98 void
99 hdb_free_key(Key *key)
100 {
101     memset(key->key.keyvalue.data,
102            0,
103            key->key.keyvalue.length);
104     free_Key(key);
105     free(key);
106 }
107
108
109 krb5_error_code
110 hdb_lock(int fd, int operation)
111 {
112     int i, code = 0;
113
114     for(i = 0; i < 3; i++){
115         code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB);
116         if(code == 0 || errno != EWOULDBLOCK)
117             break;
118         sleep(1);
119     }
120     if(code == 0)
121         return 0;
122     if(errno == EWOULDBLOCK)
123         return HDB_ERR_DB_INUSE;
124     return HDB_ERR_CANT_LOCK_DB;
125 }
126
127 krb5_error_code
128 hdb_unlock(int fd)
129 {
130     int code;
131     code = flock(fd, LOCK_UN);
132     if(code)
133         return 4711 /* XXX */;
134     return 0;
135 }
136
137 void
138 hdb_free_entry(krb5_context context, hdb_entry_ex *ent)
139 {
140     int i;
141
142     if (ent->free_entry)
143         (*ent->free_entry)(context, ent);
144
145     for(i = 0; i < ent->entry.keys.len; ++i) {
146         Key *k = &ent->entry.keys.val[i];
147
148         memset (k->key.keyvalue.data, 0, k->key.keyvalue.length);
149     }
150     free_hdb_entry(&ent->entry);
151 }
152
153 krb5_error_code
154 hdb_foreach(krb5_context context,
155             HDB *db,
156             unsigned flags,
157             hdb_foreach_func_t func,
158             void *data)
159 {
160     krb5_error_code ret;
161     hdb_entry_ex entry;
162     ret = db->hdb_firstkey(context, db, flags, &entry);
163     if (ret == 0)
164         krb5_clear_error_message(context);
165     while(ret == 0){
166         ret = (*func)(context, db, &entry, data);
167         hdb_free_entry(context, &entry);
168         if(ret == 0)
169             ret = db->hdb_nextkey(context, db, flags, &entry);
170     }
171     if(ret == HDB_ERR_NOENTRY)
172         ret = 0;
173     return ret;
174 }
175
176 krb5_error_code
177 hdb_check_db_format(krb5_context context, HDB *db)
178 {
179     krb5_data tag;
180     krb5_data version;
181     krb5_error_code ret, ret2;
182     unsigned ver;
183     int foo;
184
185     ret = db->hdb_lock(context, db, HDB_RLOCK);
186     if (ret)
187         return ret;
188
189     tag.data = HDB_DB_FORMAT_ENTRY;
190     tag.length = strlen(tag.data);
191     ret = (*db->hdb__get)(context, db, tag, &version);
192     ret2 = db->hdb_unlock(context, db);
193     if(ret)
194         return ret;
195     if (ret2)
196         return ret2;
197     foo = sscanf(version.data, "%u", &ver);
198     krb5_data_free (&version);
199     if (foo != 1)
200         return HDB_ERR_BADVERSION;
201     if(ver != HDB_DB_FORMAT)
202         return HDB_ERR_BADVERSION;
203     return 0;
204 }
205
206 krb5_error_code
207 hdb_init_db(krb5_context context, HDB *db)
208 {
209     krb5_error_code ret, ret2;
210     krb5_data tag;
211     krb5_data version;
212     char ver[32];
213
214     ret = hdb_check_db_format(context, db);
215     if(ret != HDB_ERR_NOENTRY)
216         return ret;
217
218     ret = db->hdb_lock(context, db, HDB_WLOCK);
219     if (ret)
220         return ret;
221
222     tag.data = HDB_DB_FORMAT_ENTRY;
223     tag.length = strlen(tag.data);
224     snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT);
225     version.data = ver;
226     version.length = strlen(version.data) + 1; /* zero terminated */
227     ret = (*db->hdb__put)(context, db, 0, tag, version);
228     ret2 = db->hdb_unlock(context, db);
229     if (ret) {
230         if (ret2)
231             krb5_clear_error_message(context);
232         return ret;
233     }
234     return ret2;
235 }
236
237 #ifdef HAVE_DLOPEN
238
239  /*
240  * Load a dynamic backend from /usr/heimdal/lib/hdb_NAME.so,
241  * looking for the hdb_NAME_create symbol.
242  */
243
244 static const struct hdb_method *
245 find_dynamic_method (krb5_context context,
246                      const char *filename,
247                      const char **rest)
248 {
249     static struct hdb_method method;
250     struct hdb_so_method *mso;
251     char *prefix, *path, *symbol;
252     const char *p;
253     void *dl;
254     size_t len;
255
256     p = strchr(filename, ':');
257
258     /* if no prefix, don't know what module to load, just ignore it */
259     if (p == NULL)
260         return NULL;
261
262     len = p - filename;
263     *rest = filename + len + 1;
264
265     prefix = malloc(len + 1);
266     if (prefix == NULL)
267         krb5_errx(context, 1, "out of memory");
268     strlcpy(prefix, filename, len + 1);
269
270     if (asprintf(&path, LIBDIR "/hdb_%s.so", prefix) == -1)
271         krb5_errx(context, 1, "out of memory");
272
273 #ifndef RTLD_NOW
274 #define RTLD_NOW 0
275 #endif
276 #ifndef RTLD_GLOBAL
277 #define RTLD_GLOBAL 0
278 #endif
279
280     dl = dlopen(path, RTLD_NOW | RTLD_GLOBAL);
281     if (dl == NULL) {
282         krb5_warnx(context, "error trying to load dynamic module %s: %s\n",
283                    path, dlerror());
284         free(prefix);
285         free(path);
286         return NULL;
287     }
288
289     if (asprintf(&symbol, "hdb_%s_interface", prefix) == -1)
290         krb5_errx(context, 1, "out of memory");
291         
292     mso = dlsym(dl, symbol);
293     if (mso == NULL) {
294         krb5_warnx(context, "error finding symbol %s in %s: %s\n",
295                    symbol, path, dlerror());
296         dlclose(dl);
297         free(symbol);
298         free(prefix);
299         free(path);
300         return NULL;
301     }
302     free(path);
303     free(symbol);
304
305     if (mso->version != HDB_INTERFACE_VERSION) {
306         krb5_warnx(context,
307                    "error wrong version in shared module %s "
308                    "version: %d should have been %d\n",
309                    prefix, mso->version, HDB_INTERFACE_VERSION);
310         dlclose(dl);
311         free(prefix);
312         return NULL;
313     }
314
315     if (mso->create == NULL) {
316         krb5_errx(context, 1,
317                   "no entry point function in shared mod %s ",
318                    prefix);
319         dlclose(dl);
320         free(prefix);
321         return NULL;
322     }
323
324     method.create = mso->create;
325     method.prefix = prefix;
326
327     return &method;
328 }
329 #endif /* HAVE_DLOPEN */
330
331 /*
332  * find the relevant method for `filename', returning a pointer to the
333  * rest in `rest'.
334  * return NULL if there's no such method.
335  */
336
337 static const struct hdb_method *
338 find_method (const char *filename, const char **rest)
339 {
340     const struct hdb_method *h;
341
342     for (h = methods; h->prefix != NULL; ++h) {
343         if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0) {
344             *rest = filename + strlen(h->prefix);
345             return h;
346         }
347     }
348 #if defined(HAVE_DB1) || defined(HAVE_DB3) || defined(HAVE_NDBM)
349     if (strncmp(filename, "/", 1) == 0
350         || strncmp(filename, "./", 2) == 0
351         || strncmp(filename, "../", 3) == 0)
352     {
353         *rest = filename;
354         return &dbmetod;
355     }
356 #endif
357
358     return NULL;
359 }
360
361 krb5_error_code
362 hdb_list_builtin(krb5_context context, char **list)
363 {
364     const struct hdb_method *h;
365     size_t len = 0;
366     char *buf = NULL;
367
368     for (h = methods; h->prefix != NULL; ++h) {
369         if (h->prefix[0] == '\0')
370             continue;
371         len += strlen(h->prefix) + 2;
372     }
373
374     len += 1;
375     buf = malloc(len);
376     if (buf == NULL) {
377         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
378         return ENOMEM;
379     }
380     buf[0] = '\0';
381
382     for (h = methods; h->prefix != NULL; ++h) {
383         if (h != methods)
384             strlcat(buf, ", ", len);
385         strlcat(buf, h->prefix, len);
386     }
387     *list = buf;
388     return 0;
389 }
390
391 krb5_error_code
392 hdb_create(krb5_context context, HDB **db, const char *filename)
393 {
394     const struct hdb_method *h;
395     const char *residual;
396     krb5_error_code ret;
397     struct krb5_plugin *list = NULL, *e;
398
399     if(filename == NULL)
400         filename = HDB_DEFAULT_DB;
401     krb5_add_et_list(context, initialize_hdb_error_table_r);
402     h = find_method (filename, &residual);
403
404     if (h == NULL) {
405             ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, "hdb", &list);
406             if(ret == 0 && list != NULL) {
407                     for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) {
408                             h = _krb5_plugin_get_symbol(e);
409                             if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0
410                                 && h->interface_version == HDB_INTERFACE_VERSION) {
411                                     residual = filename + strlen(h->prefix);
412                                     break;
413                             }
414                     }
415                     if (e == NULL) {
416                             h = NULL;
417                             _krb5_plugin_free(list);
418                     }
419             }
420     }
421
422 #ifdef HAVE_DLOPEN
423     if (h == NULL)
424         h = find_dynamic_method (context, filename, &residual);
425 #endif
426     if (h == NULL)
427         krb5_errx(context, 1, "No database support for %s", filename);
428     return (*h->create)(context, db, residual);
429 }