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