xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/hdb/db3.c (revision 241bea01a19bbb306af27777a870b86d41cb3fda)
1 /*	$NetBSD: db3.c,v 1.3 2019/12/15 22:50:49 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the Institute nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include "hdb_locl.h"
37 
38 #include <fcntl.h>
39 
40 #if HAVE_DB3
41 
42 #ifdef HAVE_DBHEADER
43 #include <db.h>
44 #elif HAVE_DB6_DB_H
45 #include <db6/db.h>
46 #elif HAVE_DB5_DB_H
47 #include <db5/db.h>
48 #elif HAVE_DB4_DB_H
49 #include <db4/db.h>
50 #elif HAVE_DB3_DB_H
51 #include <db3/db.h>
52 #else
53 #include <db.h>
54 #endif
55 
56 typedef struct {
57     HDB hdb;            /* generic members */
58     int lock_fd;        /* DB3-specific */
59     int do_sync;        /* DB3-specific */
60 } DB3_HDB;
61 
62 
63 static krb5_error_code
DB_close(krb5_context context,HDB * db)64 DB_close(krb5_context context, HDB *db)
65 {
66     DB3_HDB *db3 = (DB3_HDB *)db;
67     DB *d = (DB*)db->hdb_db;
68     DBC *dbcp = (DBC*)db->hdb_dbc;
69 
70     heim_assert(d != 0, "Closing already closed HDB");
71 
72     if (dbcp != NULL)
73 	dbcp->c_close(dbcp);
74     if (d != NULL)
75 	d->close(d, 0);
76     if (db3->lock_fd >= 0)
77 	close(db3->lock_fd);
78 
79     db3->lock_fd = -1;
80     db->hdb_dbc = 0;
81     db->hdb_db = 0;
82 
83     return 0;
84 }
85 
86 static krb5_error_code
DB_destroy(krb5_context context,HDB * db)87 DB_destroy(krb5_context context, HDB *db)
88 {
89     krb5_error_code ret;
90 
91     ret = hdb_clear_master_key (context, db);
92     free(db->hdb_name);
93     free(db);
94     return ret;
95 }
96 
97 static krb5_error_code
DB_set_sync(krb5_context context,HDB * db,int on)98 DB_set_sync(krb5_context context, HDB *db, int on)
99 {
100     DB3_HDB *db3 = (DB3_HDB *)db;
101     DB *d = (DB*)db->hdb_db;
102     krb5_error_code ret = 0;
103 
104     db3->do_sync = on;
105     if (on) {
106         ret = (*d->sync)(d, 0);
107         if (ret) {
108             if (ret == EACCES || ret == ENOSPC || ret == EINVAL) {
109                 krb5_set_error_message(context, ret,
110                                        "Database %s put sync error: %s",
111                                        db->hdb_name, strerror(ret));
112             } else {
113                 ret = HDB_ERR_UK_SERROR;
114                 krb5_set_error_message(context, ret,
115                                        "Database %s put sync error: unknown (%d)",
116                                        db->hdb_name, ret);
117             }
118         }
119     }
120     return ret;
121 }
122 
123 static krb5_error_code
DB_lock(krb5_context context,HDB * db,int operation)124 DB_lock(krb5_context context, HDB *db, int operation)
125 {
126 
127     return 0;
128 }
129 
130 static krb5_error_code
DB_unlock(krb5_context context,HDB * db)131 DB_unlock(krb5_context context, HDB *db)
132 {
133 
134     return 0;
135 }
136 
137 
138 static krb5_error_code
DB_seq(krb5_context context,HDB * db,unsigned flags,hdb_entry_ex * entry,int flag)139 DB_seq(krb5_context context, HDB *db,
140        unsigned flags, hdb_entry_ex *entry, int flag)
141 {
142     DBT key, value;
143     DBC *dbcp = db->hdb_dbc;
144     krb5_data key_data, data;
145     int code;
146 
147     memset(&key, 0, sizeof(DBT));
148     memset(&value, 0, sizeof(DBT));
149     code = (*dbcp->c_get)(dbcp, &key, &value, flag);
150     if (code == DB_NOTFOUND)
151 	return HDB_ERR_NOENTRY;
152     if (code)
153 	return code;
154 
155     key_data.data = key.data;
156     key_data.length = key.size;
157     data.data = value.data;
158     data.length = value.size;
159     memset(entry, 0, sizeof(*entry));
160     if (hdb_value2entry(context, &data, &entry->entry))
161 	return DB_seq(context, db, flags, entry, DB_NEXT);
162     if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
163 	code = hdb_unseal_keys (context, db, &entry->entry);
164 	if (code)
165 	    hdb_free_entry (context, entry);
166     }
167     if (entry->entry.principal == NULL) {
168 	entry->entry.principal = malloc(sizeof(*entry->entry.principal));
169 	if (entry->entry.principal == NULL) {
170 	    hdb_free_entry (context, entry);
171 	    krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
172 	    return ENOMEM;
173 	} else {
174 	    hdb_key2principal(context, &key_data, entry->entry.principal);
175 	}
176     }
177     return 0;
178 }
179 
180 
181 static krb5_error_code
DB_firstkey(krb5_context context,HDB * db,unsigned flags,hdb_entry_ex * entry)182 DB_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
183 {
184     return DB_seq(context, db, flags, entry, DB_FIRST);
185 }
186 
187 
188 static krb5_error_code
DB_nextkey(krb5_context context,HDB * db,unsigned flags,hdb_entry_ex * entry)189 DB_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
190 {
191     return DB_seq(context, db, flags, entry, DB_NEXT);
192 }
193 
194 static krb5_error_code
DB_rename(krb5_context context,HDB * db,const char * new_name)195 DB_rename(krb5_context context, HDB *db, const char *new_name)
196 {
197     int ret;
198     char *old, *new;
199 
200     if (strncmp(new_name, "db:", sizeof("db:") - 1) == 0)
201         new_name += sizeof("db:") - 1;
202     else if (strncmp(new_name, "db3:", sizeof("db3:") - 1) == 0)
203         new_name += sizeof("db3:") - 1;
204 
205     ret = asprintf(&old, "%s.db", db->hdb_name);
206     if (ret == -1)
207 	return ENOMEM;
208     ret = asprintf(&new, "%s.db", new_name);
209     if (ret == -1) {
210 	free(old);
211 	return ENOMEM;
212     }
213     ret = rename(old, new);
214     free(old);
215     if(ret) {
216 	free(new);
217 	return errno;
218     }
219 
220     free(db->hdb_name);
221     new[strlen(new) - 3] = '\0';
222     db->hdb_name = new;
223     return 0;
224 }
225 
226 static krb5_error_code
DB__get(krb5_context context,HDB * db,krb5_data key,krb5_data * reply)227 DB__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply)
228 {
229     DB *d = (DB*)db->hdb_db;
230     DBT k, v;
231     int code;
232 
233     memset(&k, 0, sizeof(DBT));
234     memset(&v, 0, sizeof(DBT));
235     k.data = key.data;
236     k.size = key.length;
237     k.flags = 0;
238     code = (*d->get)(d, NULL, &k, &v, 0);
239     if(code == DB_NOTFOUND)
240 	return HDB_ERR_NOENTRY;
241     if(code)
242 	return code;
243 
244     krb5_data_copy(reply, v.data, v.size);
245     return 0;
246 }
247 
248 static krb5_error_code
DB__put(krb5_context context,HDB * db,int replace,krb5_data key,krb5_data value)249 DB__put(krb5_context context, HDB *db, int replace,
250 	krb5_data key, krb5_data value)
251 {
252     DB3_HDB *db3 = (DB3_HDB *)db;
253     DB *d = (DB*)db->hdb_db;
254     DBT k, v;
255     int code;
256 
257     memset(&k, 0, sizeof(DBT));
258     memset(&v, 0, sizeof(DBT));
259     k.data = key.data;
260     k.size = key.length;
261     k.flags = 0;
262     v.data = value.data;
263     v.size = value.length;
264     v.flags = 0;
265     code = (*d->put)(d, NULL, &k, &v, replace ? 0 : DB_NOOVERWRITE);
266     if(code == DB_KEYEXIST)
267 	return HDB_ERR_EXISTS;
268     if (code) {
269         /*
270          * Berkeley DB 3 and up have a terrible error reporting
271          * interface...
272          *
273          * DB->err() doesn't output a string.
274          * DB->set_errcall()'s callback function doesn't have a void *
275          * argument that can be used to place the error somewhere.
276          *
277          * The only thing we could do is fopen()/fdopen() a file, set it
278          * with DB->set_errfile(), then call DB->err(), then read the
279          * message from the file, unset it with DB->set_errfile(), close
280          * it and delete it.  That's a lot of work... so we don't do it.
281          */
282         if (code == EACCES || code == ENOSPC || code == EINVAL) {
283             krb5_set_error_message(context, code,
284                                    "Database %s put error: %s",
285                                    db->hdb_name, strerror(code));
286         } else {
287             code = HDB_ERR_UK_SERROR;
288             krb5_set_error_message(context, code,
289                                    "Database %s put error: unknown (%d)",
290                                    db->hdb_name, code);
291         }
292 	return code;
293     }
294     return db->hdb_set_sync(context, db, db3->do_sync);
295 }
296 
297 static krb5_error_code
DB__del(krb5_context context,HDB * db,krb5_data key)298 DB__del(krb5_context context, HDB *db, krb5_data key)
299 {
300     DB3_HDB *db3 = (DB3_HDB *)db;
301     DB *d = (DB*)db->hdb_db;
302     DBT k;
303     krb5_error_code code;
304     memset(&k, 0, sizeof(DBT));
305     k.data = key.data;
306     k.size = key.length;
307     k.flags = 0;
308     code = (*d->del)(d, NULL, &k, 0);
309     if(code == DB_NOTFOUND)
310 	return HDB_ERR_NOENTRY;
311     if (code) {
312         if (code == EACCES || code == ENOSPC || code == EINVAL) {
313             krb5_set_error_message(context, code,
314                                    "Database %s del error: %s",
315                                    db->hdb_name, strerror(code));
316         } else {
317             code = HDB_ERR_UK_SERROR;
318             krb5_set_error_message(context, code,
319                                    "Database %s del error: unknown (%d)",
320                                    db->hdb_name, code);
321         }
322 	return code;
323     }
324     return db->hdb_set_sync(context, db, db3->do_sync);
325 }
326 
327 #define RD_CACHE_SZ 0x8000     /* Minimal read cache size */
328 #define WR_CACHE_SZ 0x8000     /* Minimal write cache size */
329 
330 static int
_open_db(DB * d,char * fn,int myflags,int flags,mode_t mode,int * fd)331 _open_db(DB *d, char *fn, int myflags, int flags, mode_t mode, int *fd)
332 {
333     int ret;
334     int cache_size = (myflags & DB_RDONLY) ? RD_CACHE_SZ : WR_CACHE_SZ;
335 
336     *fd = open(fn, flags, mode);
337 
338     if (*fd == -1)
339        return errno;
340 
341     /*
342      * Without DB_FCNTL_LOCKING, the DB library complains when initializing
343      * a database in an empty file. Since the database is our lock file,
344      * we create it before Berkeley DB does, so a new DB always starts empty.
345      */
346     myflags |= DB_FCNTL_LOCKING;
347 
348     ret = flock(*fd, (myflags&DB_RDONLY) ? LOCK_SH : LOCK_EX);
349     if (ret == -1) {
350 	ret = errno;
351 	close(*fd);
352 	*fd = -1;
353 	return ret;
354     }
355 
356     d->set_cachesize(d, 0, cache_size, 0);
357 
358 #if (DB_VERSION_MAJOR > 4) || ((DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR >= 1))
359     ret = (*d->open)(d, NULL, fn, NULL, DB_BTREE, myflags, mode);
360 #else
361     ret = (*d->open)(d, fn, NULL, DB_BTREE, myflags, mode);
362 #endif
363 
364     if (ret != 0) {
365 	close(*fd);
366 	*fd = -1;
367     }
368 
369     return ret;
370 }
371 
372 static krb5_error_code
DB_open(krb5_context context,HDB * db,int flags,mode_t mode)373 DB_open(krb5_context context, HDB *db, int flags, mode_t mode)
374 {
375     DB3_HDB *db3 = (DB3_HDB *)db;
376     DBC *dbc = NULL;
377     char *fn;
378     krb5_error_code ret;
379     DB *d;
380     int myflags = 0;
381     int aret;
382 
383     heim_assert(db->hdb_db == 0, "Opening already open HDB");
384 
385     if (flags & O_CREAT)
386       myflags |= DB_CREATE;
387 
388     if (flags & O_EXCL)
389       myflags |= DB_EXCL;
390 
391     if((flags & O_ACCMODE) == O_RDONLY)
392       myflags |= DB_RDONLY;
393 
394     if (flags & O_TRUNC)
395       myflags |= DB_TRUNCATE;
396 
397     aret = asprintf(&fn, "%s.db", db->hdb_name);
398     if (aret == -1) {
399 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
400 	return ENOMEM;
401     }
402 
403     if (db_create(&d, NULL, 0) != 0) {
404 	free(fn);
405 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
406 	return ENOMEM;
407     }
408     db->hdb_db = d;
409 
410     /* From here on out always DB_close() before returning on error */
411 
412     ret = _open_db(d, fn, myflags, flags, mode, &db3->lock_fd);
413     free(fn);
414     if (ret == ENOENT) {
415 	/* try to open without .db extension */
416 	ret = _open_db(d, db->hdb_name, myflags, flags, mode, &db3->lock_fd);
417     }
418 
419     if (ret) {
420 	DB_close(context, db);
421 	krb5_set_error_message(context, ret, "opening %s: %s",
422 			       db->hdb_name, strerror(ret));
423 	return ret;
424     }
425 
426 #ifndef DB_CURSOR_BULK
427 # define DB_CURSOR_BULK 0	/* Missing with DB < 4.8 */
428 #endif
429     ret = (*d->cursor)(d, NULL, &dbc, DB_CURSOR_BULK);
430 
431     if (ret) {
432 	DB_close(context, db);
433 	krb5_set_error_message(context, ret, "d->cursor: %s", strerror(ret));
434         return ret;
435     }
436     db->hdb_dbc = dbc;
437 
438     if((flags & O_ACCMODE) == O_RDONLY)
439 	ret = hdb_check_db_format(context, db);
440     else
441 	ret = hdb_init_db(context, db);
442     if(ret == HDB_ERR_NOENTRY)
443 	return 0;
444     if (ret) {
445 	DB_close(context, db);
446 	krb5_set_error_message(context, ret, "hdb_open: failed %s database %s",
447 			       (flags & O_ACCMODE) == O_RDONLY ?
448 			       "checking format of" : "initialize",
449 			       db->hdb_name);
450     }
451 
452     return ret;
453 }
454 
455 krb5_error_code
hdb_db3_create(krb5_context context,HDB ** db,const char * filename)456 hdb_db3_create(krb5_context context, HDB **db,
457 	       const char *filename)
458 {
459     DB3_HDB **db3 = (DB3_HDB **)db;
460     *db3 = calloc(1, sizeof(**db3));    /* Allocate space for the larger db3 */
461     if (*db == NULL) {
462 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
463 	return ENOMEM;
464     }
465 
466     (*db)->hdb_db = NULL;
467     (*db)->hdb_name = strdup(filename);
468     if ((*db)->hdb_name == NULL) {
469 	free(*db);
470 	*db = NULL;
471 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
472 	return ENOMEM;
473     }
474     (*db)->hdb_master_key_set = 0;
475     (*db)->hdb_openp = 0;
476     (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
477     (*db)->hdb_open  = DB_open;
478     (*db)->hdb_close = DB_close;
479     (*db)->hdb_fetch_kvno = _hdb_fetch_kvno;
480     (*db)->hdb_store = _hdb_store;
481     (*db)->hdb_remove = _hdb_remove;
482     (*db)->hdb_firstkey = DB_firstkey;
483     (*db)->hdb_nextkey= DB_nextkey;
484     (*db)->hdb_lock = DB_lock;
485     (*db)->hdb_unlock = DB_unlock;
486     (*db)->hdb_rename = DB_rename;
487     (*db)->hdb__get = DB__get;
488     (*db)->hdb__put = DB__put;
489     (*db)->hdb__del = DB__del;
490     (*db)->hdb_destroy = DB_destroy;
491     (*db)->hdb_set_sync = DB_set_sync;
492 
493     (*db3)->lock_fd = -1;
494     return 0;
495 }
496 #endif /* HAVE_DB3 */
497