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