xref: /illumos-gate/usr/src/lib/krb5/plugins/kdb/db2/kdb_db2.c (revision e8d80663e4f91871f843bb8ad9108dc0b76dfcf3)
1 /*
2  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 
7 /*
8  * lib/kdb/kdb_db2.c
9  *
10  * Copyright 1997,2006 by the Massachusetts Institute of Technology.
11  * All Rights Reserved.
12  *
13  * Export of this software from the United States of America may
14  *   require a specific license from the United States Government.
15  *   It is the responsibility of any person or organization contemplating
16  *   export to obtain such a license before exporting.
17  *
18  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
19  * distribute this software and its documentation for any purpose and
20  * without fee is hereby granted, provided that the above copyright
21  * notice appear in all copies and that both that copyright notice and
22  * this permission notice appear in supporting documentation, and that
23  * the name of M.I.T. not be used in advertising or publicity pertaining
24  * to distribution of the software without specific, written prior
25  * permission.  Furthermore if you modify this software you must label
26  * your software as modified software and not distribute it in such a
27  * fashion that it might be confused with the original M.I.T. software.
28  * M.I.T. makes no representations about the suitability of
29  * this software for any purpose.  It is provided "as is" without express
30  * or implied warranty.
31  *
32  */
33 
34 /*
35  * Copyright (C) 1998 by the FundsXpress, INC.
36  *
37  * All rights reserved.
38  *
39  * Export of this software from the United States of America may require
40  * a specific license from the United States Government.  It is the
41  * responsibility of any person or organization contemplating export to
42  * obtain such a license before exporting.
43  *
44  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
45  * distribute this software and its documentation for any purpose and
46  * without fee is hereby granted, provided that the above copyright
47  * notice appear in all copies and that both that copyright notice and
48  * this permission notice appear in supporting documentation, and that
49  * the name of FundsXpress. not be used in advertising or publicity pertaining
50  * to distribution of the software without specific, written prior
51  * permission.  FundsXpress makes no representations about the suitability of
52  * this software for any purpose.  It is provided "as is" without express
53  * or implied warranty.
54  *
55  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
56  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
57  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
58  */
59 
60 #include "k5-int.h"
61 #include <kdb_log.h>
62 
63 #if HAVE_UNISTD_H
64 #include <unistd.h>
65 #endif
66 
67 #include <db.h>
68 #include <stdio.h>
69 #include <errno.h>
70 #include <utime.h>
71 #include "kdb5.h"
72 #include "kdb_db2.h"
73 #include "kdb_xdr.h"
74 #include "policy_db.h"
75 #include <libintl.h>
76 
77 #define KDB_DB2_DATABASE_NAME "database_name"
78 
79 #include "kdb_db2.h"
80 
81 static char *gen_dbsuffix(char *, char *);
82 
83 static krb5_error_code krb5_db2_db_start_update(krb5_context);
84 static krb5_error_code krb5_db2_db_end_update(krb5_context);
85 
86 static krb5_error_code krb5_db2_db_set_name(krb5_context, char *, int);
87 
88 krb5_error_code krb5_db2_db_lock(krb5_context, int);
89 
90 static krb5_error_code krb5_db2_db_set_hashfirst(krb5_context, int);
91 
92 /*
93  * Solaris Kerberos
94  * Extra error handling
95  */
96 char errbuf[1024];
97 static void krb5_db2_prepend_err_str(krb5_context , const char *,
98     krb5_error_code, krb5_error_code);
99 
100 static char default_db_name[] = DEFAULT_KDB_FILE;
101 
102 /*
103  * Locking:
104  *
105  * There are two distinct locking protocols used.  One is designed to
106  * lock against processes (the admin_server, for one) which make
107  * incremental changes to the database; the other is designed to lock
108  * against utilities (kdb5_edit, kpropd, kdb5_convert) which replace the
109  * entire database in one fell swoop.
110  *
111  * The first locking protocol is implemented using flock() in the
112  * krb_dbl_lock() and krb_dbl_unlock routines.
113  *
114  * The second locking protocol is necessary because DBM "files" are
115  * actually implemented as two separate files, and it is impossible to
116  * atomically rename two files simultaneously.  It assumes that the
117  * database is replaced only very infrequently in comparison to the time
118  * needed to do a database read operation.
119  *
120  * A third file is used as a "version" semaphore; the modification
121  * time of this file is the "version number" of the database.
122  * At the start of a read operation, the reader checks the version
123  * number; at the end of the read operation, it checks again.  If the
124  * version number changed, or if the semaphore was nonexistant at
125  * either time, the reader sleeps for a second to let things
126  * stabilize, and then tries again; if it does not succeed after
127  * KRB5_DBM_MAX_RETRY attempts, it gives up.
128  *
129  * On update, the semaphore file is deleted (if it exists) before any
130  * update takes place; at the end of the update, it is replaced, with
131  * a version number strictly greater than the version number which
132  * existed at the start of the update.
133  *
134  * If the system crashes in the middle of an update, the semaphore
135  * file is not automatically created on reboot; this is a feature, not
136  * a bug, since the database may be inconsistant.  Note that the
137  * absence of a semaphore file does not prevent another _update_ from
138  * taking place later.  Database replacements take place automatically
139  * only on slave servers; a crash in the middle of an update will be
140  * fixed by the next slave propagation.  A crash in the middle of an
141  * update on the master would be somewhat more serious, but this would
142  * likely be noticed by an administrator, who could fix the problem and
143  * retry the operation.
144  */
145 
146 #define free_dbsuffix(name) free(name)
147 
148 /*
149  * Routines to deal with context.
150  */
151 #define	k5db2_inited(c)	(c && c->db_context \
152 			 && ((kdb5_dal_handle*)c->db_context)->db_context \
153                          && ((krb5_db2_context *) ((kdb5_dal_handle*)c->db_context)->db_context)->db_inited)
154 
155 static krb5_error_code
156 krb5_db2_get_db_opt(char *input, char **opt, char **val)
157 {
158     char   *pos = strchr(input, '=');
159     if (pos == NULL) {
160 	*opt = NULL;
161 	*val = strdup(input);
162 	if (*val == NULL) {
163 	    return ENOMEM;
164 	}
165     } else {
166 	*opt = malloc((pos - input) + 1);
167 	*val = strdup(pos + 1);
168 	if (!*opt || !*val) {
169 	    return ENOMEM;
170 	}
171 	memcpy(*opt, input, pos - input);
172 	(*opt)[pos - input] = '\0';
173     }
174     return (0);
175 
176 }
177 
178 /*
179  * Restore the default context.
180  */
181 static void
182 k5db2_clear_context(krb5_db2_context *dbctx)
183 {
184     /*
185      * Free any dynamically allocated memory.  File descriptors and locks
186      * are the caller's problem.
187      */
188     if (dbctx->db_lf_name)
189 	free(dbctx->db_lf_name);
190     if (dbctx->db_name && (dbctx->db_name != default_db_name))
191 	free(dbctx->db_name);
192     /*
193      * Clear the structure and reset the defaults.
194      */
195     memset((char *) dbctx, 0, sizeof(krb5_db2_context));
196     dbctx->db_name = default_db_name;
197     dbctx->db_nb_locks = FALSE;
198     dbctx->tempdb = FALSE;
199 }
200 
201 static krb5_error_code
202 k5db2_init_context(krb5_context context)
203 {
204     krb5_db2_context *db_ctx;
205     kdb5_dal_handle *dal_handle;
206 
207     dal_handle = (kdb5_dal_handle *) context->db_context;
208 
209     if (dal_handle->db_context == NULL) {
210 	db_ctx = (krb5_db2_context *) malloc(sizeof(krb5_db2_context));
211 	if (db_ctx == NULL)
212 	    return ENOMEM;
213 	else {
214 	    memset((char *) db_ctx, 0, sizeof(krb5_db2_context));
215 	    k5db2_clear_context((krb5_db2_context *) db_ctx);
216 	    dal_handle->db_context = (void *) db_ctx;
217 	}
218     }
219     return (0);
220 }
221 
222 /*
223  * Utility routine: generate name of database file.
224  */
225 
226 static char *
227 gen_dbsuffix(char *db_name, char *sfx)
228 {
229     char   *dbsuffix;
230 
231     if (sfx == NULL)
232 	return ((char *) NULL);
233 
234     dbsuffix = malloc(strlen(db_name) + strlen(sfx) + 1);
235     if (!dbsuffix)
236 	return (0);
237     /*LINTED*/
238     (void) strcpy(dbsuffix, db_name);
239     /*LINTED*/
240     (void) strcat(dbsuffix, sfx);
241     return dbsuffix;
242 }
243 
244 static DB *
245 k5db2_dbopen(krb5_db2_context *dbc, char *fname, int flags, int mode, int tempdb)
246 {
247     DB     *db;
248     BTREEINFO bti;
249     HASHINFO hashi;
250     bti.flags = 0;
251     bti.cachesize = 0;
252     bti.psize = 4096;
253     bti.lorder = 0;
254     bti.minkeypage = 0;
255     bti.compare = NULL;
256     bti.prefix = NULL;
257 
258     if (tempdb) {
259 	fname = gen_dbsuffix(fname, "~");
260     } else {
261 	fname = strdup(fname);
262     }
263     if (fname == NULL)
264     {
265 	errno = ENOMEM;
266 	return NULL;
267     }
268 
269 
270     hashi.bsize = 4096;
271     hashi.cachesize = 0;
272     hashi.ffactor = 40;
273     hashi.hash = NULL;
274     hashi.lorder = 0;
275     hashi.nelem = 1;
276 
277     db = dbopen(fname, flags, mode,
278 		dbc->hashfirst ? DB_HASH : DB_BTREE,
279 		dbc->hashfirst ? (void *) &hashi : (void *) &bti);
280     if (db != NULL) {
281 	free(fname);
282 	return db;
283     }
284     switch (errno) {
285 #ifdef EFTYPE
286     case EFTYPE:
287 #endif
288     case EINVAL:
289 	db = dbopen(fname, flags, mode,
290 		    dbc->hashfirst ? DB_BTREE : DB_HASH,
291 		    dbc->hashfirst ? (void *) &bti : (void *) &hashi);
292 	if (db != NULL)
293 	    dbc->hashfirst = !dbc->hashfirst;
294     default:
295 	free(fname);
296 	return db;
297     }
298 }
299 
300 static krb5_error_code
301 krb5_db2_db_set_hashfirst(krb5_context context, int hashfirst)
302 {
303     krb5_db2_context *dbc;
304     kdb5_dal_handle *dal_handle;
305 
306     if (k5db2_inited(context))
307 	return KRB5_KDB_DBNOTINITED;
308     dal_handle = (kdb5_dal_handle *) context->db_context;
309     dbc = (krb5_db2_context *) dal_handle->db_context;
310     dbc->hashfirst = hashfirst;
311     return 0;
312 }
313 
314 /*
315  * initialization for data base routines.
316  */
317 
318 krb5_error_code
319 krb5_db2_db_init(krb5_context context)
320 {
321     char   *filename = NULL;
322     krb5_db2_context *db_ctx;
323     krb5_error_code retval;
324     kdb5_dal_handle *dal_handle;
325     char    policy_db_name[1024], policy_lock_name[1024];
326 
327     if (k5db2_inited(context))
328 	return 0;
329 
330     /* Check for presence of our context, if not present, allocate one. */
331     if ((retval = k5db2_init_context(context)))
332 	return (retval);
333 
334     dal_handle = (kdb5_dal_handle *) context->db_context;
335     db_ctx = dal_handle->db_context;
336     db_ctx->db = NULL;
337 
338     if (!(filename = gen_dbsuffix(db_ctx->db_name, db_ctx->tempdb
339 				  ?KDB2_TEMP_LOCK_EXT:KDB2_LOCK_EXT)))
340 	return ENOMEM;
341     db_ctx->db_lf_name = filename;	/* so it gets freed by clear_context */
342 
343     /*
344      * should be opened read/write so that write locking can work with
345      * POSIX systems
346      */
347     if ((db_ctx->db_lf_file = open(filename, O_RDWR, 0666)) < 0) {
348 	if ((db_ctx->db_lf_file = open(filename, O_RDONLY, 0666)) < 0) {
349 	    retval = errno;
350 
351 	    /* Solaris Kerberos: Better error logging */
352   	    (void) snprintf(errbuf, sizeof(errbuf), gettext("Failed to open \"%s\": "), filename);
353 	    krb5_db2_prepend_err_str(context, errbuf, retval, retval);
354 
355 	    goto err_out;
356 	}
357     }
358     db_ctx->db_inited++;
359 
360     if ((retval = krb5_db2_db_get_age(context, NULL, &db_ctx->db_lf_time)))
361 	goto err_out;
362 
363     sprintf(policy_db_name, db_ctx->tempdb ? "%s~.kadm5" : "%s.kadm5",
364 	    db_ctx->db_name);
365     sprintf(policy_lock_name, "%s.lock", policy_db_name);
366 
367     if ((retval = osa_adb_init_db(&db_ctx->policy_db, policy_db_name,
368 				  policy_lock_name, OSA_ADB_POLICY_DB_MAGIC)))
369     {
370 	/* Solaris Kerberos: Better error logging */
371 	snprintf(errbuf, sizeof(errbuf),
372 	    gettext("Failed to initialize db, \"%s\", lockfile, \"%s\" : "),
373 	    policy_db_name, policy_lock_name);
374 	krb5_db2_prepend_err_str(context, errbuf, retval, retval);
375 
376 	goto err_out;
377     }
378     return 0;
379 
380   err_out:
381     db_ctx->db = NULL;
382     k5db2_clear_context(db_ctx);
383     return (retval);
384 }
385 
386 /*
387  * gracefully shut down database--must be called by ANY program that does
388  * a krb5_db2_db_init
389  */
390 krb5_error_code
391 krb5_db2_db_fini(krb5_context context)
392 {
393     krb5_error_code retval = 0;
394     krb5_db2_context *db_ctx;
395     kdb5_dal_handle *dal_handle;
396 
397     dal_handle = (kdb5_dal_handle *) context->db_context;
398     if (dal_handle == NULL) {
399 	return 0;
400     }
401 
402     db_ctx = (krb5_db2_context *) dal_handle->db_context;
403 
404     if (k5db2_inited(context)) {
405 	if (close(db_ctx->db_lf_file))
406 	    retval = errno;
407 	else
408 	    retval = 0;
409     }
410     if (db_ctx) {
411 	if (db_ctx->policy_db) {
412 	    retval =
413 		osa_adb_fini_db(db_ctx->policy_db, OSA_ADB_POLICY_DB_MAGIC);
414 	    if (retval)
415 		return retval;
416 	}
417 
418 	k5db2_clear_context(db_ctx);
419 	/*      free(dal_handle->db_context); */
420 	dal_handle->db_context = NULL;
421     }
422     return retval;
423 }
424 
425 /*
426  * Set/Get the master key associated with the database
427  */
428 krb5_error_code
429 krb5_db2_db_set_mkey(krb5_context context, krb5_keyblock *key)
430 {
431     krb5_db2_context *db_ctx;
432     kdb5_dal_handle *dal_handle;
433 
434     if (!k5db2_inited(context))
435 	return (KRB5_KDB_DBNOTINITED);
436 
437     dal_handle = (kdb5_dal_handle *) context->db_context;
438     db_ctx = dal_handle->db_context;
439     db_ctx->db_master_key = key;
440     return 0;
441 }
442 
443 krb5_error_code
444 krb5_db2_db_get_mkey(krb5_context context, krb5_keyblock **key)
445 {
446     krb5_db2_context *db_ctx;
447     kdb5_dal_handle *dal_handle;
448 
449     if (!k5db2_inited(context))
450 	return (KRB5_KDB_DBNOTINITED);
451 
452     dal_handle = (kdb5_dal_handle *) context->db_context;
453     db_ctx = dal_handle->db_context;
454     *key = db_ctx->db_master_key;
455 
456     return 0;
457 }
458 
459 /*
460  * Set the "name" of the current database to some alternate value.
461  *
462  * Passing a null pointer as "name" will set back to the default.
463  * If the alternate database doesn't exist, nothing is changed.
464  *
465  * XXX rethink this
466  */
467 
468 static krb5_error_code
469 krb5_db2_db_set_name(krb5_context context, char *name, int tempdb)
470 {
471     DB     *db;
472     krb5_db2_context *db_ctx;
473     krb5_error_code kret;
474     kdb5_dal_handle *dal_handle;
475 
476     if (k5db2_inited(context))
477 	return KRB5_KDB_DBINITED;
478 
479     /* Check for presence of our context, if not present, allocate one. */
480     if ((kret = k5db2_init_context(context)))
481 	return (kret);
482 
483     if (name == NULL)
484 	name = default_db_name;
485 
486     dal_handle = (kdb5_dal_handle *) context->db_context;
487     db_ctx = dal_handle->db_context;
488     db_ctx->tempdb = tempdb;
489     db = k5db2_dbopen(db_ctx, name, O_RDONLY, 0, tempdb);
490     if (db == NULL)
491 	return errno;
492 
493     db_ctx->db_name = strdup(name);
494     if (db_ctx->db_name == NULL) {
495 	(*db->close) (db);
496 	return ENOMEM;
497     }
498     (*db->close) (db);
499     return 0;
500 }
501 
502 /*
503  * Return the last modification time of the database.
504  *
505  * Think about using fstat.
506  */
507 
508 krb5_error_code
509 krb5_db2_db_get_age(krb5_context context, char *db_name, time_t *age)
510 {
511     krb5_db2_context *db_ctx;
512     kdb5_dal_handle *dal_handle;
513     struct stat st;
514 
515     if (!k5db2_inited(context))
516 	return (KRB5_KDB_DBNOTINITED);
517     dal_handle = (kdb5_dal_handle *) context->db_context;
518     db_ctx = (krb5_db2_context *) dal_handle->db_context;
519 
520     if (fstat(db_ctx->db_lf_file, &st) < 0)
521 	*age = -1;
522     else
523 	*age = st.st_mtime;
524     return 0;
525 }
526 
527 /*
528  * Remove the semaphore file; indicates that database is currently
529  * under renovation.
530  *
531  * This is only for use when moving the database out from underneath
532  * the server (for example, during slave updates).
533  */
534 
535 static krb5_error_code
536 krb5_db2_db_start_update(krb5_context context)
537 {
538     return 0;
539 }
540 
541 static krb5_error_code
542 krb5_db2_db_end_update(krb5_context context)
543 {
544     krb5_error_code retval;
545     krb5_db2_context *db_ctx;
546     kdb5_dal_handle *dal_handle;
547     struct stat st;
548     time_t  now;
549     struct utimbuf utbuf;
550 
551     if (!k5db2_inited(context))
552 	return (KRB5_KDB_DBNOTINITED);
553 
554     retval = 0;
555     dal_handle = (kdb5_dal_handle *) context->db_context;
556     db_ctx = dal_handle->db_context;
557     now = time((time_t *) NULL);
558     if (fstat(db_ctx->db_lf_file, &st) == 0) {
559 	if (st.st_mtime >= now) {
560 	    utbuf.actime = st.st_mtime + 1;
561 	    utbuf.modtime = st.st_mtime + 1;
562 	    if (utime(db_ctx->db_lf_name, &utbuf))
563 		retval = errno;
564 	} else {
565 	    if (utime(db_ctx->db_lf_name, (struct utimbuf *) NULL))
566 		retval = errno;
567 	}
568 	if (retval) {
569 	    /* Solaris Kerberos: Better error logging */
570 	    snprintf(errbuf, sizeof(errbuf), gettext("Failed to modify "
571 	        "access and modification times for \"%s\": "),
572 	        db_ctx->db_lf_name);
573 	    krb5_db2_prepend_err_str(context, errbuf, retval, retval);
574 	}
575     } else {
576 	retval = errno;
577 	/* Solaris Kerberos: Better error logging */
578 	snprintf(errbuf, sizeof(errbuf), gettext("Failed to stat \"%s\": "),
579 	    db_ctx->db_lf_name);
580 	krb5_db2_prepend_err_str(context, errbuf, retval, retval);
581     }
582     if (!retval) {
583 	if (fstat(db_ctx->db_lf_file, &st) == 0)
584 	    db_ctx->db_lf_time = st.st_mtime;
585 	else {
586 	    retval = errno;
587 	    /* Solaris Kerberos: Better error logging */
588 	    snprintf(errbuf, sizeof(errbuf), gettext("Failed to stat \"%s\": "),
589 	        db_ctx->db_lf_name);
590 	    krb5_db2_prepend_err_str(context, errbuf, retval, retval);
591 	}
592     }
593     return (retval);
594 }
595 
596 #define MAX_LOCK_TRIES 5
597 
598 krb5_error_code
599 krb5_db2_db_lock(krb5_context context, int in_mode)
600 {
601     krb5_db2_context *db_ctx;
602     int     krb5_lock_mode;
603     DB     *db;
604     krb5_error_code retval;
605     time_t  mod_time;
606     kdb5_dal_handle *dal_handle;
607     int     mode, gotlock, tries;
608 
609     switch (in_mode) {
610     case KRB5_DB_LOCKMODE_PERMANENT:
611 	mode = KRB5_DB_LOCKMODE_EXCLUSIVE;
612 	break;
613     case KRB5_DB_LOCKMODE_EXCLUSIVE:
614 	mode = KRB5_LOCKMODE_EXCLUSIVE;
615 	break;
616 
617     case KRB5_DB_LOCKMODE_SHARED:
618 	mode = KRB5_LOCKMODE_SHARED;
619 	break;
620     default:
621 	return EINVAL;
622     }
623 
624     if (!k5db2_inited(context))
625 	return KRB5_KDB_DBNOTINITED;
626 
627     dal_handle = (kdb5_dal_handle *) context->db_context;
628     db_ctx = (krb5_db2_context *) dal_handle->db_context;
629     if (db_ctx->db_locks_held && (db_ctx->db_lock_mode >= mode)) {
630 	/* No need to upgrade lock, just return */
631 	db_ctx->db_locks_held++;
632 	goto policy_lock;
633     }
634 
635     if ((mode != KRB5_LOCKMODE_SHARED) && (mode != KRB5_LOCKMODE_EXCLUSIVE))
636 	return KRB5_KDB_BADLOCKMODE;
637 
638     krb5_lock_mode = mode | KRB5_LOCKMODE_DONTBLOCK;
639     for (gotlock = tries = 0; tries < MAX_LOCK_TRIES; tries++) {
640 	retval = krb5_lock_file(context, db_ctx->db_lf_file, krb5_lock_mode);
641 	if (retval == 0) {
642 	    gotlock++;
643 	    break;
644 	} else if (retval == EBADF && mode == KRB5_DB_LOCKMODE_EXCLUSIVE) {
645 	    /* tried to exclusive-lock something we don't have */
646 	    /* write access to */
647 
648 	    /* Solaris Kerberos: Better error logging */
649 	    snprintf(errbuf, sizeof(errbuf),
650 	        gettext("Failed to exclusively lock \"%s\": "),
651 	        db_ctx->db_lf_name);
652 	    krb5_db2_prepend_err_str(context, errbuf, EBADF, EBADF);
653 
654 	    return KRB5_KDB_CANTLOCK_DB;
655 	}
656 	sleep(1);
657     }
658 
659     if (retval) {
660 	/* Solaris Kerberos: Better error logging */
661 	snprintf(errbuf, sizeof(errbuf),
662 	    gettext("Failed to lock \"%s\": "),
663 	    db_ctx->db_lf_name);
664 	krb5_db2_prepend_err_str(context, errbuf, retval, retval);
665     }
666 
667     if (retval == EACCES)
668 	return KRB5_KDB_CANTLOCK_DB;
669     else if (retval == EAGAIN || retval == EWOULDBLOCK)
670 	return OSA_ADB_CANTLOCK_DB;
671     else if (retval != 0)
672 	return retval;
673 
674     if ((retval = krb5_db2_db_get_age(context, NULL, &mod_time)))
675 	goto lock_error;
676 
677     db = k5db2_dbopen(db_ctx, db_ctx->db_name,
678 		      mode == KRB5_LOCKMODE_SHARED ? O_RDONLY : O_RDWR, 0600, db_ctx->tempdb);
679     if (db) {
680 	db_ctx->db_lf_time = mod_time;
681 	db_ctx->db = db;
682     } else {
683 	retval = errno;
684 
685 	/* Solaris Kerberos: Better error logging */
686 	snprintf(errbuf, sizeof(errbuf),
687 	    gettext("Failed to open db \"%s\": "),
688 	    db_ctx->db_name);
689 	krb5_db2_prepend_err_str(context, errbuf, retval, retval);
690 
691 	db_ctx->db = NULL;
692 	goto lock_error;
693     }
694 
695     db_ctx->db_lock_mode = mode;
696     db_ctx->db_locks_held++;
697 
698   policy_lock:
699     if ((retval = osa_adb_get_lock(db_ctx->policy_db, in_mode))) {
700 	krb5_db2_db_unlock(context);
701     }
702     return retval;
703 
704   lock_error:;
705     db_ctx->db_lock_mode = 0;
706     db_ctx->db_locks_held = 0;
707     krb5_db2_db_unlock(context);
708     return retval;
709 }
710 
711 krb5_error_code
712 krb5_db2_db_unlock(krb5_context context)
713 {
714     krb5_db2_context *db_ctx;
715     kdb5_dal_handle *dal_handle;
716     DB     *db;
717     krb5_error_code retval;
718 
719     if (!k5db2_inited(context))
720 	return KRB5_KDB_DBNOTINITED;
721 
722     dal_handle = (kdb5_dal_handle *) context->db_context;
723     db_ctx = (krb5_db2_context *) dal_handle->db_context;
724 
725     if ((retval = osa_adb_release_lock(db_ctx->policy_db))) {
726 	return retval;
727     }
728 
729     if (!db_ctx->db_locks_held)	/* lock already unlocked */
730 	return KRB5_KDB_NOTLOCKED;
731     db = db_ctx->db;
732     if (--(db_ctx->db_locks_held) == 0) {
733 	(*db->close) (db);
734 	db_ctx->db = NULL;
735 
736 	retval = krb5_lock_file(context, db_ctx->db_lf_file,
737 				KRB5_LOCKMODE_UNLOCK);
738 	db_ctx->db_lock_mode = 0;
739 	return (retval);
740     }
741     return 0;
742 }
743 
744 /*
745  * Create the database, assuming it's not there.
746  */
747 krb5_error_code
748 krb5_db2_db_create(krb5_context context, char *db_name, krb5_int32 flags)
749 {
750     register krb5_error_code retval = 0;
751     kdb5_dal_handle *dal_handle;
752     char   *okname;
753     char   *db_name2 = NULL;
754     int     fd;
755     krb5_db2_context *db_ctx;
756     DB     *db;
757     char    policy_db_name[1024], policy_lock_name[1024];
758 
759     if ((retval = k5db2_init_context(context)))
760 	return (retval);
761 
762     dal_handle = (kdb5_dal_handle *) context->db_context;
763     db_ctx = (krb5_db2_context *) dal_handle->db_context;
764     switch (flags) {
765     case KRB5_KDB_CREATE_HASH:
766 	if ((retval = krb5_db2_db_set_hashfirst(context, TRUE)))
767 	    return retval;
768 	break;
769     case KRB5_KDB_CREATE_BTREE:
770     case 0:
771 	if ((retval = krb5_db2_db_set_hashfirst(context, FALSE)))
772 	    return retval;
773 	break;
774     default:
775 	return KRB5_KDB_BAD_CREATEFLAGS;
776     }
777     db = k5db2_dbopen(db_ctx, db_name, O_RDWR | O_CREAT | O_EXCL, 0600, db_ctx->tempdb);
778     if (db == NULL) {
779 	retval = errno;
780 
781 	/* Solaris Kerberos: Better error logging */
782   	snprintf(errbuf, sizeof(errbuf), gettext("Failed to open \"%s\": "), db_name);
783 	krb5_db2_prepend_err_str(context, errbuf, retval, retval);
784     }
785     else
786 	(*db->close) (db);
787     if (retval == 0) {
788 
789 	db_name2 = db_ctx->tempdb ? gen_dbsuffix(db_name, "~") : strdup(db_name);
790 	if (db_name2 == NULL)
791 	    return ENOMEM;
792 	okname = gen_dbsuffix(db_name2, KDB2_LOCK_EXT);
793 	if (!okname)
794 	    retval = ENOMEM;
795 	else {
796 	    fd = open(okname, O_CREAT | O_RDWR | O_TRUNC, 0600);
797 	    if (fd < 0) {
798 		retval = errno;
799 		/* Solaris Kerberos: Better error logging */
800 		snprintf(errbuf, sizeof(errbuf), gettext("Failed to open \"%s\": "), okname);
801 		krb5_db2_prepend_err_str(context, errbuf, retval, retval);
802 	    }
803 	    else
804 		close(fd);
805 	    free_dbsuffix(okname);
806 	}
807     }
808 
809     sprintf(policy_db_name, "%s.kadm5", db_name2);
810     sprintf(policy_lock_name, "%s.lock", policy_db_name);
811 
812     retval = osa_adb_create_db(policy_db_name,
813 			       policy_lock_name, OSA_ADB_POLICY_DB_MAGIC);
814     free(db_name2);
815     return retval;
816 }
817 
818 /*
819  * Destroy the database.  Zero's out all of the files, just to be sure.
820  */
821 static krb5_error_code
822 destroy_file_suffix(char *dbname, char *suffix)
823 {
824     char   *filename;
825     struct stat statb;
826     int     nb, fd;
827     unsigned int j;
828     off_t   pos;
829     char    buf[BUFSIZ];
830     char    zbuf[BUFSIZ];
831     int     dowrite;
832 
833     filename = gen_dbsuffix(dbname, suffix);
834     if (filename == 0)
835 	return ENOMEM;
836     if ((fd = open(filename, O_RDWR, 0)) < 0) {
837 	free(filename);
838 	return errno;
839     }
840     /* fstat() will probably not fail unless using a remote filesystem
841      * (which is inappropriate for the kerberos database) so this check
842      * is mostly paranoia.  */
843     if (fstat(fd, &statb) == -1) {
844 	int     retval = errno;
845 	free(filename);
846 	return retval;
847     }
848     /*
849      * Stroll through the file, reading in BUFSIZ chunks.  If everything
850      * is zero, then we're done for that block, otherwise, zero the block.
851      * We would like to just blast through everything, but some DB
852      * implementations make holey files and writing data to the holes
853      * causes actual blocks to be allocated which is no good, since
854      * we're just about to unlink it anyways.
855      */
856     memset(zbuf, 0, BUFSIZ);
857     pos = 0;
858     while (pos < statb.st_size) {
859 	dowrite = 0;
860 	nb = read(fd, buf, BUFSIZ);
861 	if (nb < 0) {
862 	    int     retval = errno;
863 	    free(filename);
864 	    return retval;
865 	}
866 	for (j = 0; j < nb; j++) {
867 	    if (buf[j] != '\0') {
868 		dowrite = 1;
869 		break;
870 	    }
871 	}
872 	/* For signedness */
873 	j = nb;
874 	if (dowrite) {
875 	    lseek(fd, pos, SEEK_SET);
876 	    nb = write(fd, zbuf, j);
877 	    if (nb < 0) {
878 		int     retval = errno;
879 		free(filename);
880 		return retval;
881 	    }
882 	}
883 	pos += nb;
884     }
885     /* ??? Is fsync really needed?  I don't know of any non-networked
886      * filesystem which will discard queued writes to disk if a file
887      * is deleted after it is closed.  --jfc */
888 #ifndef NOFSYNC
889     fsync(fd);
890 #endif
891     close(fd);
892 
893     if (unlink(filename)) {
894 	free(filename);
895 	return (errno);
896     }
897     free(filename);
898     return (0);
899 }
900 
901 /*
902  * Since the destroy operation happens outside the init/fini bracket, we
903  * have some tomfoolery to undergo here.  If we're operating under no
904  * database context, then we initialize with the default.  If the caller
905  * wishes a different context (e.g. different dispatch table), it's their
906  * responsibility to call kdb5_db_set_dbops() before this call.  That will
907  * set up the right dispatch table values (e.g. name extensions).
908  *
909  * Not quite valid due to ripping out of dbops...
910  */
911 krb5_error_code
912 krb5_db2_db_destroy(krb5_context context, char *dbname)
913 {
914     krb5_error_code retval1, retval2;
915     krb5_boolean tmpcontext;
916     char    policy_db_name[1024], policy_lock_name[1024];
917 
918     tmpcontext = 0;
919     if (!context->db_context
920 	|| !((kdb5_dal_handle *) context->db_context)->db_context) {
921 	tmpcontext = 1;
922 	if ((retval1 = k5db2_init_context(context)))
923 	    return (retval1);
924     }
925 
926     retval1 = retval2 = 0;
927     retval1 = destroy_file_suffix(dbname, "");
928     retval2 = destroy_file_suffix(dbname, KDB2_LOCK_EXT);
929 
930     if (tmpcontext) {
931 	k5db2_clear_context((krb5_db2_context *) ((kdb5_dal_handle *) context->
932 						  db_context)->db_context);
933 	free(((kdb5_dal_handle *) context->db_context)->db_context);
934 	((kdb5_dal_handle *) context->db_context)->db_context = NULL;
935     }
936 
937     if (retval1 || retval2)
938 	return (retval1 ? retval1 : retval2);
939 
940     assert (strlen(dbname) + strlen("%s.kadm5") < sizeof(policy_db_name));
941     sprintf(policy_db_name, "%s.kadm5", dbname);
942     /* XXX finish this */
943     sprintf(policy_lock_name, "%s.lock", policy_db_name);
944 
945     retval1 = osa_adb_destroy_db(policy_db_name,
946 				 policy_lock_name, OSA_ADB_POLICY_DB_MAGIC);
947 
948     return retval1;
949 }
950 
951 /*
952  * look up a principal in the data base.
953  * returns number of entries found, and whether there were
954  * more than requested.
955  */
956 
957 krb5_error_code
958 krb5_db2_db_get_principal(krb5_context context,
959 			  krb5_const_principal searchfor,
960 			  krb5_db_entry *entries, /* filled in */
961 			  int *nentries, /* how much room/how many found */
962 			  krb5_boolean *more) /* are there more? */
963 {
964     krb5_db2_context *db_ctx;
965     krb5_error_code retval;
966     DB     *db;
967     DBT     key, contents;
968     krb5_data keydata, contdata;
969     int     trynum, dbret;
970     kdb5_dal_handle *dal_handle;
971 
972     *more = FALSE;
973     *nentries = 0;
974 
975     if (!k5db2_inited(context))
976 	return KRB5_KDB_DBNOTINITED;
977 
978     dal_handle = (kdb5_dal_handle *) context->db_context;
979     db_ctx = (krb5_db2_context *) dal_handle->db_context;
980 
981     for (trynum = 0; trynum < KRB5_DB2_MAX_RETRY; trynum++) {
982 	if ((retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_SHARED))) {
983 	    if (db_ctx->db_nb_locks)
984 		return (retval);
985 	    sleep(1);
986 	    continue;
987 	}
988 	break;
989     }
990     if (trynum == KRB5_DB2_MAX_RETRY)
991 	return KRB5_KDB_DB_INUSE;
992 
993     /* XXX deal with wildcard lookups */
994     retval = krb5_encode_princ_dbkey(context, &keydata, searchfor);
995     if (retval)
996 	goto cleanup;
997     key.data = keydata.data;
998     key.size = keydata.length;
999 
1000     db = db_ctx->db;
1001     dbret = (*db->get) (db, &key, &contents, 0);
1002     retval = errno;
1003     krb5_free_data_contents(context, &keydata);
1004     switch (dbret) {
1005     case 1:
1006 	retval = 0;
1007     /*LINTED*/
1008     case -1:
1009     default:
1010 	*nentries = 0;
1011 	goto cleanup;
1012     case 0:
1013 	contdata.data = contents.data;
1014 	contdata.length = contents.size;
1015 	retval = krb5_decode_princ_contents(context, &contdata, entries);
1016 	if (!retval)
1017 	    *nentries = 1;
1018 	break;
1019     }
1020 
1021   cleanup:
1022     (void) krb5_db2_db_unlock(context);	/* unlock read lock */
1023     return retval;
1024 }
1025 
1026 /*
1027   Free stuff returned by krb5_db2_db_get_principal.
1028  */
1029 krb5_error_code
1030 krb5_db2_db_free_principal(krb5_context context, krb5_db_entry *entries,
1031 			   int nentries)
1032 {
1033     register int i;
1034     for (i = 0; i < nentries; i++)
1035 	krb5_dbe_free_contents(context, &entries[i]);
1036     return 0;
1037 }
1038 
1039 /*
1040   Stores the *"nentries" entry structures pointed to by "entries" in the
1041   database.
1042 
1043   *"nentries" is updated upon return to reflect the number of records
1044   acutally stored; the first *"nstored" records will have been stored in the
1045   database (even if an error occurs).
1046 
1047  */
1048 
1049 krb5_error_code
1050 krb5_db2_db_put_principal(krb5_context context,
1051 			  krb5_db_entry *entries,
1052 			  int *nentries, /* number of entry structs to update */
1053 			  char **db_args)
1054 {
1055     int     i, n, dbret;
1056     DB     *db;
1057     DBT     key, contents;
1058     krb5_data contdata, keydata;
1059     krb5_error_code retval;
1060     krb5_db2_context *db_ctx;
1061     kdb5_dal_handle *dal_handle;
1062     kdb_incr_update_t *upd, *fupd;
1063     char *princ_name = NULL;
1064     kdb_log_context *log_ctx;
1065 
1066     krb5_clear_error_message (context);
1067     if (db_args) {
1068 	/* DB2 does not support db_args DB arguments for principal */
1069 	krb5_set_error_message(context, EINVAL,
1070 			       gettext("Unsupported argument \"%s\" for db2"),
1071 			       db_args[0]);
1072 	return EINVAL;
1073     }
1074 
1075     log_ctx = context->kdblog_context;
1076 
1077     n = *nentries;
1078     *nentries = 0;
1079     if (!k5db2_inited(context))
1080 	return KRB5_KDB_DBNOTINITED;
1081 
1082     dal_handle = (kdb5_dal_handle *) context->db_context;
1083     db_ctx = (krb5_db2_context *) dal_handle->db_context;
1084     if ((retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
1085 	return retval;
1086 
1087     /*
1088      * Solaris Kerberos: We need the lock since ulog_conv_2logentry() does a get
1089      */
1090     if (log_ctx && (log_ctx->iproprole == IPROP_MASTER)) {
1091 	if (!(upd = (kdb_incr_update_t *)
1092 	  malloc(sizeof (kdb_incr_update_t)*n))) {
1093 	    retval = errno;
1094 	    goto err_lock;
1095 	}
1096 	fupd = upd;
1097 
1098 	(void) memset(upd, 0, sizeof(kdb_incr_update_t)*n);
1099 
1100         if ((retval = ulog_conv_2logentry(context, entries, upd, n))) {
1101 	    goto err_lock;
1102 	}
1103     }
1104 
1105     db = db_ctx->db;
1106     if ((retval = krb5_db2_db_start_update(context))) {
1107 	(void) krb5_db2_db_unlock(context);
1108 	goto err_lock;
1109     }
1110 
1111     /* for each one, stuff temps, and do replace/append */
1112     for (i = 0; i < n; i++) {
1113 	/*
1114 	 * Solaris Kerberos: We'll be sharing the same locks as db for logging
1115 	 */
1116         if (log_ctx && (log_ctx->iproprole == IPROP_MASTER)) {
1117 		if ((retval = krb5_unparse_name(context, entries->princ,
1118 		    &princ_name)))
1119 			goto err_lock;
1120 
1121 		upd->kdb_princ_name.utf8str_t_val = princ_name;
1122 		upd->kdb_princ_name.utf8str_t_len = strlen(princ_name);
1123 
1124                 if (retval = ulog_add_update(context, upd))
1125 			goto err_lock;
1126         }
1127 
1128 	retval = krb5_encode_princ_contents(context, &contdata, entries);
1129 	if (retval)
1130 	    break;
1131 	contents.data = contdata.data;
1132 	contents.size = contdata.length;
1133 	retval = krb5_encode_princ_dbkey(context, &keydata, entries->princ);
1134 	if (retval) {
1135 	    krb5_free_data_contents(context, &contdata);
1136 	    break;
1137 	}
1138 
1139 	key.data = keydata.data;
1140 	key.size = keydata.length;
1141 	dbret = (*db->put) (db, &key, &contents, 0);
1142 	retval = dbret ? errno : 0;
1143 	krb5_free_data_contents(context, &keydata);
1144 	krb5_free_data_contents(context, &contdata);
1145 	if (retval)
1146 	    break;
1147 	else if (log_ctx && (log_ctx->iproprole == IPROP_MASTER)) {
1148 	    /*
1149 	     * We need to make sure the db record is synced before we mark
1150 	     * it as committed via finish_update.
1151 	     */
1152 	    dbret = (*db->sync)(db, 0);
1153 	    if (dbret) {
1154 		retval = errno;
1155 		goto err_lock;
1156 	    }
1157 	    (void) ulog_finish_update(context, upd);
1158 	    upd++;
1159 	}
1160 	entries++;		/* bump to next struct */
1161     }
1162 
1163     (void) krb5_db2_db_end_update(context);
1164 
1165 err_lock:
1166     (void) krb5_db2_db_unlock(context);	/* unlock database */
1167 
1168     if (log_ctx && (log_ctx->iproprole == IPROP_MASTER))
1169         ulog_free_entries(fupd, n);
1170 
1171     *nentries = i;
1172     return (retval);
1173 }
1174 
1175 /*
1176  * delete a principal from the data base.
1177  * returns number of entries removed
1178  */
1179 
1180 krb5_error_code
1181 krb5_db2_db_delete_principal(krb5_context context,
1182 			     krb5_const_principal searchfor,
1183 			     int *nentries) /* how many found & deleted */
1184 {
1185     krb5_error_code retval;
1186     krb5_db_entry entry;
1187     krb5_db2_context *db_ctx;
1188     DB     *db;
1189     DBT     key, contents;
1190     krb5_data keydata, contdata;
1191     int     i, dbret;
1192     kdb5_dal_handle *dal_handle;
1193     kdb_incr_update_t upd;
1194     char *princ_name = NULL;
1195     kdb_log_context *log_ctx;
1196 
1197     log_ctx = context->kdblog_context;
1198 
1199     if (!k5db2_inited(context))
1200 	return KRB5_KDB_DBNOTINITED;
1201 
1202     dal_handle = (kdb5_dal_handle *) context->db_context;
1203     db_ctx = (krb5_db2_context *) dal_handle->db_context;
1204     if ((retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
1205 	return (retval);
1206 
1207     if ((retval = krb5_db2_db_start_update(context))) {
1208 	(void) krb5_db2_db_unlock(context);	/* unlock write lock */
1209 	return (retval);
1210     }
1211 
1212     if ((retval = krb5_encode_princ_dbkey(context, &keydata, searchfor)))
1213 	goto cleanup;
1214     key.data = keydata.data;
1215     key.size = keydata.length;
1216 
1217     db = db_ctx->db;
1218     dbret = (*db->get) (db, &key, &contents, 0);
1219     retval = errno;
1220     switch (dbret) {
1221     case 1:
1222 	retval = KRB5_KDB_NOENTRY;
1223     /*LINTED*/
1224     case -1:
1225     default:
1226 	*nentries = 0;
1227 	goto cleankey;
1228     case 0:
1229 	;
1230     }
1231     /*
1232      * Solaris Kerberos: We'll be sharing the same locks as db for logging
1233      */
1234     if (log_ctx && (log_ctx->iproprole == IPROP_MASTER)) {
1235 	if ((retval = krb5_unparse_name(context, searchfor, &princ_name))) {
1236 		(void) krb5_db2_db_unlock(context);
1237 		return retval;
1238 	}
1239 
1240 	(void) memset(&upd, 0, sizeof (kdb_incr_update_t));
1241 
1242 	upd.kdb_princ_name.utf8str_t_val = princ_name;
1243 	upd.kdb_princ_name.utf8str_t_len = strlen(princ_name);
1244 
1245 	if (retval = ulog_delete_update(context, &upd)) {
1246 		free(princ_name);
1247 		(void) krb5_db2_db_unlock(context);
1248 		return retval;
1249 	}
1250 
1251 	free(princ_name);
1252     }
1253 
1254     memset((char *) &entry, 0, sizeof(entry));
1255     contdata.data = contents.data;
1256     contdata.length = contents.size;
1257     retval = krb5_decode_princ_contents(context, &contdata, &entry);
1258     if (retval)
1259 	goto cleankey;
1260     *nentries = 1;
1261 
1262     /* Clear encrypted key contents */
1263     for (i = 0; i < entry.n_key_data; i++) {
1264 	if (entry.key_data[i].key_data_length[0]) {
1265 	    memset((char *) entry.key_data[i].key_data_contents[0], 0,
1266 		   (unsigned) entry.key_data[i].key_data_length[0]);
1267 	}
1268     }
1269 
1270     retval = krb5_encode_princ_contents(context, &contdata, &entry);
1271     krb5_dbe_free_contents(context, &entry);
1272     if (retval)
1273 	goto cleankey;
1274 
1275     contents.data = contdata.data;
1276     contents.size = contdata.length;
1277     dbret = (*db->put) (db, &key, &contents, 0);
1278     retval = dbret ? errno : 0;
1279     krb5_free_data_contents(context, &contdata);
1280     if (retval)
1281 	goto cleankey;
1282     dbret = (*db->del) (db, &key, 0);
1283     retval = dbret ? errno : 0;
1284 
1285     /*
1286      * We need to commit our update upon success
1287      */
1288     if (!retval)
1289 	if (log_ctx && (log_ctx->iproprole == IPROP_MASTER))
1290 		(void) ulog_finish_update(context, &upd);
1291 
1292   cleankey:
1293     krb5_free_data_contents(context, &keydata);
1294 
1295   cleanup:
1296     (void) krb5_db2_db_end_update(context);
1297     (void) krb5_db2_db_unlock(context);	/* unlock write lock */
1298     return retval;
1299 }
1300 
1301 krb5_error_code
1302 krb5_db2_db_iterate_ext(krb5_context context,
1303 			krb5_error_code(*func) (krb5_pointer, krb5_db_entry *),
1304 			krb5_pointer func_arg,
1305 			int backwards, int recursive)
1306 {
1307     krb5_db2_context *db_ctx;
1308     DB     *db;
1309     DBT     key, contents;
1310     krb5_data contdata;
1311     krb5_db_entry entries;
1312     krb5_error_code retval;
1313     kdb5_dal_handle *dal_handle;
1314     int     dbret;
1315     void   *cookie;
1316 
1317     cookie = NULL;
1318     if (!k5db2_inited(context))
1319 	return KRB5_KDB_DBNOTINITED;
1320 
1321     dal_handle = (kdb5_dal_handle *) context->db_context;
1322     db_ctx = (krb5_db2_context *) dal_handle->db_context;
1323     retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_SHARED);
1324 
1325     if (retval)
1326 	return retval;
1327 
1328     db = db_ctx->db;
1329     if (recursive && db->type != DB_BTREE) {
1330 	(void) krb5_db2_db_unlock(context);
1331 	return KRB5_KDB_UK_RERROR;	/* Not optimal, but close enough. */
1332     }
1333 
1334     if (!recursive) {
1335 	dbret = (*db->seq) (db, &key, &contents, backwards ? R_LAST : R_FIRST);
1336     } else {
1337 #ifdef HAVE_BT_RSEQ
1338 	dbret = bt_rseq(db, &key, &contents, &cookie,
1339 			backwards ? R_LAST : R_FIRST);
1340 #else
1341 	(void) krb5_db2_db_unlock(context);
1342 	return KRB5_KDB_UK_RERROR;	/* Not optimal, but close enough. */
1343 #endif
1344     }
1345     while (dbret == 0) {
1346 	contdata.data = contents.data;
1347 	contdata.length = contents.size;
1348 	retval = krb5_decode_princ_contents(context, &contdata, &entries);
1349 	if (retval)
1350 	    break;
1351 	retval = (*func) (func_arg, &entries);
1352 	krb5_dbe_free_contents(context, &entries);
1353 	if (retval)
1354 	    break;
1355 	if (!recursive) {
1356 	    dbret = (*db->seq) (db, &key, &contents,
1357 				backwards ? R_PREV : R_NEXT);
1358 	} else {
1359 #ifdef HAVE_BT_RSEQ
1360 	    dbret = bt_rseq(db, &key, &contents, &cookie,
1361 			    backwards ? R_PREV : R_NEXT);
1362 #else
1363 	    (void) krb5_db2_db_unlock(context);
1364 	    return KRB5_KDB_UK_RERROR;	/* Not optimal, but close enough. */
1365 #endif
1366 	}
1367     }
1368     switch (dbret) {
1369     case 1:
1370     case 0:
1371 	break;
1372     case -1:
1373     default:
1374 	retval = errno;
1375     }
1376     (void) krb5_db2_db_unlock(context);
1377     return retval;
1378 }
1379 
1380 krb5_error_code
1381 krb5_db2_db_iterate(krb5_context context,
1382 		    char *match_expr,
1383 		    krb5_error_code(*func) (krb5_pointer, krb5_db_entry *),
1384 		    krb5_pointer func_arg, char **db_args)
1385 {
1386     char  **t_ptr = db_args;
1387     int backwards = 0, recursive = 0;
1388 
1389     while (t_ptr && *t_ptr) {
1390 	char   *opt = NULL, *val = NULL;
1391 
1392 	krb5_db2_get_db_opt(*t_ptr, &opt, &val);
1393 
1394 	/* Solaris Kerberos: adding support for -rev/recurse flags */
1395 	if (val && !strcmp(val, "rev"))
1396 	    backwards = 1;
1397 	else if (val && !strcmp(val, "recurse"))
1398 	    recursive = 1;
1399 	else {
1400 	    krb5_set_error_message(context, EINVAL,
1401 				   gettext("Unsupported argument \"%s\" for db2"),
1402 				   val);
1403 	    free(opt);
1404 	    free(val);
1405 	    return EINVAL;
1406 	}
1407 
1408 	free(opt);
1409 	free(val);
1410 	t_ptr++;
1411     }
1412 
1413     /* Solaris Kerberos: adding support for -rev/recurse flags */
1414     return krb5_db2_db_iterate_ext(context, func, func_arg, backwards, recursive);
1415 }
1416 
1417 krb5_boolean
1418 krb5_db2_db_set_lockmode(krb5_context context, krb5_boolean mode)
1419 {
1420     krb5_boolean old;
1421     krb5_db2_context *db_ctx;
1422     kdb5_dal_handle *dal_handle;
1423 
1424     dal_handle = (kdb5_dal_handle *) context->db_context;
1425     old = mode;
1426     if (dal_handle && (db_ctx = (krb5_db2_context *) dal_handle->db_context)) {
1427 	old = db_ctx->db_nb_locks;
1428 	db_ctx->db_nb_locks = mode;
1429     }
1430     return old;
1431 }
1432 
1433 /*
1434  *     DAL API functions
1435  */
1436 krb5_error_code
1437 krb5_db2_lib_init()
1438 {
1439     return 0;
1440 }
1441 
1442 krb5_error_code
1443 krb5_db2_lib_cleanup()
1444 {
1445     /* right now, no cleanup required */
1446     return 0;
1447 }
1448 
1449 krb5_error_code
1450 krb5_db2_open(krb5_context kcontext,
1451 	      char *conf_section, char **db_args, int mode)
1452 {
1453     krb5_error_code status = 0;
1454     char  **t_ptr = db_args;
1455     int     db_name_set = 0, tempdb=0;
1456     char *dbname = NULL;
1457 
1458     krb5_clear_error_message (kcontext);
1459 
1460     if (k5db2_inited(kcontext))
1461 	return 0;
1462 
1463     while (t_ptr && *t_ptr) {
1464 	char   *opt = NULL, *val = NULL;
1465 
1466 	krb5_db2_get_db_opt(*t_ptr, &opt, &val);
1467 	if (opt && !strcmp(opt, "dbname")) {
1468 	    if (dbname) free(dbname);
1469 	    dbname = strdup(val);
1470 	}
1471 	else if (!opt && !strcmp(val, "temporary") ) {
1472 	    tempdb = 1;
1473 	}
1474 	/* ignore hash argument. Might have been passed from create */
1475 	else if (!opt || strcmp(opt, "hash")) {
1476 	    krb5_set_error_message(kcontext, EINVAL,
1477 				   gettext("Unsupported argument \"%s\" for db2"),
1478 				   opt ? opt : val);
1479 	    free(opt);
1480 	    free(val);
1481 	    return EINVAL;
1482 	}
1483 
1484 	free(opt);
1485 	free(val);
1486 	t_ptr++;
1487     }
1488 
1489     if(dbname) {
1490 	status = krb5_db2_db_set_name(kcontext, dbname, tempdb);
1491 	free(dbname);
1492 	if (status) {
1493 	    /* Solaris Kerberos: Better error logging */
1494 	    snprintf(errbuf, sizeof(errbuf), gettext("Failed to set db2 name to \"%s\": "), dbname);
1495 	    krb5_db2_prepend_err_str(kcontext, errbuf, status, status);
1496 
1497 	    goto clean_n_exit;
1498 	}
1499 	db_name_set = 1;
1500     }
1501     if (!db_name_set) {
1502 	char   *value = NULL;
1503 	status = profile_get_string(KRB5_DB_GET_PROFILE(kcontext), KDB_MODULE_SECTION, conf_section, KDB_DB2_DATABASE_NAME,	/* under given conf section */
1504 				    NULL, &value);
1505 
1506 	if (value == NULL) {
1507 	    /* special case for db2. We might actually be looking at old type config file where database is specified as part of realm */
1508 	    status = profile_get_string(KRB5_DB_GET_PROFILE(kcontext), KDB_REALM_SECTION, KRB5_DB_GET_REALM(kcontext), KDB_DB2_DATABASE_NAME,	/* under given realm */
1509 	        default_db_name, &value);
1510 
1511 	    if (status) {
1512 		/* Solaris Kerberos: Better error logging */
1513 		snprintf(errbuf, sizeof(errbuf), gettext("Failed when searching for "
1514 		    "\"%s\", \"%s\", \"%s\" in profile: "), KDB_REALM_SECTION,
1515 		    KRB5_DB_GET_REALM(kcontext), KDB_DB2_DATABASE_NAME);
1516 		krb5_db2_prepend_err_str(kcontext, errbuf, status, status);
1517 
1518 		goto clean_n_exit;
1519 	    }
1520 	}
1521 
1522 	status = krb5_db2_db_set_name(kcontext, value, tempdb);
1523 
1524 	if (status) {
1525 
1526 	    /* Solaris Kerberos: Better error logging */
1527 	    snprintf(errbuf, sizeof(errbuf), gettext("Failed to set db2 name to \"%s\": "), value);
1528 	    krb5_db2_prepend_err_str(kcontext, errbuf, status, status);
1529 	    profile_release_string(value);
1530 	    goto clean_n_exit;
1531 	}
1532 	profile_release_string(value);
1533 
1534     }
1535 
1536     status = krb5_db2_db_init(kcontext);
1537     if (status) {
1538         /* Solaris Kerberos: Better error logging */
1539         snprintf(errbuf, sizeof(errbuf), gettext("Failed to initialize db2 db: "));
1540         krb5_db2_prepend_err_str(kcontext, errbuf, status, status);
1541     }
1542 
1543   clean_n_exit:
1544     return status;
1545 }
1546 
1547 krb5_error_code
1548 krb5_db2_create(krb5_context kcontext, char *conf_section, char **db_args)
1549 {
1550     krb5_error_code status = 0;
1551     char  **t_ptr = db_args;
1552     int     db_name_set = 0, tempdb=0;
1553     krb5_int32 flags = KRB5_KDB_CREATE_BTREE;
1554     char   *db_name = NULL;
1555 
1556     krb5_clear_error_message (kcontext);
1557 
1558     if (k5db2_inited(kcontext))
1559 	return 0;
1560 
1561     while (t_ptr && *t_ptr) {
1562 	char   *opt = NULL, *val = NULL;
1563 
1564 	krb5_db2_get_db_opt(*t_ptr, &opt, &val);
1565 	if (opt && !strcmp(opt, "dbname")) {
1566 	    db_name = strdup(val);
1567 	    if (db_name == NULL)
1568 		return ENOMEM;
1569 	}
1570 	else if (!opt && !strcmp(val, "temporary")) {
1571 	    tempdb = 1;
1572 	}
1573 	else if (opt && !strcmp(opt, "hash")) {
1574 	    flags = KRB5_KDB_CREATE_HASH;
1575 	} else {
1576 	    krb5_set_error_message(kcontext, EINVAL,
1577 				   gettext("Unsupported argument \"%s\" for db2"),
1578 				   opt ? opt : val);
1579 	    free(opt);
1580 	    free(val);
1581 	    return EINVAL;
1582 	}
1583 
1584 	free(opt);
1585 	free(val);
1586 	t_ptr++;
1587     }
1588     if (db_name) {
1589 	    status = krb5_db2_db_set_name(kcontext, db_name, tempdb);
1590 	    if (!status) {
1591 		status = EEXIST;
1592 		goto clean_n_exit;
1593 	    }
1594 	    db_name_set = 1;
1595     }
1596     if (!db_name_set) {
1597 	char   *value = NULL;
1598 	status = profile_get_string(KRB5_DB_GET_PROFILE(kcontext),
1599 				    KDB_MODULE_SECTION, conf_section,
1600 				    /* under given conf section */
1601 				    KDB_DB2_DATABASE_NAME, NULL, &value);
1602 
1603 	if (value == NULL) {
1604 	    /* Special case for db2.  We might actually be looking at
1605 	     * old type config file where database is specified as
1606 	     * part of realm.  */
1607 	    status = profile_get_string(KRB5_DB_GET_PROFILE(kcontext),
1608 					KDB_REALM_SECTION,
1609 					KRB5_DB_GET_REALM(kcontext),
1610 					/* under given realm */
1611 					KDB_DB2_DATABASE_NAME,
1612 					default_db_name, &value);
1613 	    if (status) {
1614 		goto clean_n_exit;
1615 	    }
1616 	}
1617 
1618 	db_name = strdup(value);
1619 	/* Solaris Kerberos: for safety */
1620 	if (db_name == NULL) {
1621 	    status = ENOMEM;
1622 	    goto clean_n_exit;
1623 	}
1624 	status = krb5_db2_db_set_name(kcontext, value, tempdb);
1625 	profile_release_string(value);
1626 	if (!status) {
1627 	    status = EEXIST;
1628 	    goto clean_n_exit;
1629 	}
1630 
1631     }
1632 
1633     status = krb5_db2_db_create(kcontext, db_name, flags);
1634     if (status)
1635 	goto clean_n_exit;
1636     /* db2 has a problem of needing to close and open the database again. This removes that need */
1637     status = krb5_db2_db_fini(kcontext);
1638     if (status)
1639 	goto clean_n_exit;
1640 
1641     status = krb5_db2_open(kcontext, conf_section, db_args, KRB5_KDB_OPEN_RW);
1642 
1643   clean_n_exit:
1644     if (db_name)
1645 	free(db_name);
1646     return status;
1647 }
1648 
1649 krb5_error_code
1650 krb5_db2_destroy(krb5_context kcontext, char *conf_section, char **db_args)
1651 {
1652     krb5_error_code status = 0;
1653     char  **t_ptr = db_args;
1654     int     db_name_set = 0, tempdb=0;
1655     char   *db_name = NULL;
1656 
1657     while (t_ptr && *t_ptr) {
1658 	char   *opt = NULL, *val = NULL;
1659 
1660 	krb5_db2_get_db_opt(*t_ptr, &opt, &val);
1661 	if (opt && !strcmp(opt, "dbname")) {
1662 	    db_name = strdup(val);
1663 	    if (db_name == NULL)
1664 		return ENOMEM;
1665 	}
1666 	else if (!opt && !strcmp(val, "temporary")) {
1667 	    tempdb = 1;
1668 	}
1669 	/* ignore hash argument. Might have been passed from create */
1670 	else if (!opt || strcmp(opt, "hash")) {
1671 	    free(opt);
1672 	    free(val);
1673 	    return EINVAL;
1674 	}
1675 
1676 	free(opt);
1677 	free(val);
1678 	t_ptr++;
1679     }
1680 
1681     if (db_name) {
1682 	status = krb5_db2_db_set_name(kcontext, db_name, tempdb);
1683 	if (status) {
1684 	    goto clean_n_exit;
1685 	}
1686 	db_name_set = 1;
1687     }
1688     if (!db_name_set) {
1689 	char   *value = NULL;
1690 	status = profile_get_string(KRB5_DB_GET_PROFILE(kcontext), KDB_MODULE_SECTION, conf_section, KDB_DB2_DATABASE_NAME,	/* under given conf section */
1691 				    NULL, &value);
1692 
1693 	if (value == NULL) {
1694 	    /* special case for db2. We might actually be looking at old type config file where database is specified as part of realm */
1695 	    status = profile_get_string(KRB5_DB_GET_PROFILE(kcontext), KDB_REALM_SECTION, KRB5_DB_GET_REALM(kcontext), KDB_DB2_DATABASE_NAME,	/* under given realm */
1696 					default_db_name, &value);
1697 	    if (status) {
1698 		goto clean_n_exit;
1699 	    }
1700 	}
1701 
1702 	db_name = strdup(value);
1703 	if (db_name == NULL) {
1704 	    status = ENOMEM;
1705 	    goto clean_n_exit;
1706 	}
1707 	status = krb5_db2_db_set_name(kcontext, value, tempdb);
1708 	profile_release_string(value);
1709 	if (status) {
1710 	    goto clean_n_exit;
1711 	}
1712 
1713     }
1714 
1715     status = krb5_db2_db_destroy(kcontext, db_name);
1716 
1717   clean_n_exit:
1718     if (db_name)
1719 	free(db_name);
1720     return status;
1721 }
1722 
1723 krb5_error_code
1724 krb5_db2_set_master_key_ext(krb5_context kcontext,
1725 			    char *pwd, krb5_keyblock * key)
1726 {
1727     return krb5_db2_db_set_mkey(kcontext, key);
1728 }
1729 
1730 krb5_error_code
1731 krb5_db2_db_set_option(krb5_context kcontext, int option, void *value)
1732 {
1733     krb5_error_code status = 0;
1734     krb5_boolean oldval;
1735     krb5_db2_context *db_ctx;
1736     kdb5_dal_handle *dal_handle;
1737 
1738         if (!k5db2_inited(kcontext))
1739 	return KRB5_KDB_DBNOTINITED;
1740 
1741     dal_handle = (kdb5_dal_handle *) kcontext->db_context;
1742     db_ctx = (krb5_db2_context *) dal_handle->db_context;
1743 
1744 
1745     switch (option) {
1746     case KRB5_KDB_OPT_SET_DB_NAME:
1747 	status = krb5_db2_db_set_name(kcontext, (char *) value, db_ctx->tempdb);
1748 	break;
1749 
1750     case KRB5_KDB_OPT_SET_LOCK_MODE:
1751 	oldval = krb5_db2_db_set_lockmode(kcontext, *((krb5_boolean *) value));
1752 	*((krb5_boolean *) value) = oldval;
1753 	break;
1754 
1755     default:
1756 	status = -1;		/* TBD */
1757 	break;
1758     }
1759 
1760     return status;
1761 }
1762 
1763 void   *
1764 krb5_db2_alloc(krb5_context kcontext, void *ptr, size_t size)
1765 {
1766     return realloc(ptr, size);
1767 }
1768 
1769 void
1770 krb5_db2_free(krb5_context kcontext, void *ptr)
1771 {
1772     free(ptr);
1773 }
1774 
1775 /* policy functions */
1776 krb5_error_code
1777 krb5_db2_create_policy(krb5_context kcontext, osa_policy_ent_t policy)
1778 {
1779     kdb5_dal_handle *dal_handle;
1780     krb5_db2_context *dbc;
1781 
1782     dal_handle = (kdb5_dal_handle *) kcontext->db_context;
1783     dbc = (krb5_db2_context *) dal_handle->db_context;
1784 
1785     return osa_adb_create_policy(dbc->policy_db, policy);
1786 }
1787 
1788 krb5_error_code
1789 krb5_db2_get_policy(krb5_context kcontext,
1790 		    char *name, osa_policy_ent_t * policy, int *cnt)
1791 {
1792     kdb5_dal_handle *dal_handle;
1793     krb5_db2_context *dbc;
1794 
1795     dal_handle = (kdb5_dal_handle *) kcontext->db_context;
1796     dbc = (krb5_db2_context *) dal_handle->db_context;
1797 
1798     return osa_adb_get_policy(dbc->policy_db, name, policy, cnt);
1799 }
1800 
1801 krb5_error_code
1802 krb5_db2_put_policy(krb5_context kcontext, osa_policy_ent_t policy)
1803 {
1804     kdb5_dal_handle *dal_handle;
1805     krb5_db2_context *dbc;
1806 
1807     dal_handle = (kdb5_dal_handle *) kcontext->db_context;
1808     dbc = (krb5_db2_context *) dal_handle->db_context;
1809 
1810     return osa_adb_put_policy(dbc->policy_db, policy);
1811 }
1812 
1813 krb5_error_code
1814 krb5_db2_iter_policy(krb5_context kcontext,
1815 		     char *match_entry,
1816 		     osa_adb_iter_policy_func func, void *data)
1817 {
1818     kdb5_dal_handle *dal_handle;
1819     krb5_db2_context *dbc;
1820 
1821     dal_handle = (kdb5_dal_handle *) kcontext->db_context;
1822     dbc = (krb5_db2_context *) dal_handle->db_context;
1823 
1824     return osa_adb_iter_policy(dbc->policy_db, func, data);
1825 }
1826 
1827 krb5_error_code
1828 krb5_db2_delete_policy(krb5_context kcontext, char *policy)
1829 {
1830     kdb5_dal_handle *dal_handle;
1831     krb5_db2_context *dbc;
1832 
1833     dal_handle = (kdb5_dal_handle *) kcontext->db_context;
1834     dbc = (krb5_db2_context *) dal_handle->db_context;
1835 
1836     return osa_adb_destroy_policy(dbc->policy_db, policy);
1837 }
1838 
1839 void
1840 krb5_db2_free_policy(krb5_context kcontext, osa_policy_ent_t entry)
1841 {
1842     osa_free_policy_ent(entry);
1843 }
1844 
1845 
1846 /* */
1847 
1848 krb5_error_code
1849 krb5_db2_promote_db(krb5_context kcontext, char *conf_section, char **db_args)
1850 {
1851     krb5_error_code status = 0;
1852     char *db_name = NULL;
1853     char *temp_db_name = NULL;
1854 
1855     krb5_clear_error_message (kcontext);
1856 
1857     {
1858 	kdb5_dal_handle *dal_handle = kcontext->db_context;
1859 	krb5_db2_context *db_ctx = dal_handle->db_context;
1860 	db_name = strdup(db_ctx->db_name);
1861 	if (db_name == NULL) {
1862 	    status = ENOMEM;
1863 	    goto clean_n_exit;
1864 	}
1865     }
1866 
1867     assert(kcontext->db_context != NULL);
1868     temp_db_name = gen_dbsuffix(db_name, "~");
1869     if (temp_db_name == NULL) {
1870 	status = ENOMEM;
1871 	goto clean_n_exit;
1872     }
1873 
1874     status = krb5_db2_db_rename (kcontext, temp_db_name, db_name);
1875 
1876 clean_n_exit:
1877     if (db_name)
1878 	free(db_name);
1879     if (temp_db_name)
1880 	free(temp_db_name);
1881     return status;
1882 }
1883 
1884 /* Retrieved from pre-DAL code base.  */
1885 /*
1886  * "Atomically" rename the database in a way that locks out read
1887  * access in the middle of the rename.
1888  *
1889  * Not perfect; if we crash in the middle of an update, we don't
1890  * necessarily know to complete the transaction the rename, but...
1891  *
1892  * Since the rename operation happens outside the init/fini bracket, we
1893  * have to go through the same stuff that we went through up in db_destroy.
1894  */
1895 krb5_error_code
1896 krb5_db2_db_rename(context, from, to)
1897     krb5_context context;
1898     char *from;
1899     char *to;
1900 {
1901     char *fromok;
1902     krb5_error_code retval;
1903     krb5_db2_context *s_context, *db_ctx;
1904     kdb5_dal_handle *dal_handle = context->db_context;
1905 
1906     s_context = dal_handle->db_context;
1907     dal_handle->db_context = NULL;
1908     if ((retval = k5db2_init_context(context)))
1909 	return retval;
1910     db_ctx = (krb5_db2_context *) dal_handle->db_context;
1911 
1912     /*
1913      * Create the database if it does not already exist; the
1914      * files must exist because krb5_db2_db_lock, called below,
1915      * will fail otherwise.
1916      */
1917     {
1918 	struct stat statbuf;
1919 
1920 	if (stat(to, &statbuf) == -1) {
1921 	    if (errno == ENOENT) {
1922 		retval = krb5_db2_db_create(context, to,
1923 					    KRB5_KDB_CREATE_BTREE);
1924 		if (retval)
1925 		    goto errout;
1926 	    }
1927 	    else {
1928 		/*
1929 		 * XXX assuming we should bail if there is some other stat error
1930 		 */
1931 		retval = errno;
1932 		goto errout;
1933 	    }
1934 	}
1935     }
1936     /*
1937      * Set the database to the target, so that other processes sharing
1938      * the target will stop their activity, and notice the new database.
1939      */
1940     retval = krb5_db2_db_set_name(context, to, 0);
1941     if (retval)
1942 	goto errout;
1943 
1944     retval = krb5_db2_db_init(context);
1945     if (retval)
1946 	goto errout;
1947 
1948     /* XXX WAF this needs to be redone (not lock safe)!!! */
1949     {
1950 	/* Ugly brute force hack.
1951 
1952 	   Should be going through nice friendly helper routines for
1953 	   this, but it's a mess of jumbled so-called interfaces right
1954 	   now.  */
1955 	char    policy[2048], new_policy[2048];
1956 	assert (strlen(db_ctx->db_name) < 2000);
1957 	/*LINTED*/
1958 	sprintf(policy, "%s.kadm5", db_ctx->db_name);
1959 	/*LINTED*/
1960 	sprintf(new_policy, "%s~.kadm5", db_ctx->db_name);
1961 	if (0 != rename(new_policy, policy)) {
1962 	    retval = errno;
1963 	    goto errout;
1964 	}
1965 	strcat(new_policy, ".lock");
1966 	(void) unlink(new_policy);
1967     }
1968 
1969     retval = krb5_db2_db_get_age(context, NULL, &db_ctx->db_lf_time);
1970     if (retval)
1971 	goto errout;
1972 
1973     fromok = gen_dbsuffix(from, KDB2_LOCK_EXT);
1974     if (fromok == NULL) {
1975 	retval = ENOMEM;
1976 	goto errout;
1977     }
1978 
1979     if ((retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
1980 	goto errfromok;
1981 
1982     if ((retval = krb5_db2_db_start_update(context)))
1983 	goto errfromok;
1984 
1985     if (rename(from, to)) {
1986 	retval = errno;
1987 	goto errfromok;
1988     }
1989     if (unlink(fromok)) {
1990 	retval = errno;
1991 	goto errfromok;
1992     }
1993     retval = krb5_db2_db_end_update(context);
1994 errfromok:
1995     free_dbsuffix(fromok);
1996 errout:
1997     if (dal_handle->db_context) {
1998 	if (db_ctx->db_lf_file >= 0) {
1999 	    krb5_db2_db_unlock(context);
2000 	    close(db_ctx->db_lf_file);
2001 	}
2002 	k5db2_clear_context((krb5_db2_context *) dal_handle->db_context);
2003 	free(dal_handle->db_context);
2004     }
2005 
2006     dal_handle->db_context = s_context;
2007     (void) krb5_db2_db_unlock(context);	/* unlock saved context db */
2008 
2009     return retval;
2010 }
2011 
2012 const char *
2013 krb5_db2_errcode_2_string(krb5_context kcontext, long err_code)
2014 {
2015     return krb5_get_error_message(kcontext, err_code);
2016 }
2017 
2018 void
2019 krb5_db2_release_errcode_string(krb5_context kcontext, const char *msg)
2020 {
2021     krb5_free_error_message(kcontext, msg);
2022 }
2023 
2024 
2025 /*
2026  * Solaris Kerberos:
2027  * Similar to the ldap plugin.
2028  */
2029 static void
2030 krb5_db2_prepend_err_str(krb5_context ctx, const char *str, krb5_error_code err,
2031     krb5_error_code oerr) {
2032 	const char *omsg;
2033 	if (oerr == 0)
2034 		oerr = err;
2035 	omsg = krb5_get_error_message (ctx, err);
2036 	krb5_set_error_message (ctx, err, "%s %s", str, omsg);
2037 	krb5_free_error_message(ctx, omsg);
2038 }
2039 
2040