Update dependencies
[krb5.git] / src / lib / kdb / kdb_db2.c
1 /*
2  * lib/kdb/kdb_db2.c
3  *
4  * Copyright 1997 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  * 
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  * 
26  */
27
28 /*
29  * Copyright (C) 1998 by the FundsXpress, INC.
30  * 
31  * All rights reserved.
32  * 
33  * Export of this software from the United States of America may require
34  * a specific license from the United States Government.  It is the
35  * responsibility of any person or organization contemplating export to
36  * obtain such a license before exporting.
37  * 
38  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
39  * distribute this software and its documentation for any purpose and
40  * without fee is hereby granted, provided that the above copyright
41  * notice appear in all copies and that both that copyright notice and
42  * this permission notice appear in supporting documentation, and that
43  * the name of FundsXpress. not be used in advertising or publicity pertaining
44  * to distribution of the software without specific, written prior
45  * permission.  FundsXpress makes no representations about the suitability of
46  * this software for any purpose.  It is provided "as is" without express
47  * or implied warranty.
48  * 
49  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
50  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
51  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
52  */
53
54 #if HAVE_UNISTD_H
55 #include <unistd.h>
56 #endif
57
58 #include "k5-int.h"
59 #include <db.h>
60 #include <stdio.h>
61 #include <errno.h>
62 #include <utime.h>
63
64 #define OLD_COMPAT_VERSION_1
65
66 #ifdef OLD_COMPAT_VERSION_1
67 #include "kdb_compat.h"
68 #endif
69
70 #include "kdb_db2.h"
71
72 static char *gen_dbsuffix 
73         (char *, char * );
74 static krb5_error_code krb5_db2_db_start_update 
75         (krb5_context);
76 static krb5_error_code krb5_db2_db_end_update 
77         (krb5_context);
78 static krb5_error_code krb5_db2_db_set_hashfirst
79         (krb5_context, int);
80
81 static char default_db_name[] = DEFAULT_KDB_FILE;
82
83 /*
84  * Locking:
85  * 
86  * There are two distinct locking protocols used.  One is designed to
87  * lock against processes (the admin_server, for one) which make
88  * incremental changes to the database; the other is designed to lock
89  * against utilities (kdb5_edit, kpropd, kdb5_convert) which replace the
90  * entire database in one fell swoop.
91  *
92  * The first locking protocol is implemented using flock() in the 
93  * krb_dbl_lock() and krb_dbl_unlock routines.
94  *
95  * The second locking protocol is necessary because DBM "files" are
96  * actually implemented as two separate files, and it is impossible to
97  * atomically rename two files simultaneously.  It assumes that the
98  * database is replaced only very infrequently in comparison to the time
99  * needed to do a database read operation.
100  *
101  * A third file is used as a "version" semaphore; the modification
102  * time of this file is the "version number" of the database.
103  * At the start of a read operation, the reader checks the version
104  * number; at the end of the read operation, it checks again.  If the
105  * version number changed, or if the semaphore was nonexistant at
106  * either time, the reader sleeps for a second to let things
107  * stabilize, and then tries again; if it does not succeed after
108  * KRB5_DBM_MAX_RETRY attempts, it gives up.
109  * 
110  * On update, the semaphore file is deleted (if it exists) before any
111  * update takes place; at the end of the update, it is replaced, with
112  * a version number strictly greater than the version number which
113  * existed at the start of the update.
114  * 
115  * If the system crashes in the middle of an update, the semaphore
116  * file is not automatically created on reboot; this is a feature, not
117  * a bug, since the database may be inconsistant.  Note that the
118  * absence of a semaphore file does not prevent another _update_ from
119  * taking place later.  Database replacements take place automatically
120  * only on slave servers; a crash in the middle of an update will be
121  * fixed by the next slave propagation.  A crash in the middle of an
122  * update on the master would be somewhat more serious, but this would
123  * likely be noticed by an administrator, who could fix the problem and
124  * retry the operation.
125  */
126
127 #define free_dbsuffix(name) free(name)
128
129 /*
130  * Routines to deal with context.
131  */
132 #define k5db2_inited(c) (c && c->db_context &&  \
133                          ((krb5_db2_context *) c->db_context)->db_inited)
134
135 /*
136  * Restore the default context.
137  */
138 static void
139 k5db2_clear_context(dbctx)
140     krb5_db2_context *dbctx;
141 {
142     /*
143      * Free any dynamically allocated memory.  File descriptors and locks
144      * are the caller's problem.
145      */
146     if (dbctx->db_lf_name)
147         free(dbctx->db_lf_name);
148     if (dbctx->db_name && (dbctx->db_name != default_db_name))
149         free(dbctx->db_name);
150     /*
151      * Clear the structure and reset the defaults.
152      */
153     memset((char *) dbctx, 0, sizeof(krb5_db2_context));
154     dbctx->db_name = default_db_name;
155     dbctx->db_nb_locks = FALSE;
156 }
157
158 static krb5_error_code
159 k5db2_init_context(context)
160     krb5_context context;
161 {
162     krb5_db2_context *db_ctx;
163
164     if (context->db_context == NULL) {
165         db_ctx = (krb5_db2_context *) malloc(sizeof(krb5_db2_context));
166         if (db_ctx == NULL)
167             return ENOMEM;
168         else {
169             memset((char *) db_ctx, 0, sizeof(krb5_db2_context));
170             k5db2_clear_context((krb5_db2_context *)db_ctx);
171             context->db_context = (void *) db_ctx;
172         }
173     }
174     return(0);
175 }
176
177 /*
178  * Utility routine: generate name of database file.
179  */
180
181 static char *
182 gen_dbsuffix(db_name, sfx)
183     char *db_name;
184     char *sfx;
185 {
186     char *dbsuffix;
187     
188     if (sfx == NULL)
189         return((char *) NULL);
190
191     dbsuffix = malloc (strlen(db_name) + strlen(sfx) + 1);
192     if (!dbsuffix)
193         return(0);
194     (void) strcpy(dbsuffix, db_name);
195     (void) strcat(dbsuffix, sfx);
196     return dbsuffix;
197 }
198
199 static DB *
200 k5db2_dbopen(dbc, fname, flags, mode)
201     krb5_db2_context *dbc;
202     char *fname;
203     int flags;
204     int mode;
205 {
206     DB *db;
207     BTREEINFO bti;
208     HASHINFO hashi;
209
210     bti.flags = 0;
211     bti.cachesize = 0;
212     bti.psize = 4096;
213     bti.lorder = 0;
214     bti.minkeypage = 0;
215     bti.compare = NULL;
216     bti.prefix = NULL;
217
218     hashi.bsize = 4096;
219     hashi.cachesize = 0;
220     hashi.ffactor = 40;
221     hashi.hash = NULL;
222     hashi.lorder = 0;
223     hashi.nelem = 1;
224
225     db = dbopen(fname, flags, mode,
226                 dbc->hashfirst ? DB_HASH : DB_BTREE,
227                 dbc->hashfirst ? (void *) &hashi : (void *) &bti);
228     if (db != NULL)
229         return db;
230     switch (errno) {
231 #ifdef EFTYPE
232     case EFTYPE:
233 #endif
234     case EINVAL:
235         db = dbopen(fname, flags, mode,
236                     dbc->hashfirst ? DB_BTREE : DB_HASH,
237                     dbc->hashfirst ? (void *) &bti : (void *) &hashi);
238         if (db != NULL)
239             dbc->hashfirst = !dbc->hashfirst;
240     default:
241         return db;
242     }
243 }
244
245 static krb5_error_code
246 krb5_db2_db_set_hashfirst(context, hashfirst)
247     krb5_context context;
248     int hashfirst;
249 {
250     krb5_db2_context *dbc;
251
252     if (k5db2_inited(context))
253         return KRB5_KDB_DBNOTINITED;
254     dbc = (krb5_db2_context *) context->db_context;
255     dbc->hashfirst = hashfirst;
256     return 0;
257 }
258
259 /*
260  * initialization for data base routines.
261  */
262
263 krb5_error_code
264 krb5_db2_db_init(context)
265     krb5_context context;
266 {
267     char *filename = NULL;
268     krb5_db2_context *db_ctx;
269     krb5_error_code retval;
270
271     if (k5db2_inited(context))
272         return 0;
273
274     /* Check for presence of our context, if not present, allocate one. */
275     if ((retval = k5db2_init_context(context)))
276         return(retval);
277
278     db_ctx = context->db_context;
279     db_ctx->db = NULL;
280
281     if (!(filename = gen_dbsuffix(db_ctx->db_name, KDB2_LOCK_EXT)))
282         return ENOMEM;
283     db_ctx->db_lf_name = filename; /* so it gets freed by clear_context */
284
285     /*
286      * should be opened read/write so that write locking can work with
287      * POSIX systems
288      */
289     if ((db_ctx->db_lf_file = open(filename, O_RDWR, 0666)) < 0) {
290         if ((db_ctx->db_lf_file = open(filename, O_RDONLY, 0666)) < 0) {
291             retval = errno;
292             goto err_out;
293         }
294     }
295     db_ctx->db_inited++;
296
297     if ((retval = krb5_db2_db_get_age(context, NULL, &db_ctx->db_lf_time))) 
298         goto err_out;
299
300     return 0;
301     
302 err_out:
303     db_ctx->db = NULL;
304     k5db2_clear_context(db_ctx);
305     return (retval);
306 }
307
308 /*
309  * gracefully shut down database--must be called by ANY program that does
310  * a krb5_db2_db_init 
311  */
312 krb5_error_code
313 krb5_db2_db_fini(context)
314     krb5_context context;
315 {
316     krb5_error_code retval = 0;
317     krb5_db2_context *db_ctx;
318
319     db_ctx = (krb5_db2_context *) context->db_context;
320
321     if (k5db2_inited(context)) {
322         if (close(db_ctx->db_lf_file))
323             retval = errno;
324         else
325             retval = 0;
326     }
327     if (db_ctx) {
328         k5db2_clear_context(db_ctx);
329         free(context->db_context);
330         context->db_context = NULL;
331     }
332     return retval;
333 }
334
335 krb5_error_code
336 krb5_db2_db_open_database(context)
337     krb5_context context;
338 {
339     if (!k5db2_inited(context))
340         return KRB5_KDB_DBNOTINITED;
341     return 0;
342 }
343
344 krb5_error_code
345 krb5_db2_db_close_database(context)
346     krb5_context context;
347 {
348     if (!k5db2_inited(context))
349         return KRB5_KDB_DBNOTINITED;
350     return 0;
351 }
352
353 /*
354  * Set/Get the master key associated with the database
355  */
356 krb5_error_code
357 krb5_db2_db_set_mkey(context, key)
358     krb5_context context;
359     krb5_keyblock *key;
360 {
361     krb5_db2_context *db_ctx;
362
363     if (!k5db2_inited(context))
364         return(KRB5_KDB_DBNOTINITED);
365
366     db_ctx = context->db_context;
367     db_ctx->db_master_key = key;
368     return 0;
369 }
370
371 krb5_error_code
372 krb5_db2_db_get_mkey(context, key)
373     krb5_context context;
374     krb5_keyblock **key;
375 {
376     krb5_db2_context *db_ctx;
377
378     if (!k5db2_inited(context))
379         return(KRB5_KDB_DBNOTINITED);
380
381     db_ctx = context->db_context;
382     *key = db_ctx->db_master_key;
383
384     return 0;
385 }
386
387 /*
388  * Set the "name" of the current database to some alternate value.
389  *
390  * Passing a null pointer as "name" will set back to the default.
391  * If the alternate database doesn't exist, nothing is changed.
392  *
393  * XXX rethink this
394  */
395
396 krb5_error_code
397 krb5_db2_db_set_name(context, name)
398     krb5_context context;
399     char *name;
400 {
401     DB *db;
402     krb5_db2_context *db_ctx;
403     krb5_error_code kret;
404
405     if (k5db2_inited(context))
406         return KRB5_KDB_DBINITED;
407
408     /* Check for presence of our context, if not present, allocate one. */
409     if ((kret = k5db2_init_context(context)))
410         return(kret);
411
412     if (name == NULL)
413         name = default_db_name;
414
415     db_ctx = context->db_context;
416     db = k5db2_dbopen(db_ctx, name, O_RDONLY, 0);
417     if (db == NULL)
418         return errno;
419
420     db_ctx->db_name = strdup(name);
421     (*db->close)(db);
422     return 0;
423 }
424
425 /*
426  * Return the last modification time of the database.
427  *
428  * Think about using fstat.
429  */
430
431 krb5_error_code
432 krb5_db2_db_get_age(context, db_name, age)
433     krb5_context context;
434     char *db_name;
435     time_t *age;
436 {
437     krb5_db2_context *db_ctx;
438     struct stat st;
439
440     if (!k5db2_inited(context))
441         return(KRB5_KDB_DBNOTINITED);
442     db_ctx = (krb5_db2_context *) context->db_context;
443     if (fstat (db_ctx->db_lf_file, &st) < 0)
444         *age = -1;
445     else
446         *age = st.st_mtime;
447     return 0;
448 }
449
450 /*
451  * Remove the semaphore file; indicates that database is currently
452  * under renovation.
453  *
454  * This is only for use when moving the database out from underneath
455  * the server (for example, during slave updates).
456  */
457
458 static krb5_error_code
459 krb5_db2_db_start_update(context)
460     krb5_context context;
461 {
462     return 0;
463 }
464
465 static krb5_error_code
466 krb5_db2_db_end_update(context)
467     krb5_context context;
468 {
469     krb5_error_code retval;
470     krb5_db2_context *db_ctx;
471     struct stat st;
472     time_t now;
473     struct utimbuf utbuf;
474
475     if (!k5db2_inited(context))
476         return(KRB5_KDB_DBNOTINITED);
477
478     retval = 0;
479     db_ctx = context->db_context;
480     now = time((time_t *) NULL);
481     if (fstat(db_ctx->db_lf_file, &st) == 0) {
482         if (st.st_mtime >= now) {
483             utbuf.actime = st.st_mtime+1;
484             utbuf.modtime = st.st_mtime+1;
485             if (utime(db_ctx->db_lf_name, &utbuf))
486                 retval = errno;
487         }
488         else {
489             if (utime(db_ctx->db_lf_name, (struct utimbuf *) NULL))
490                 retval = errno;
491         }
492     }
493     else
494         retval = errno;
495     if (!retval) {
496         if (fstat(db_ctx->db_lf_file, &st) == 0)
497             db_ctx->db_lf_time = st.st_mtime;
498         else
499             retval = errno;
500     }
501     return(retval);
502 }
503
504 krb5_error_code
505 krb5_db2_db_lock(context, mode)
506     krb5_context          context;
507     int                   mode;
508 {
509     krb5_db2_context *db_ctx;
510     int krb5_lock_mode;
511     DB *db;
512     krb5_error_code retval;
513     time_t mod_time;
514
515     if (!k5db2_inited(context))
516         return KRB5_KDB_DBNOTINITED;
517
518     db_ctx = (krb5_db2_context *) context->db_context;
519     if (db_ctx->db_locks_held && (db_ctx->db_lock_mode >= mode)) {
520         /* No need to upgrade lock, just return */
521         db_ctx->db_locks_held++;
522         return(0);
523     }
524
525     if ((mode != KRB5_LOCKMODE_SHARED) && (mode != KRB5_LOCKMODE_EXCLUSIVE)) 
526         return KRB5_KDB_BADLOCKMODE;
527
528     if (db_ctx->db_nb_locks)
529         krb5_lock_mode = mode | KRB5_LOCKMODE_DONTBLOCK;
530     else
531         krb5_lock_mode = mode;
532     retval = krb5_lock_file(context, db_ctx->db_lf_file, krb5_lock_mode);
533     switch (retval) {
534     case EBADF:
535         if (mode == KRB5_LOCKMODE_EXCLUSIVE)
536             return KRB5_KDB_CANTLOCK_DB;
537     default:
538         return retval;
539     case 0:
540         break;
541     }
542
543     if ((retval = krb5_db2_db_get_age(context, NULL, &mod_time)))
544         goto lock_error;
545
546     db = k5db2_dbopen(db_ctx, db_ctx->db_name,
547                 mode == KRB5_LOCKMODE_SHARED ? O_RDONLY : O_RDWR,
548                 0600);
549     if (db) {
550          db_ctx->db_lf_time = mod_time;
551          db_ctx->db = db;
552     } else {
553          retval = errno;
554          db_ctx->db = NULL;
555          goto lock_error;
556     }
557
558     db_ctx->db_lock_mode = mode;
559     db_ctx->db_locks_held++;
560     return 0;
561
562 lock_error:;
563     db_ctx->db_lock_mode = 0;
564     db_ctx->db_locks_held = 0;
565     (void) krb5_db2_db_unlock(context);
566     return retval;
567 }
568
569 krb5_error_code
570 krb5_db2_db_unlock(context)
571     krb5_context context;
572 {
573     krb5_db2_context *db_ctx;
574     DB *db;
575     krb5_error_code retval;
576
577     if (!k5db2_inited(context))
578         return KRB5_KDB_DBNOTINITED;
579
580     db_ctx = (krb5_db2_context *) context->db_context;
581     if (!db_ctx->db_locks_held)         /* lock already unlocked */
582         return KRB5_KDB_NOTLOCKED;
583     db = db_ctx->db;
584     if (--(db_ctx->db_locks_held) == 0) {
585         (*db->close)(db);
586         db_ctx->db = NULL;
587
588         retval = krb5_lock_file(context, db_ctx->db_lf_file,
589                                 KRB5_LOCKMODE_UNLOCK);
590         db_ctx->db_lock_mode = 0;
591         return(retval);
592     }
593     return 0;
594 }
595
596 /*
597  * Create the database, assuming it's not there.
598  */
599 krb5_error_code
600 krb5_db2_db_create(context, db_name, flags)
601     krb5_context context;
602     char *db_name;
603     krb5_int32 flags;
604 {
605     register krb5_error_code retval = 0;
606     char *okname;
607     int fd;
608     krb5_db2_context *db_ctx;
609     DB *db;
610
611     if ((retval = k5db2_init_context(context)))
612         return(retval);
613
614     db_ctx = (krb5_db2_context *) context->db_context;
615     switch (flags) {
616     case KRB5_KDB_CREATE_HASH:
617         if ((retval = krb5_db2_db_set_hashfirst(context, TRUE)))
618             return retval;
619         break;
620     case KRB5_KDB_CREATE_BTREE:
621     case 0:
622         if ((retval = krb5_db2_db_set_hashfirst(context, FALSE)))
623             return retval;
624         break;
625     default:
626         return KRB5_KDB_BAD_CREATEFLAGS;
627     }
628     db = k5db2_dbopen(db_ctx, db_name, O_RDWR|O_CREAT|O_EXCL, 0600);
629     if (db == NULL)
630         retval = errno;
631     else
632         (*db->close)(db);
633     if (retval == 0) {
634         okname = gen_dbsuffix(db_name, KDB2_LOCK_EXT);
635         if (!okname)
636             retval = ENOMEM;
637         else {
638             fd = open (okname, O_CREAT|O_RDWR|O_TRUNC, 0600);
639             if (fd < 0)
640                 retval = errno;
641             else
642                 close(fd);
643             free_dbsuffix(okname);
644         }
645     }
646     return retval;
647 }
648
649 /*
650  * Destroy the database.  Zero's out all of the files, just to be sure.
651  */
652 static krb5_error_code
653 destroy_file_suffix(dbname, suffix)
654     char *dbname;
655     char *suffix;
656 {
657     char *filename;
658     struct stat statb;
659     int nb,fd;
660     unsigned int j;
661     off_t pos;
662     char buf[BUFSIZ];
663     char zbuf[BUFSIZ];
664     int dowrite;
665
666     filename = gen_dbsuffix(dbname, suffix);
667     if (filename == 0)
668         return ENOMEM;
669     if ((fd = open(filename, O_RDWR, 0)) < 0) {
670         free(filename);
671         return errno;
672     }
673     /* fstat() will probably not fail unless using a remote filesystem
674        (which is inappropriate for the kerberos database) so this check
675        is mostly paranoia.  */
676     if (fstat(fd, &statb) == -1) {
677         int retval = errno;
678         free(filename);
679         return retval;
680     }
681     /*
682      * Stroll through the file, reading in BUFSIZ chunks.  If everything
683      * is zero, then we're done for that block, otherwise, zero the block.
684      * We would like to just blast through everything, but some DB
685      * implementations make holey files and writing data to the holes
686      * causes actual blocks to be allocated which is no good, since
687      * we're just about to unlink it anyways.
688      */
689     memset(zbuf, 0, BUFSIZ);
690     pos = 0;
691     while (pos < statb.st_size) {
692         dowrite = 0;
693         nb = read(fd, buf, BUFSIZ);
694         if (nb < 0) {
695             int retval = errno;
696             free(filename);
697             return retval;
698         }
699         for (j=0; j<nb; j++) {
700             if (buf[j] != '\0') {
701                 dowrite = 1;
702                 break;
703             }
704         }
705         /* For signedness */
706         j = nb;
707         if (dowrite) {
708             lseek(fd, pos, SEEK_SET);
709             nb = write(fd, zbuf, j);
710             if (nb < 0) {
711                 int retval = errno;
712                 free(filename);
713                 return retval;
714             }
715         }
716         pos += nb;
717     }
718     /* ??? Is fsync really needed?  I don't know of any non-networked
719        filesystem which will discard queued writes to disk if a file
720        is deleted after it is closed.  --jfc */
721 #ifndef NOFSYNC
722     fsync(fd);
723 #endif
724     close(fd);
725
726     if (unlink(filename)) {
727         free(filename);
728         return(errno);
729     }
730     free(filename);
731     return(0);
732 }
733
734 /*
735  * Since the destroy operation happens outside the init/fini bracket, we
736  * have some tomfoolery to undergo here.  If we're operating under no
737  * database context, then we initialize with the default.  If the caller
738  * wishes a different context (e.g. different dispatch table), it's their
739  * responsibility to call kdb5_db_set_dbops() before this call.  That will
740  * set up the right dispatch table values (e.g. name extensions).
741  *
742  * Not quite valid due to ripping out of dbops...
743  */
744 krb5_error_code
745 krb5_db2_db_destroy(context, dbname)
746     krb5_context context;
747     char *dbname;
748 {
749     krb5_error_code retval1, retval2;
750     krb5_boolean tmpcontext;
751
752     tmpcontext = 0;
753     if (!context->db_context) {
754         tmpcontext = 1;
755         if ((retval1 = k5db2_init_context(context)))
756             return(retval1);
757     }
758
759     retval1 = retval2 = 0;
760     retval1 = destroy_file_suffix(dbname, "");
761     retval2 = destroy_file_suffix(dbname, KDB2_LOCK_EXT);
762
763     if (tmpcontext) {
764         k5db2_clear_context((krb5_db2_context *) context->db_context);
765         free(context->db_context);
766         context->db_context = NULL;
767     }
768
769     if (retval1 || retval2)
770         return (retval1 ? retval1 : retval2);
771     else
772         return 0;
773 }
774
775 /*
776  * "Atomically" rename the database in a way that locks out read
777  * access in the middle of the rename.
778  *
779  * Not perfect; if we crash in the middle of an update, we don't
780  * necessarily know to complete the transaction the rename, but...
781  *
782  * Since the rename operation happens outside the init/fini bracket, we
783  * have to go through the same stuff that we went through up in db_destroy.
784  */
785 krb5_error_code
786 krb5_db2_db_rename(context, from, to)
787     krb5_context context;
788     char *from;
789     char *to;
790 {
791     DB *db;
792     char *fromok;
793     krb5_error_code retval;
794     krb5_db2_context *s_context, *db_ctx;
795
796     s_context = context->db_context;
797     context->db_context = NULL;
798     if ((retval = k5db2_init_context(context)))
799         return retval;
800     db_ctx = (krb5_db2_context *) context->db_context;
801
802     /*
803      * Create the database if it does not already exist; the
804      * files must exist because krb5_db2_db_lock, called below,
805      * will fail otherwise.
806      */
807     db = k5db2_dbopen(db_ctx, to, O_RDWR|O_CREAT, 0600);
808     if (db == NULL) {
809         retval = errno;
810         goto errout;
811     }
812     else
813         (*db->close)(db);
814     /*
815      * Set the database to the target, so that other processes sharing
816      * the target will stop their activity, and notice the new database.
817      */
818     retval = krb5_db2_db_set_name(context, to);
819     if (retval)
820         goto errout;
821
822     db_ctx->db_lf_name = gen_dbsuffix(db_ctx->db_name, KDB2_LOCK_EXT);
823     if (db_ctx->db_lf_name == NULL) {
824         retval = ENOMEM;
825         goto errout;
826     }
827     db_ctx->db_lf_file = open(db_ctx->db_lf_name, O_RDWR|O_CREAT, 0600);
828     if (db_ctx->db_lf_file < 0) {
829         retval = errno;
830         goto errout;
831     }
832
833     db_ctx->db_inited = 1;
834
835     retval = krb5_db2_db_get_age(context, NULL, &db_ctx->db_lf_time);
836     if (retval)
837         goto errout;
838
839     fromok = gen_dbsuffix(from, KDB2_LOCK_EXT);
840     if (fromok == NULL) {
841         retval = ENOMEM;
842         goto errout;
843     }
844
845     if ((retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
846         goto errfromok;
847
848     if ((retval = krb5_db2_db_start_update(context)))
849         goto errfromok;
850
851     if (rename(from, to)) {
852         retval = errno;
853         goto errfromok;
854     }
855     if (unlink(fromok)) {
856         retval = errno;
857         goto errfromok;
858     }
859     retval = krb5_db2_db_end_update(context);
860 errfromok:
861     free_dbsuffix(fromok);
862 errout:
863     if (context->db_context) {
864         if (db_ctx->db_lf_file >= 0) {
865             krb5_db2_db_unlock(context);
866             close(db_ctx->db_lf_file);
867         }
868         k5db2_clear_context((krb5_db2_context *) context->db_context);
869         free(context->db_context);
870     }
871
872     context->db_context = s_context;
873     (void) krb5_db2_db_unlock(context); /* unlock saved context db */
874
875     return retval;
876 }
877
878 /*
879  * look up a principal in the data base.
880  * returns number of entries found, and whether there were
881  * more than requested. 
882  */
883
884 krb5_error_code
885 krb5_db2_db_get_principal(context, searchfor, entries, nentries, more)
886     krb5_context context;
887     krb5_const_principal searchfor;
888     krb5_db_entry *entries;     /* filled in */
889     int *nentries;              /* how much room/how many found */
890     krb5_boolean *more;         /* are there more? */
891 {
892     krb5_db2_context *db_ctx;
893     krb5_error_code retval;
894     DB *db;
895     DBT key, contents;
896     krb5_data keydata, contdata;
897     int trynum, dbret;
898
899     *more = FALSE;
900     *nentries = 0;
901
902     if (!k5db2_inited(context))
903         return KRB5_KDB_DBNOTINITED;
904
905     db_ctx = (krb5_db2_context *) context->db_context;
906     for (trynum = 0; trynum < KRB5_DB2_MAX_RETRY; trynum++) {
907         if ((retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_SHARED))) {
908             if (db_ctx->db_nb_locks) 
909                 return(retval);
910             sleep(1);
911             continue;
912         }
913         break;
914     }
915     if (trynum == KRB5_DB2_MAX_RETRY) 
916         return KRB5_KDB_DB_INUSE;
917
918     /* XXX deal with wildcard lookups */
919     retval = krb5_encode_princ_dbkey(context, &keydata, searchfor);
920     if (retval)
921         goto cleanup;
922     key.data = keydata.data;
923     key.size = keydata.length;
924
925     db = db_ctx->db;
926     dbret = (*db->get)(db, &key, &contents, 0);
927     retval = errno;
928     krb5_free_data_contents(context, &keydata);
929     switch (dbret) {
930     case 1:
931         retval = 0;
932     case -1:
933     default:
934         *nentries = 0;
935         goto cleanup;
936     case 0:
937         contdata.data = contents.data;
938         contdata.length = contents.size;
939         retval = krb5_decode_princ_contents(context, &contdata, entries);
940         if (!retval)
941             *nentries = 1;
942         break;
943     }
944
945 cleanup:
946     (void) krb5_db2_db_unlock(context);         /* unlock read lock */
947     return retval;
948 }
949
950 /*
951   Free stuff returned by krb5_db2_db_get_principal.
952  */
953 void
954 krb5_db2_db_free_principal(context, entries, nentries)
955     krb5_context context;
956     krb5_db_entry *entries;
957     int nentries;
958 {
959     register int i;
960     for (i = 0; i < nentries; i++)
961         krb5_dbe_free_contents(context, &entries[i]);
962     return;
963 }
964
965 /*
966   Stores the *"nentries" entry structures pointed to by "entries" in the
967   database.
968
969   *"nentries" is updated upon return to reflect the number of records
970   acutally stored; the first *"nstored" records will have been stored in the
971   database (even if an error occurs).
972
973  */
974
975 krb5_error_code
976 krb5_db2_db_put_principal(context, entries, nentries)
977     krb5_context context;
978     krb5_db_entry *entries;
979     register int *nentries;             /* number of entry structs to update */
980 {
981     int i, n, dbret;
982     DB *db;
983     DBT key, contents;
984     krb5_data contdata, keydata;
985     krb5_error_code retval;
986     krb5_db2_context *db_ctx;
987
988     n = *nentries;
989     *nentries = 0;
990     if (!k5db2_inited(context))
991         return KRB5_KDB_DBNOTINITED;
992
993     db_ctx = (krb5_db2_context *) context->db_context;
994     if ((retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
995         return retval;
996
997     db = db_ctx->db;
998     if ((retval = krb5_db2_db_start_update(context))) {
999         (void)krb5_db2_db_unlock(context);
1000         return retval;
1001     }
1002
1003     /* for each one, stuff temps, and do replace/append */
1004     for (i = 0; i < n; i++) {
1005         retval = krb5_encode_princ_contents(context, &contdata, entries);
1006         if (retval)
1007             break;
1008         contents.data = contdata.data;
1009         contents.size = contdata.length;
1010         retval = krb5_encode_princ_dbkey(context, &keydata, entries->princ);
1011         if (retval) {
1012             krb5_free_data_contents(context, &contdata);
1013             break;
1014         }
1015
1016         key.data = keydata.data;
1017         key.size = keydata.length;
1018         dbret = (*db->put)(db, &key, &contents, 0);
1019         retval = dbret ? errno : 0;
1020         krb5_free_data_contents(context, &keydata);
1021         krb5_free_data_contents(context, &contdata);
1022         if (retval)
1023             break;
1024         entries++;                      /* bump to next struct */
1025     }
1026
1027     (void)krb5_db2_db_end_update(context);
1028     (void)krb5_db2_db_unlock(context);          /* unlock database */
1029     *nentries = i;
1030     return(retval);
1031 }
1032
1033 /*
1034  * delete a principal from the data base.
1035  * returns number of entries removed
1036  */
1037
1038 krb5_error_code
1039 krb5_db2_db_delete_principal(context, searchfor, nentries)
1040     krb5_context context;
1041     krb5_const_principal searchfor;
1042     int *nentries;              /* how many found & deleted */
1043 {
1044     krb5_error_code retval;
1045     krb5_db_entry entry;
1046     krb5_db2_context *db_ctx;
1047     DB *db;
1048     DBT key, contents;
1049     krb5_data keydata, contdata;
1050     int i, dbret;
1051
1052     if (!k5db2_inited(context))
1053         return KRB5_KDB_DBNOTINITED;
1054
1055     db_ctx = (krb5_db2_context *) context->db_context;
1056     if ((retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
1057         return(retval);
1058
1059     if ((retval = krb5_db2_db_start_update(context))) {
1060         (void) krb5_db2_db_unlock(context); /* unlock write lock */
1061         return(retval);
1062     }
1063
1064     if ((retval = krb5_encode_princ_dbkey(context, &keydata, searchfor)))
1065         goto cleanup;
1066     key.data = keydata.data;
1067     key.size = keydata.length;
1068
1069     db = db_ctx->db;
1070     dbret = (*db->get)(db, &key, &contents, 0);
1071     retval = errno;
1072     switch (dbret) {
1073     case 1:
1074         retval = KRB5_KDB_NOENTRY;
1075     case -1:
1076     default:
1077         *nentries = 0;
1078         goto cleankey;
1079     case 0:
1080         ;
1081     }
1082     memset((char *)&entry, 0, sizeof(entry));
1083     contdata.data = contents.data;
1084     contdata.length = contents.size;
1085     retval = krb5_decode_princ_contents(context, &contdata, &entry);
1086     if (retval)
1087         goto cleankey;
1088     *nentries = 1;
1089
1090     /* Clear encrypted key contents */
1091     for (i = 0; i < entry.n_key_data; i++) {
1092         if (entry.key_data[i].key_data_length[0]) {
1093             memset((char *)entry.key_data[i].key_data_contents[0], 0, 
1094                    (unsigned) entry.key_data[i].key_data_length[0]); 
1095         }
1096     }
1097
1098     retval = krb5_encode_princ_contents(context, &contdata, &entry);
1099     krb5_dbe_free_contents(context, &entry);
1100     if (retval)
1101         goto cleankey;
1102
1103     contents.data = contdata.data;
1104     contents.size = contdata.length;
1105     dbret = (*db->put)(db, &key, &contents, 0);
1106     retval = dbret ? errno : 0;
1107     krb5_free_data_contents(context, &contdata);
1108     if (retval)
1109         goto cleankey;
1110     dbret = (*db->del)(db, &key, 0);
1111     retval = dbret ? errno : 0;
1112 cleankey:
1113     krb5_free_data_contents(context, &keydata);
1114
1115 cleanup:
1116     (void) krb5_db2_db_end_update(context);
1117     (void) krb5_db2_db_unlock(context); /* unlock write lock */
1118     return retval;
1119 }
1120
1121 krb5_error_code
1122 krb5_db2_db_iterate_ext(context, func, func_arg, backwards, recursive)
1123     krb5_context context;
1124     krb5_error_code (*func) (krb5_pointer, krb5_db_entry *);
1125     krb5_pointer func_arg;
1126     int backwards, recursive;
1127 {
1128     krb5_db2_context *db_ctx;
1129     DB *db;
1130     DBT key, contents;
1131     krb5_data contdata;
1132     krb5_db_entry entries;
1133     krb5_error_code retval;
1134     int dbret;
1135     void *cookie;
1136
1137     cookie = NULL;
1138     if (!k5db2_inited(context))
1139         return KRB5_KDB_DBNOTINITED;
1140
1141     db_ctx = (krb5_db2_context *) context->db_context;
1142     retval = krb5_db2_db_lock(context, KRB5_LOCKMODE_SHARED);
1143
1144     if (retval)
1145         return retval;
1146
1147     db = db_ctx->db;
1148     if (recursive && db->type != DB_BTREE) {
1149         (void)krb5_db2_db_unlock(context);
1150         return KRB5_KDB_UK_RERROR; /* Not optimal, but close enough. */
1151     }
1152
1153     if (!recursive) {
1154         dbret = (*db->seq)(db, &key, &contents,
1155                            backwards ? R_LAST : R_FIRST);
1156     } else {
1157 #ifdef HAVE_BT_RSEQ
1158         dbret = bt_rseq(db, &key, &contents, &cookie,
1159                         backwards ? R_LAST : R_FIRST);
1160 #else
1161         (void)krb5_db2_db_unlock(context);
1162         return KRB5_KDB_UK_RERROR; /* Not optimal, but close enough. */
1163 #endif
1164     }
1165     while (dbret == 0) {
1166         contdata.data = contents.data;
1167         contdata.length = contents.size;
1168         retval = krb5_decode_princ_contents(context, &contdata, &entries);
1169         if (retval)
1170             break;
1171         retval = (*func)(func_arg, &entries);
1172         krb5_dbe_free_contents(context, &entries);
1173         if (retval)
1174             break;
1175         if (!recursive) {
1176             dbret = (*db->seq)(db, &key, &contents,
1177                                backwards ? R_PREV : R_NEXT);
1178         } else {
1179 #ifdef HAVE_BT_RSEQ
1180             dbret = bt_rseq(db, &key, &contents, &cookie,
1181                             backwards ? R_PREV : R_NEXT);
1182 #else
1183             (void)krb5_db2_db_unlock(context);
1184             return KRB5_KDB_UK_RERROR; /* Not optimal, but close enough. */
1185 #endif
1186         }
1187     }
1188     switch (dbret) {
1189     case 1:
1190     case 0:
1191         break;
1192     case -1:
1193     default:
1194         retval = errno;
1195     }
1196     (void) krb5_db2_db_unlock(context);
1197     return retval;
1198 }
1199
1200 krb5_error_code
1201 krb5_db2_db_iterate(context, func, func_arg)
1202     krb5_context context;
1203     krb5_error_code (*func) (krb5_pointer, krb5_db_entry *);
1204     krb5_pointer func_arg;
1205 {
1206     return krb5_db2_db_iterate_ext(context, func, func_arg, 0, 0);
1207 }
1208
1209 krb5_boolean
1210 krb5_db2_db_set_lockmode(context, mode)
1211     krb5_context context;
1212     krb5_boolean mode;
1213 {
1214     krb5_boolean old;
1215     krb5_db2_context *db_ctx;
1216
1217     old = mode;
1218     if ((db_ctx = (krb5_db2_context *) context->db_context)) {
1219         old = db_ctx->db_nb_locks;
1220         db_ctx->db_nb_locks = mode;
1221     }
1222     return old;
1223 }
1224
1225 /*
1226  * Context serialization operations.
1227  *
1228  * Ick, this is really gross. --- tlyu
1229  */
1230
1231 /*
1232  * kdb5_context_size()  - Determine size required to serialize.
1233  */
1234 static krb5_error_code
1235 kdb5_context_size(kcontext, arg, sizep)
1236     krb5_context        kcontext;
1237     krb5_pointer        arg;
1238     size_t              *sizep;
1239 {
1240     krb5_error_code     kret;
1241     size_t              required;
1242     krb5_db2_context    *dbctx;
1243
1244     /*
1245      * The database context requires at minimum:
1246      *  krb5_int32      for KV5M_DB_CONTEXT
1247      *  krb5_int32      for db_inited
1248      *  krb5_int32      for database lockfile non-blocking flag
1249      *  krb5_int32      for database lockfile lock count
1250      *  krb5_int32      for database lockfile lock mode
1251      *  krb5_int32      for length of database name.
1252      *  krb5_int32      for KV5M_DB_CONTEXT
1253      */
1254     kret = EINVAL;
1255     if ((dbctx = (krb5_db2_context *) arg)) {
1256         required = (sizeof(krb5_int32) * 7);
1257         if (dbctx->db_inited && dbctx->db_name)
1258             required += strlen(dbctx->db_name);
1259         kret = 0;
1260         *sizep += required;
1261     }
1262     return(kret);
1263 }
1264 \f
1265 /*
1266  * kdb5_context_externalize()   - Externalize the database context.
1267  */
1268 static krb5_error_code
1269 kdb5_context_externalize(kcontext, arg, buffer, lenremain)
1270     krb5_context        kcontext;
1271     krb5_pointer        arg;
1272     krb5_octet          **buffer;
1273     size_t              *lenremain;
1274 {
1275     krb5_error_code     kret;
1276     krb5_db2_context    *dbctx;
1277     size_t              required;
1278     krb5_octet          *bp;
1279     size_t              remain;
1280
1281     required = 0;
1282     bp = *buffer;
1283     remain = *lenremain;
1284     kret = EINVAL;
1285     if ((dbctx = (krb5_db2_context *) arg)) {
1286         kret = ENOMEM;
1287         if (!kdb5_context_size(kcontext, arg, &required) &&
1288             (required <= remain)) {
1289             /* Write magic number */
1290             (void) krb5_ser_pack_int32(KV5M_DB_CONTEXT, &bp, &remain);
1291
1292             /* Write inited flag */
1293             (void) krb5_ser_pack_int32((krb5_int32) dbctx->db_inited,
1294                                        &bp, &remain);
1295
1296             /* Write blocking lock lockmode */
1297             (void) krb5_ser_pack_int32((krb5_int32) dbctx->db_nb_locks,
1298                                        &bp, &remain);
1299
1300             /* Write lock count */
1301             (void) krb5_ser_pack_int32((krb5_int32)
1302                                        (dbctx->db_inited) ?
1303                                        dbctx->db_locks_held : 0,
1304                                        &bp, &remain);
1305
1306             /* Write lock mode */
1307             (void) krb5_ser_pack_int32((krb5_int32)
1308                                        (dbctx->db_inited) ?
1309                                        dbctx->db_lock_mode : 0,
1310                                        &bp, &remain);
1311
1312             /* Write length of database name */
1313             (void) krb5_ser_pack_int32((dbctx->db_inited && dbctx->db_name) ?
1314                                        (krb5_int32) strlen(dbctx->db_name) : 0,
1315                                        &bp, &remain);
1316             if (dbctx->db_inited && dbctx->db_name)
1317                 (void) krb5_ser_pack_bytes((krb5_octet *) dbctx->db_name,
1318                                            strlen(dbctx->db_name),
1319                                            &bp, &remain);
1320
1321             /* Write trailer */
1322             (void) krb5_ser_pack_int32(KV5M_DB_CONTEXT, &bp, &remain);
1323             kret = 0;
1324             *buffer = bp;
1325             *lenremain = remain;
1326         }
1327     }
1328     return(kret);
1329 }
1330 \f
1331 /*
1332  * kdb5_context_internalize()   - Internalize the database context.
1333  */
1334 static krb5_error_code
1335 kdb5_context_internalize(kcontext, argp, buffer, lenremain)
1336     krb5_context        kcontext;
1337     krb5_pointer        *argp;
1338     krb5_octet          **buffer;
1339     size_t              *lenremain;
1340 {
1341     krb5_error_code     kret;
1342     krb5_context        tmpctx;
1343     krb5_db2_context    *dbctx;
1344     krb5_int32          ibuf;
1345     krb5_octet          *bp;
1346     size_t              remain;
1347     krb5_int32          iflag;
1348     krb5_int32          nb_lockmode;
1349     krb5_int32          lockcount;
1350     krb5_int32          lockmode;
1351     krb5_int32          dbnamelen;
1352     krb5_boolean        nb_lock;
1353     char                *dbname;
1354
1355     bp = *buffer;
1356     remain = *lenremain;
1357     kret = EINVAL;
1358     dbctx = (krb5_db2_context *) NULL;
1359     /* Read our magic number */
1360     if (krb5_ser_unpack_int32(&ibuf, &bp, &remain))
1361         ibuf = 0;
1362     if (ibuf == KV5M_DB_CONTEXT) {
1363         kret = ENOMEM;
1364
1365         if (!(kret = krb5_ser_unpack_int32(&iflag, &bp, &remain)) &&
1366             !(kret = krb5_ser_unpack_int32(&nb_lockmode, &bp, &remain)) &&
1367             !(kret = krb5_ser_unpack_int32(&lockcount, &bp, &remain)) &&
1368             !(kret = krb5_ser_unpack_int32(&lockmode, &bp, &remain)) &&
1369             !(kret = krb5_ser_unpack_int32(&dbnamelen, &bp, &remain)) &&
1370             !(kret = krb5_init_context(&tmpctx))) {
1371             if (iflag) {
1372                 dbname = (char *) NULL;
1373                 if (dbnamelen &&
1374                     (dbname = (char *) malloc((size_t) (dbnamelen+1)))) {
1375                     kret = krb5_ser_unpack_bytes((krb5_octet *) dbname,
1376                                                  (size_t) dbnamelen,
1377                                                  &bp, &remain);
1378                     if (!kret)
1379                         dbname[dbnamelen] = '\0';
1380                 }
1381                 if (!kret &&
1382                     (!dbname || !(kret = krb5_db_set_name(tmpctx, dbname))) &&
1383                     !(kret = krb5_db_init(tmpctx))) {
1384                     dbctx = (krb5_db2_context *) tmpctx->db_context;
1385                     (void) krb5_db2_db_set_lockmode(tmpctx, 0);
1386                     if (lockmode)
1387                         kret = krb5_db_lock(tmpctx, lockmode);
1388                     if (!kret && lockmode)
1389                         dbctx->db_locks_held = lockcount;
1390                     nb_lock = nb_lockmode & 0xff;
1391                     (void) krb5_db2_db_set_lockmode(tmpctx, nb_lock);
1392                 }
1393                 if (dbname)
1394                     krb5_xfree(dbname);
1395             }
1396             if (!kret)
1397                 kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
1398             if (kret || (ibuf != KV5M_DB_CONTEXT))
1399                 kret = EINVAL;
1400
1401             if (kret) {
1402                 if (dbctx)
1403                     krb5_db_fini(tmpctx);
1404             }
1405             else
1406                 tmpctx->db_context = NULL;
1407             krb5_free_context(tmpctx);
1408         }
1409     }
1410     if (!kret) {
1411         *buffer = bp;
1412         *lenremain = remain;
1413         *argp = (krb5_pointer) dbctx;
1414     }
1415     return(kret);
1416 }
1417
1418 /* Dispatch entry */
1419 static const krb5_ser_entry kdb5_context_ser_entry = {
1420     KV5M_DB_CONTEXT,                    /* Type                 */
1421     kdb5_context_size,                  /* Sizer routine        */
1422     kdb5_context_externalize,           /* Externalize routine  */
1423     kdb5_context_internalize            /* Externalize routine  */
1424 };
1425
1426 /*
1427  * Register serializer.
1428  */
1429 krb5_error_code
1430 krb5_ser_db_context_init(kcontext)
1431     krb5_context        kcontext;
1432 {
1433     return(krb5_register_serializer(kcontext, &kdb5_context_ser_entry));
1434 }