Imported Upstream version 1.10.2
[platform/upstream/krb5.git] / src / plugins / kdb / db2 / kdb_db2.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/kdb/db2/kdb_db2.c */
3 /*
4  * Copyright 1997,2006,2007-2009 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 #include "k5-int.h"
55
56 #if HAVE_UNISTD_H
57 #include <unistd.h>
58 #endif
59
60 #include <db.h>
61 #include <stdio.h>
62 #include <errno.h>
63 #include <utime.h>
64 #include "kdb5.h"
65 #include "kdb_db2.h"
66 #include "kdb_xdr.h"
67 #include "policy_db.h"
68
69 #define KDB_DB2_DATABASE_NAME "database_name"
70
71 #define SUFFIX_DB ""
72 #define SUFFIX_LOCK ".ok"
73 #define SUFFIX_POLICY ".kadm5"
74 #define SUFFIX_POLICY_LOCK ".kadm5.lock"
75
76 /*
77  * Locking:
78  *
79  * There are two distinct locking protocols used.  One is designed to
80  * lock against processes (the admin_server, for one) which make
81  * incremental changes to the database; the other is designed to lock
82  * against utilities (kdb5_edit, kpropd, kdb5_convert) which replace the
83  * entire database in one fell swoop.
84  *
85  * The first locking protocol is implemented using flock() in the
86  * krb_dbl_lock() and krb_dbl_unlock routines.
87  *
88  * The second locking protocol is necessary because DBM "files" are
89  * actually implemented as two separate files, and it is impossible to
90  * atomically rename two files simultaneously.  It assumes that the
91  * database is replaced only very infrequently in comparison to the time
92  * needed to do a database read operation.
93  *
94  * A third file is used as a "version" semaphore; the modification
95  * time of this file is the "version number" of the database.
96  * At the start of a read operation, the reader checks the version
97  * number; at the end of the read operation, it checks again.  If the
98  * version number changed, or if the semaphore was nonexistant at
99  * either time, the reader sleeps for a second to let things
100  * stabilize, and then tries again; if it does not succeed after
101  * KRB5_DBM_MAX_RETRY attempts, it gives up.
102  *
103  * On update, the semaphore file is deleted (if it exists) before any
104  * update takes place; at the end of the update, it is replaced, with
105  * a version number strictly greater than the version number which
106  * existed at the start of the update.
107  *
108  * If the system crashes in the middle of an update, the semaphore
109  * file is not automatically created on reboot; this is a feature, not
110  * a bug, since the database may be inconsistant.  Note that the
111  * absence of a semaphore file does not prevent another _update_ from
112  * taking place later.  Database replacements take place automatically
113  * only on slave servers; a crash in the middle of an update will be
114  * fixed by the next slave propagation.  A crash in the middle of an
115  * update on the master would be somewhat more serious, but this would
116  * likely be noticed by an administrator, who could fix the problem and
117  * retry the operation.
118  */
119
120 /* Evaluate to true if the krb5_context c contains an initialized db2
121  * context. */
122 #define inited(c) ((c)->dal_handle->db_context &&                       \
123                    ((krb5_db2_context *)(c)->dal_handle->db_context)->  \
124                    db_inited)
125
126 static krb5_error_code
127 get_db_opt(char *input, char **opt, char **val)
128 {
129     char   *pos = strchr(input, '=');
130     if (pos == NULL) {
131         *opt = NULL;
132         *val = strdup(input);
133         if (*val == NULL) {
134             return ENOMEM;
135         }
136     } else {
137         *opt = malloc((pos - input) + 1);
138         *val = strdup(pos + 1);
139         if (!*opt || !*val) {
140             free(*opt);
141             *opt = NULL;
142             free(*val);
143             *val = NULL;
144             return ENOMEM;
145         }
146         memcpy(*opt, input, pos - input);
147         (*opt)[pos - input] = '\0';
148     }
149     return (0);
150
151 }
152
153 /* Restore dbctx to the uninitialized state. */
154 static void
155 ctx_clear(krb5_db2_context *dbc)
156 {
157     /*
158      * Free any dynamically allocated memory.  File descriptors and locks
159      * are the caller's problem.
160      */
161     free(dbc->db_lf_name);
162     free(dbc->db_name);
163     /*
164      * Clear the structure and reset the defaults.
165      */
166     memset(dbc, 0, sizeof(krb5_db2_context));
167     dbc->db = NULL;
168     dbc->db_lf_name = NULL;
169     dbc->db_lf_file = -1;
170     dbc->db_name = NULL;
171     dbc->db_nb_locks = FALSE;
172     dbc->tempdb = FALSE;
173 }
174
175 /* Set *dbc_out to the db2 database context for context.  If one does not
176  * exist, create one in the uninitialized state. */
177 static krb5_error_code
178 ctx_get(krb5_context context, krb5_db2_context **dbc_out)
179 {
180     krb5_db2_context *dbc;
181     kdb5_dal_handle *dal_handle;
182
183     dal_handle = context->dal_handle;
184
185     if (dal_handle->db_context == NULL) {
186         dbc = (krb5_db2_context *) malloc(sizeof(krb5_db2_context));
187         if (dbc == NULL)
188             return ENOMEM;
189         else {
190             memset(dbc, 0, sizeof(krb5_db2_context));
191             ctx_clear(dbc);
192             dal_handle->db_context = dbc;
193         }
194     }
195     *dbc_out = dal_handle->db_context;
196     return 0;
197 }
198
199 /* Using db_args and the profile, initialize the configurable parameters of the
200  * DB context inside context. */
201 static krb5_error_code
202 configure_context(krb5_context context, char *conf_section, char **db_args)
203 {
204     krb5_error_code status;
205     krb5_db2_context *dbc;
206     char **t_ptr, *opt = NULL, *val = NULL, *pval = NULL;
207     profile_t profile = KRB5_DB_GET_PROFILE(context);
208     int bval;
209
210     status = ctx_get(context, &dbc);
211     if (status != 0)
212         return status;
213
214     for (t_ptr = db_args; t_ptr && *t_ptr; t_ptr++) {
215         free(opt);
216         free(val);
217         status = get_db_opt(*t_ptr, &opt, &val);
218         if (opt && !strcmp(opt, "dbname")) {
219             dbc->db_name = strdup(val);
220             if (dbc->db_name == NULL) {
221                 status = ENOMEM;
222                 goto cleanup;
223             }
224         }
225         else if (!opt && !strcmp(val, "temporary")) {
226             dbc->tempdb = 1;
227         } else if (!opt && !strcmp(val, "merge_nra")) {
228             ;
229         } else if (opt && !strcmp(opt, "hash")) {
230             dbc->hashfirst = TRUE;
231         } else {
232             status = EINVAL;
233             krb5_set_error_message(context, status,
234                                    _("Unsupported argument \"%s\" for db2"),
235                                    opt ? opt : val);
236             goto cleanup;
237         }
238     }
239
240     if (dbc->db_name == NULL) {
241         /* Check for database_name in the db_module section. */
242         status = profile_get_string(profile, KDB_MODULE_SECTION, conf_section,
243                                     KDB_DB2_DATABASE_NAME, NULL, &pval);
244         if (status == 0 && pval == NULL) {
245             /* For compatibility, check for database_name in the realm. */
246             status = profile_get_string(profile, KDB_REALM_SECTION,
247                                         KRB5_DB_GET_REALM(context),
248                                         KDB_DB2_DATABASE_NAME,
249                                         DEFAULT_KDB_FILE, &pval);
250         }
251         if (status != 0)
252             goto cleanup;
253         dbc->db_name = strdup(pval);
254     }
255
256     status = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
257                                  KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE, &bval);
258     if (status != 0)
259         goto cleanup;
260     dbc->disable_last_success = bval;
261
262     status = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
263                                  KRB5_CONF_DISABLE_LOCKOUT, FALSE, &bval);
264     if (status != 0)
265         goto cleanup;
266     dbc->disable_lockout = bval;
267
268 cleanup:
269     free(opt);
270     free(val);
271     profile_release_string(pval);
272     return status;
273 }
274
275 /*
276  * Set *out to one of the filenames used for the DB described by dbc.  sfx
277  * should be one of SUFFIX_DB, SUFFIX_LOCK, SUFFIX_POLICY, or
278  * SUFFIX_POLICY_LOCK.
279  */
280 static krb5_error_code
281 ctx_dbsuffix(krb5_db2_context *dbc, const char *sfx, char **out)
282 {
283     char *result;
284     const char *tilde;
285
286     *out = NULL;
287     tilde = dbc->tempdb ? "~" : "";
288     if (asprintf(&result, "%s%s%s", dbc->db_name, tilde, sfx) < 0)
289         return ENOMEM;
290     *out = result;
291     return 0;
292 }
293
294 /* Generate all four files corresponding to dbc. */
295 static krb5_error_code
296 ctx_allfiles(krb5_db2_context *dbc, char **dbname_out, char **lockname_out,
297              char **polname_out, char **plockname_out)
298 {
299     char *a = NULL, *b = NULL, *c = NULL, *d = NULL;
300
301     *dbname_out = *lockname_out = *polname_out = *plockname_out = NULL;
302     if (ctx_dbsuffix(dbc, SUFFIX_DB, &a))
303         goto error;
304     if (ctx_dbsuffix(dbc, SUFFIX_LOCK, &b))
305         goto error;
306     if (ctx_dbsuffix(dbc, SUFFIX_POLICY, &c))
307         goto error;
308     if (ctx_dbsuffix(dbc, SUFFIX_POLICY_LOCK, &d))
309         goto error;
310     *dbname_out = a;
311     *lockname_out = b;
312     *polname_out = c;
313     *plockname_out = d;
314     return 0;
315 error:
316     free(a);
317     free(b);
318     free(c);
319     free(d);
320     return ENOMEM;
321 }
322
323 /*
324  * Open the DB2 database described by dbc, using the specified flags and mode,
325  * and return the resulting handle.  Try both hash and btree database types;
326  * dbc->hashfirst determines which is attempted first.  If dbc->hashfirst
327  * indicated the wrong type, update it to indicate the correct type.
328  */
329 static DB *
330 open_db(krb5_db2_context *dbc, int flags, int mode)
331 {
332     char *fname = NULL;
333     DB *db;
334     BTREEINFO bti;
335     HASHINFO hashi;
336     bti.flags = 0;
337     bti.cachesize = 0;
338     bti.psize = 4096;
339     bti.lorder = 0;
340     bti.minkeypage = 0;
341     bti.compare = NULL;
342     bti.prefix = NULL;
343
344     if (ctx_dbsuffix(dbc, SUFFIX_DB, &fname) != 0) {
345         errno = ENOMEM;
346         return NULL;
347     }
348
349     hashi.bsize = 4096;
350     hashi.cachesize = 0;
351     hashi.ffactor = 40;
352     hashi.hash = NULL;
353     hashi.lorder = 0;
354     hashi.nelem = 1;
355
356     /* Try our best guess at the database type. */
357     db = dbopen(fname, flags, mode,
358                 dbc->hashfirst ? DB_HASH : DB_BTREE,
359                 dbc->hashfirst ? (void *) &hashi : (void *) &bti);
360     if (db != NULL)
361         goto done;
362
363     /* If that was wrong, retry with the other type. */
364     switch (errno) {
365 #ifdef EFTYPE
366     case EFTYPE:
367 #endif
368     case EINVAL:
369         db = dbopen(fname, flags, mode,
370                     dbc->hashfirst ? DB_BTREE : DB_HASH,
371                     dbc->hashfirst ? (void *) &bti : (void *) &hashi);
372         /* If that worked, update our guess for next time. */
373         if (db != NULL)
374             dbc->hashfirst = !dbc->hashfirst;
375         break;
376     }
377
378 done:
379     free(fname);
380     return db;
381 }
382
383 static krb5_error_code
384 ctx_unlock(krb5_context context, krb5_db2_context *dbc)
385 {
386     krb5_error_code retval;
387     DB *db;
388
389     retval = osa_adb_release_lock(dbc->policy_db);
390     if (retval)
391         return retval;
392
393     if (!dbc->db_locks_held) /* lock already unlocked */
394         return KRB5_KDB_NOTLOCKED;
395
396     db = dbc->db;
397     if (--(dbc->db_locks_held) == 0) {
398         db->close(db);
399         dbc->db = NULL;
400         dbc->db_lock_mode = 0;
401
402         retval = krb5_lock_file(context, dbc->db_lf_file,
403                                 KRB5_LOCKMODE_UNLOCK);
404     }
405     return retval;
406 }
407
408 #define MAX_LOCK_TRIES 5
409
410 static krb5_error_code
411 ctx_lock(krb5_context context, krb5_db2_context *dbc, int lockmode)
412 {
413     krb5_error_code retval;
414     int kmode, tries;
415
416     if (lockmode == KRB5_DB_LOCKMODE_PERMANENT ||
417         lockmode == KRB5_DB_LOCKMODE_EXCLUSIVE)
418         kmode = KRB5_LOCKMODE_EXCLUSIVE;
419     else if (lockmode == KRB5_DB_LOCKMODE_SHARED)
420         kmode = KRB5_LOCKMODE_SHARED;
421     else
422         return EINVAL;
423
424     if (dbc->db_locks_held == 0 || dbc->db_lock_mode < kmode) {
425         /* Acquire or upgrade the lock. */
426         for (tries = 0; tries < MAX_LOCK_TRIES; tries++) {
427             retval = krb5_lock_file(context, dbc->db_lf_file,
428                                     kmode | KRB5_LOCKMODE_DONTBLOCK);
429             if (retval == 0)
430                 break;
431             if (retval == EBADF && kmode == KRB5_LOCKMODE_EXCLUSIVE)
432                 /* Tried to lock something we don't have write access to. */
433                 return KRB5_KDB_CANTLOCK_DB;
434             sleep(1);
435         }
436         if (retval == EACCES)
437             return KRB5_KDB_CANTLOCK_DB;
438         else if (retval == EAGAIN || retval == EWOULDBLOCK)
439             return OSA_ADB_CANTLOCK_DB;
440         else if (retval)
441             return retval;
442
443         /* Open the DB (or re-open it for read/write). */
444         if (dbc->db != NULL)
445             dbc->db->close(dbc->db);
446         dbc->db = open_db(dbc,
447                           kmode == KRB5_LOCKMODE_SHARED ? O_RDONLY : O_RDWR,
448                           0600);
449         if (dbc->db == NULL) {
450             retval = errno;
451             dbc->db_locks_held = 0;
452             dbc->db_lock_mode = 0;
453             (void) osa_adb_release_lock(dbc->policy_db);
454             (void) krb5_lock_file(context, dbc->db_lf_file,
455                                   KRB5_LOCKMODE_UNLOCK);
456             return retval;
457         }
458
459         dbc->db_lock_mode = kmode;
460     }
461     dbc->db_locks_held++;
462
463     /* Acquire or upgrade the policy lock. */
464     retval = osa_adb_get_lock(dbc->policy_db, lockmode);
465     if (retval)
466         (void) ctx_unlock(context, dbc);
467     return retval;
468 }
469
470 /* Initialize the lock file and policy database fields of dbc.  The db_name and
471  * tempdb fields must already be set. */
472 static krb5_error_code
473 ctx_init(krb5_db2_context *dbc)
474 {
475     krb5_error_code retval;
476     char *polname = NULL, *plockname = NULL;
477
478     retval = ctx_dbsuffix(dbc, SUFFIX_LOCK, &dbc->db_lf_name);
479     if (retval)
480         return retval;
481
482     /*
483      * should be opened read/write so that write locking can work with
484      * POSIX systems
485      */
486     if ((dbc->db_lf_file = open(dbc->db_lf_name, O_RDWR, 0666)) < 0) {
487         if ((dbc->db_lf_file = open(dbc->db_lf_name, O_RDONLY, 0666)) < 0) {
488             retval = errno;
489             goto cleanup;
490         }
491     }
492     set_cloexec_fd(dbc->db_lf_file);
493     dbc->db_inited++;
494
495     retval = ctx_dbsuffix(dbc, SUFFIX_POLICY, &polname);
496     if (retval)
497         goto cleanup;
498     retval = ctx_dbsuffix(dbc, SUFFIX_POLICY_LOCK, &plockname);
499     if (retval)
500         goto cleanup;
501     retval = osa_adb_init_db(&dbc->policy_db, polname, plockname,
502                              OSA_ADB_POLICY_DB_MAGIC);
503
504 cleanup:
505     free(polname);
506     free(plockname);
507     if (retval)
508         ctx_clear(dbc);
509     return retval;
510 }
511
512 static void
513 ctx_fini(krb5_db2_context *dbc)
514 {
515     if (dbc->db_lf_file != -1)
516         (void) close(dbc->db_lf_file);
517     if (dbc->policy_db)
518         (void) osa_adb_fini_db(dbc->policy_db, OSA_ADB_POLICY_DB_MAGIC);
519     ctx_clear(dbc);
520     free(dbc);
521 }
522
523 krb5_error_code
524 krb5_db2_fini(krb5_context context)
525 {
526     if (context->dal_handle->db_context != NULL) {
527         ctx_fini(context->dal_handle->db_context);
528         context->dal_handle->db_context = NULL;
529     }
530     return 0;
531 }
532
533 /* Return successfully if the db2 name set in context can be opened. */
534 static krb5_error_code
535 check_openable(krb5_context context)
536 {
537     DB     *db;
538     krb5_db2_context *dbc;
539
540     dbc = context->dal_handle->db_context;
541     db = open_db(dbc, O_RDONLY, 0);
542     if (db == NULL)
543         return errno;
544     db->close(db);
545     return 0;
546 }
547
548 /*
549  * Return the last modification time of the database.
550  *
551  * Think about using fstat.
552  */
553
554 krb5_error_code
555 krb5_db2_get_age(krb5_context context, char *db_name, time_t *age)
556 {
557     krb5_db2_context *dbc;
558     struct stat st;
559
560     if (!inited(context))
561         return (KRB5_KDB_DBNOTINITED);
562     dbc = context->dal_handle->db_context;
563
564     if (fstat(dbc->db_lf_file, &st) < 0)
565         *age = -1;
566     else
567         *age = st.st_mtime;
568     return 0;
569 }
570
571 /* Try to update the timestamp on dbc's lockfile. */
572 static void
573 ctx_update_age(krb5_db2_context *dbc)
574 {
575     struct stat st;
576     time_t now;
577     struct utimbuf utbuf;
578
579     now = time((time_t *) NULL);
580     if (fstat(dbc->db_lf_file, &st) != 0)
581         return;
582     if (st.st_mtime >= now) {
583         utbuf.actime = st.st_mtime + 1;
584         utbuf.modtime = st.st_mtime + 1;
585         (void) utime(dbc->db_lf_name, &utbuf);
586     } else
587         (void) utime(dbc->db_lf_name, (struct utimbuf *) NULL);
588 }
589
590 krb5_error_code
591 krb5_db2_lock(krb5_context context, int lockmode)
592 {
593     if (!inited(context))
594         return KRB5_KDB_DBNOTINITED;
595     return ctx_lock(context, context->dal_handle->db_context, lockmode);
596 }
597
598 krb5_error_code
599 krb5_db2_unlock(krb5_context context)
600 {
601     if (!inited(context))
602         return KRB5_KDB_DBNOTINITED;
603     return ctx_unlock(context, context->dal_handle->db_context);
604 }
605
606 /* Zero out and unlink filename. */
607 static krb5_error_code
608 destroy_file(char *filename)
609 {
610     struct stat statb;
611     int dowrite, j, nb, fd, retval;
612     off_t pos;
613     char buf[BUFSIZ], zbuf[BUFSIZ];
614
615     fd = open(filename, O_RDWR, 0);
616     if (fd < 0)
617         return errno;
618     set_cloexec_fd(fd);
619     /* fstat() will probably not fail unless using a remote filesystem
620      * (which is inappropriate for the kerberos database) so this check
621      * is mostly paranoia.  */
622     if (fstat(fd, &statb) == -1)
623         goto error;
624     /*
625      * Stroll through the file, reading in BUFSIZ chunks.  If everything
626      * is zero, then we're done for that block, otherwise, zero the block.
627      * We would like to just blast through everything, but some DB
628      * implementations make holey files and writing data to the holes
629      * causes actual blocks to be allocated which is no good, since
630      * we're just about to unlink it anyways.
631      */
632     memset(zbuf, 0, BUFSIZ);
633     pos = 0;
634     while (pos < statb.st_size) {
635         dowrite = 0;
636         nb = read(fd, buf, BUFSIZ);
637         if (nb < 0)
638             goto error;
639         for (j = 0; j < nb; j++) {
640             if (buf[j] != '\0') {
641                 dowrite = 1;
642                 break;
643             }
644         }
645         /* For signedness */
646         j = nb;
647         if (dowrite) {
648             lseek(fd, pos, SEEK_SET);
649             nb = write(fd, zbuf, j);
650             if (nb < 0)
651                 goto error;
652         }
653         pos += nb;
654     }
655     /* ??? Is fsync really needed?  I don't know of any non-networked
656      * filesystem which will discard queued writes to disk if a file
657      * is deleted after it is closed.  --jfc */
658 #ifndef NOFSYNC
659     fsync(fd);
660 #endif
661     close(fd);
662
663     if (unlink(filename))
664         return errno;
665     return 0;
666
667 error:
668     retval = errno;
669     close(fd);
670     return retval;
671 }
672
673 /* Initialize dbc by locking and creating the DB.  If the DB already exists,
674  * clear it out if dbc->tempdb is set; otherwise return EEXIST. */
675 static krb5_error_code
676 ctx_create_db(krb5_context context, krb5_db2_context *dbc)
677 {
678     krb5_error_code retval = 0;
679     char *dbname = NULL, *polname = NULL, *plockname = NULL;
680
681     retval = ctx_allfiles(dbc, &dbname, &dbc->db_lf_name, &polname,
682                           &plockname);
683     if (retval)
684         return retval;
685
686     dbc->db_lf_file = open(dbc->db_lf_name, O_CREAT | O_RDWR | O_TRUNC,
687                            0600);
688     if (dbc->db_lf_file < 0) {
689         retval = errno;
690         goto cleanup;
691     }
692     retval = krb5_lock_file(context, dbc->db_lf_file,
693                             KRB5_LOCKMODE_EXCLUSIVE | KRB5_LOCKMODE_DONTBLOCK);
694     if (retval != 0)
695         goto cleanup;
696     set_cloexec_fd(dbc->db_lf_file);
697     dbc->db_lock_mode = KRB5_LOCKMODE_EXCLUSIVE;
698     dbc->db_locks_held = 1;
699
700     if (dbc->tempdb) {
701         /* Temporary DBs are locked for their whole lifetime.  Since we have
702          * the lock, any remnant files can be safely destroyed. */
703         (void) destroy_file(dbname);
704         (void) unlink(polname);
705         (void) unlink(plockname);
706     }
707
708     dbc->db = open_db(dbc, O_RDWR | O_CREAT | O_EXCL, 0600);
709     if (dbc->db == NULL) {
710         retval = errno;
711         goto cleanup;
712     }
713
714     /* Create the policy database, initialize a handle to it, and lock it. */
715     retval = osa_adb_create_db(polname, plockname, OSA_ADB_POLICY_DB_MAGIC);
716     if (retval)
717         goto cleanup;
718     retval = osa_adb_init_db(&dbc->policy_db, polname, plockname,
719                              OSA_ADB_POLICY_DB_MAGIC);
720     if (retval)
721         goto cleanup;
722     retval = osa_adb_get_lock(dbc->policy_db, KRB5_DB_LOCKMODE_EXCLUSIVE);
723     if (retval)
724         goto cleanup;
725
726     dbc->db_inited = 1;
727
728 cleanup:
729     if (retval) {
730         if (dbc->db != NULL)
731             dbc->db->close(dbc->db);
732         if (dbc->db_locks_held > 0) {
733             (void) krb5_lock_file(context, dbc->db_lf_file,
734                                   KRB5_LOCKMODE_UNLOCK);
735         }
736         if (dbc->db_lf_file >= 0)
737             close(dbc->db_lf_file);
738         ctx_clear(dbc);
739     }
740     free(dbname);
741     free(polname);
742     free(plockname);
743     return retval;
744 }
745
746 krb5_error_code
747 krb5_db2_get_principal(krb5_context context, krb5_const_principal searchfor,
748                        unsigned int flags, krb5_db_entry **entry)
749 {
750     krb5_db2_context *dbc;
751     krb5_error_code retval;
752     DB     *db;
753     DBT     key, contents;
754     krb5_data keydata, contdata;
755     int     trynum, dbret;
756
757     *entry = NULL;
758     if (!inited(context))
759         return KRB5_KDB_DBNOTINITED;
760
761     dbc = context->dal_handle->db_context;
762
763     for (trynum = 0; trynum < KRB5_DB2_MAX_RETRY; trynum++) {
764         if ((retval = ctx_lock(context, dbc, KRB5_LOCKMODE_SHARED))) {
765             if (dbc->db_nb_locks)
766                 return (retval);
767             sleep(1);
768             continue;
769         }
770         break;
771     }
772     if (trynum == KRB5_DB2_MAX_RETRY)
773         return KRB5_KDB_DB_INUSE;
774
775     /* XXX deal with wildcard lookups */
776     retval = krb5_encode_princ_dbkey(context, &keydata, searchfor);
777     if (retval)
778         goto cleanup;
779     key.data = keydata.data;
780     key.size = keydata.length;
781
782     db = dbc->db;
783     dbret = (*db->get)(db, &key, &contents, 0);
784     retval = errno;
785     krb5_free_data_contents(context, &keydata);
786     switch (dbret) {
787     case 1:
788         retval = KRB5_KDB_NOENTRY;
789         /* Fall through. */
790     case -1:
791     default:
792         goto cleanup;
793     case 0:
794         contdata.data = contents.data;
795         contdata.length = contents.size;
796         retval = krb5_decode_princ_entry(context, &contdata, entry);
797         break;
798     }
799
800 cleanup:
801     (void) krb5_db2_unlock(context); /* unlock read lock */
802     return retval;
803 }
804
805 /* Free an entry returned by krb5_db2_get_principal. */
806 void
807 krb5_db2_free_principal(krb5_context context, krb5_db_entry *entry)
808 {
809     krb5_dbe_free(context, entry);
810 }
811
812 krb5_error_code
813 krb5_db2_put_principal(krb5_context context, krb5_db_entry *entry,
814                        char **db_args)
815 {
816     int     dbret;
817     DB     *db;
818     DBT     key, contents;
819     krb5_data contdata, keydata;
820     krb5_error_code retval;
821     krb5_db2_context *dbc;
822
823     krb5_clear_error_message (context);
824     if (db_args) {
825         /* DB2 does not support db_args DB arguments for principal */
826         krb5_set_error_message(context, EINVAL,
827                                _("Unsupported argument \"%s\" for db2"),
828                                db_args[0]);
829         return EINVAL;
830     }
831
832     if (!inited(context))
833         return KRB5_KDB_DBNOTINITED;
834
835     dbc = context->dal_handle->db_context;
836     if ((retval = ctx_lock(context, dbc, KRB5_LOCKMODE_EXCLUSIVE)))
837         return retval;
838
839     db = dbc->db;
840
841     retval = krb5_encode_princ_entry(context, &contdata, entry);
842     if (retval)
843         goto cleanup;
844     contents.data = contdata.data;
845     contents.size = contdata.length;
846     retval = krb5_encode_princ_dbkey(context, &keydata, entry->princ);
847     if (retval) {
848         krb5_free_data_contents(context, &contdata);
849         goto cleanup;
850     }
851
852     key.data = keydata.data;
853     key.size = keydata.length;
854     dbret = (*db->put)(db, &key, &contents, 0);
855     retval = dbret ? errno : 0;
856     krb5_free_data_contents(context, &keydata);
857     krb5_free_data_contents(context, &contdata);
858
859 cleanup:
860     ctx_update_age(dbc);
861     (void) krb5_db2_unlock(context); /* unlock database */
862     return (retval);
863 }
864
865 krb5_error_code
866 krb5_db2_delete_principal(krb5_context context, krb5_const_principal searchfor)
867 {
868     krb5_error_code retval;
869     krb5_db_entry *entry;
870     krb5_db2_context *dbc;
871     DB     *db;
872     DBT     key, contents;
873     krb5_data keydata, contdata;
874     int     i, dbret;
875
876     if (!inited(context))
877         return KRB5_KDB_DBNOTINITED;
878
879     dbc = context->dal_handle->db_context;
880     if ((retval = ctx_lock(context, dbc, KRB5_LOCKMODE_EXCLUSIVE)))
881         return (retval);
882
883     if ((retval = krb5_encode_princ_dbkey(context, &keydata, searchfor)))
884         goto cleanup;
885     key.data = keydata.data;
886     key.size = keydata.length;
887
888     db = dbc->db;
889     dbret = (*db->get) (db, &key, &contents, 0);
890     retval = errno;
891     switch (dbret) {
892     case 1:
893         retval = KRB5_KDB_NOENTRY;
894         /* Fall through. */
895     case -1:
896     default:
897         goto cleankey;
898     case 0:
899         ;
900     }
901     contdata.data = contents.data;
902     contdata.length = contents.size;
903     retval = krb5_decode_princ_entry(context, &contdata, &entry);
904     if (retval)
905         goto cleankey;
906
907     /* Clear encrypted key contents */
908     for (i = 0; i < entry->n_key_data; i++) {
909         if (entry->key_data[i].key_data_length[0]) {
910             memset(entry->key_data[i].key_data_contents[0], 0,
911                    (unsigned) entry->key_data[i].key_data_length[0]);
912         }
913     }
914
915     retval = krb5_encode_princ_entry(context, &contdata, entry);
916     krb5_dbe_free(context, entry);
917     if (retval)
918         goto cleankey;
919
920     contents.data = contdata.data;
921     contents.size = contdata.length;
922     dbret = (*db->put) (db, &key, &contents, 0);
923     retval = dbret ? errno : 0;
924     krb5_free_data_contents(context, &contdata);
925     if (retval)
926         goto cleankey;
927     dbret = (*db->del) (db, &key, 0);
928     retval = dbret ? errno : 0;
929 cleankey:
930     krb5_free_data_contents(context, &keydata);
931
932 cleanup:
933     ctx_update_age(dbc);
934     (void) krb5_db2_unlock(context); /* unlock write lock */
935     return retval;
936 }
937
938 static krb5_error_code
939 ctx_iterate(krb5_context context, krb5_db2_context *dbc,
940             krb5_error_code (*func)(krb5_pointer, krb5_db_entry *),
941             krb5_pointer func_arg)
942 {
943     DBT key, contents;
944     krb5_data contdata;
945     krb5_db_entry *entry;
946     krb5_error_code retval, retval2;
947     int dbret;
948
949     retval = ctx_lock(context, dbc, KRB5_LOCKMODE_SHARED);
950     if (retval)
951         return retval;
952
953     dbret = dbc->db->seq(dbc->db, &key, &contents, R_FIRST);
954     while (dbret == 0) {
955         contdata.data = contents.data;
956         contdata.length = contents.size;
957         retval = krb5_decode_princ_entry(context, &contdata, &entry);
958         if (retval)
959             break;
960         retval = k5_mutex_unlock(krb5_db2_mutex);
961         if (retval)
962             break;
963         retval = (*func)(func_arg, entry);
964         krb5_dbe_free(context, entry);
965         retval2 = k5_mutex_lock(krb5_db2_mutex);
966         /* Note: If re-locking fails, the wrapper in db2_exp.c will
967            still try to unlock it again.  That would be a bug.  Fix
968            when integrating the locking better.  */
969         if (retval)
970             break;
971         if (retval2) {
972             retval = retval2;
973             break;
974         }
975         dbret = dbc->db->seq(dbc->db, &key, &contents, R_NEXT);
976     }
977     switch (dbret) {
978     case 1:
979     case 0:
980         break;
981     case -1:
982     default:
983         retval = errno;
984     }
985     (void) ctx_unlock(context, dbc);
986     return retval;
987 }
988
989 krb5_error_code
990 krb5_db2_iterate(krb5_context context, char *match_expr,
991                  krb5_error_code(*func) (krb5_pointer, krb5_db_entry *),
992                  krb5_pointer func_arg)
993 {
994     if (!inited(context))
995         return KRB5_KDB_DBNOTINITED;
996     return ctx_iterate(context, context->dal_handle->db_context, func,
997                        func_arg);
998 }
999
1000 krb5_boolean
1001 krb5_db2_set_lockmode(krb5_context context, krb5_boolean mode)
1002 {
1003     krb5_boolean old;
1004     krb5_db2_context *dbc;
1005
1006     dbc = context->dal_handle->db_context;
1007     old = mode;
1008     if (dbc) {
1009         old = dbc->db_nb_locks;
1010         dbc->db_nb_locks = mode;
1011     }
1012     return old;
1013 }
1014
1015 /*
1016  *     DAL API functions
1017  */
1018 krb5_error_code
1019 krb5_db2_lib_init()
1020 {
1021     return 0;
1022 }
1023
1024 krb5_error_code
1025 krb5_db2_lib_cleanup()
1026 {
1027     /* right now, no cleanup required */
1028     return 0;
1029 }
1030
1031 krb5_error_code
1032 krb5_db2_open(krb5_context context, char *conf_section, char **db_args,
1033               int mode)
1034 {
1035     krb5_error_code status = 0;
1036
1037     krb5_clear_error_message(context);
1038     if (inited(context))
1039         return 0;
1040
1041     status = configure_context(context, conf_section, db_args);
1042     if (status != 0)
1043         return status;
1044
1045     status = check_openable(context);
1046     if (status != 0)
1047         return status;
1048
1049     return ctx_init(context->dal_handle->db_context);
1050 }
1051
1052 krb5_error_code
1053 krb5_db2_create(krb5_context context, char *conf_section, char **db_args)
1054 {
1055     krb5_error_code status = 0;
1056     krb5_db2_context *dbc;
1057
1058     krb5_clear_error_message(context);
1059     if (inited(context))
1060         return 0;
1061
1062     status = configure_context(context, conf_section, db_args);
1063     if (status != 0)
1064         return status;
1065
1066     dbc = context->dal_handle->db_context;
1067     status = ctx_create_db(context, dbc);
1068     if (status != 0)
1069         return status;
1070
1071     if (!dbc->tempdb)
1072         krb5_db2_unlock(context);
1073
1074     return 0;
1075 }
1076
1077 krb5_error_code
1078 krb5_db2_destroy(krb5_context context, char *conf_section, char **db_args)
1079 {
1080     krb5_error_code status;
1081     krb5_db2_context *dbc;
1082     char *dbname = NULL, *lockname = NULL, *polname = NULL, *plockname = NULL;
1083
1084     if (inited(context)) {
1085         status = krb5_db2_fini(context);
1086         if (status != 0)
1087             return status;
1088     }
1089
1090     krb5_clear_error_message(context);
1091     status = configure_context(context, conf_section, db_args);
1092     if (status != 0)
1093         return status;
1094
1095     status = check_openable(context);
1096     if (status != 0)
1097         return status;
1098
1099     dbc = context->dal_handle->db_context;
1100
1101     status = ctx_allfiles(dbc, &dbname, &lockname, &polname, &plockname);
1102     if (status)
1103         goto cleanup;
1104     status = destroy_file(dbname);
1105     if (status)
1106         goto cleanup;
1107     status = unlink(lockname);
1108     if (status)
1109         goto cleanup;
1110     status = osa_adb_destroy_db(polname, plockname, OSA_ADB_POLICY_DB_MAGIC);
1111     if (status)
1112         return status;
1113
1114     status = krb5_db2_fini(context);
1115
1116 cleanup:
1117     free(dbname);
1118     free(lockname);
1119     free(polname);
1120     free(plockname);
1121     return status;
1122 }
1123
1124 void   *
1125 krb5_db2_alloc(krb5_context context, void *ptr, size_t size)
1126 {
1127     return realloc(ptr, size);
1128 }
1129
1130 void
1131 krb5_db2_free(krb5_context context, void *ptr)
1132 {
1133     free(ptr);
1134 }
1135
1136 /* policy functions */
1137 krb5_error_code
1138 krb5_db2_create_policy(krb5_context context, osa_policy_ent_t policy)
1139 {
1140     krb5_db2_context *dbc = context->dal_handle->db_context;
1141
1142     return osa_adb_create_policy(dbc->policy_db, policy);
1143 }
1144
1145 krb5_error_code
1146 krb5_db2_get_policy(krb5_context context,
1147                     char *name, osa_policy_ent_t *policy)
1148 {
1149     krb5_db2_context *dbc = context->dal_handle->db_context;
1150
1151     return osa_adb_get_policy(dbc->policy_db, name, policy);
1152 }
1153
1154 krb5_error_code
1155 krb5_db2_put_policy(krb5_context context, osa_policy_ent_t policy)
1156 {
1157     krb5_db2_context *dbc = context->dal_handle->db_context;
1158
1159     return osa_adb_put_policy(dbc->policy_db, policy);
1160 }
1161
1162 krb5_error_code
1163 krb5_db2_iter_policy(krb5_context context,
1164                      char *match_entry,
1165                      osa_adb_iter_policy_func func, void *data)
1166 {
1167     krb5_db2_context *dbc = context->dal_handle->db_context;
1168
1169     return osa_adb_iter_policy(dbc->policy_db, func, data);
1170 }
1171
1172 krb5_error_code
1173 krb5_db2_delete_policy(krb5_context context, char *policy)
1174 {
1175     krb5_db2_context *dbc = context->dal_handle->db_context;
1176
1177     return osa_adb_destroy_policy(dbc->policy_db, policy);
1178 }
1179
1180 void
1181 krb5_db2_free_policy(krb5_context context, osa_policy_ent_t entry)
1182 {
1183     osa_free_policy_ent(entry);
1184 }
1185
1186
1187 /*
1188  * Merge non-replicated attributes from src into dst, setting
1189  * changed to non-zero if dst was changed.
1190  *
1191  * Non-replicated attributes are: last_success, last_failed,
1192  * fail_auth_count, and any negative TL data values.
1193  */
1194 static krb5_error_code
1195 krb5_db2_merge_principal(krb5_context context,
1196                          krb5_db_entry *src,
1197                          krb5_db_entry *dst,
1198                          int *changed)
1199 {
1200     *changed = 0;
1201
1202     if (dst->last_success != src->last_success) {
1203         dst->last_success = src->last_success;
1204         (*changed)++;
1205     }
1206
1207     if (dst->last_failed != src->last_failed) {
1208         dst->last_failed = src->last_failed;
1209         (*changed)++;
1210     }
1211
1212     if (dst->fail_auth_count != src->fail_auth_count) {
1213         dst->fail_auth_count = src->fail_auth_count;
1214         (*changed)++;
1215     }
1216
1217     return 0;
1218 }
1219
1220 struct nra_context {
1221     krb5_context kcontext;
1222     krb5_db2_context *db_context;
1223 };
1224
1225 /*
1226  * Iteration callback merges non-replicated attributes from
1227  * old database.
1228  */
1229 static krb5_error_code
1230 krb5_db2_merge_nra_iterator(krb5_pointer ptr, krb5_db_entry *entry)
1231 {
1232     struct nra_context *nra = (struct nra_context *)ptr;
1233     kdb5_dal_handle *dal_handle = nra->kcontext->dal_handle;
1234     krb5_error_code retval;
1235     int changed;
1236     krb5_db_entry *s_entry;
1237     krb5_db2_context *dst_db;
1238
1239     memset(&s_entry, 0, sizeof(s_entry));
1240
1241     dst_db = dal_handle->db_context;
1242     dal_handle->db_context = nra->db_context;
1243
1244     /* look up the new principal in the old DB */
1245     retval = krb5_db2_get_principal(nra->kcontext, entry->princ, 0, &s_entry);
1246     if (retval != 0) {
1247         /* principal may be newly created, so ignore */
1248         dal_handle->db_context = dst_db;
1249         return 0;
1250     }
1251
1252     /* merge non-replicated attributes from the old entry in */
1253     krb5_db2_merge_principal(nra->kcontext, s_entry, entry, &changed);
1254
1255     dal_handle->db_context = dst_db;
1256
1257     /* if necessary, commit the modified new entry to the new DB */
1258     if (changed) {
1259         retval = krb5_db2_put_principal(nra->kcontext, entry, NULL);
1260     } else {
1261         retval = 0;
1262     }
1263
1264     return retval;
1265 }
1266
1267 /*
1268  * Merge non-replicated attributes (that is, lockout-related
1269  * attributes and negative TL data types) from the real database
1270  * into the temporary one.
1271  */
1272 static krb5_error_code
1273 ctx_merge_nra(krb5_context context, krb5_db2_context *dbc_temp,
1274               krb5_db2_context *dbc_real)
1275 {
1276     struct nra_context nra;
1277
1278     nra.kcontext = context;
1279     nra.db_context = dbc_real;
1280     return ctx_iterate(context, dbc_temp, krb5_db2_merge_nra_iterator, &nra);
1281 }
1282
1283 /*
1284  * In the filesystem, promote the temporary database described by dbc_temp to
1285  * the real database described by dbc_real.  Both must be exclusively locked.
1286  */
1287 static krb5_error_code
1288 ctx_promote(krb5_context context, krb5_db2_context *dbc_temp,
1289             krb5_db2_context *dbc_real)
1290 {
1291     krb5_error_code retval;
1292     char *tdb = NULL, *tlock = NULL, *tpol = NULL, *tplock = NULL;
1293     char *rdb = NULL, *rlock = NULL, *rpol = NULL, *rplock = NULL;
1294
1295     /* Generate all filenames of interest (including a few we don't need). */
1296     retval = ctx_allfiles(dbc_temp, &tdb, &tlock, &tpol, &tplock);
1297     if (retval)
1298         return retval;
1299     retval = ctx_allfiles(dbc_real, &rdb, &rlock, &rpol, &rplock);
1300     if (retval)
1301         goto cleanup;
1302
1303     /* Rename the principal and policy databases into place. */
1304     if (rename(tdb, rdb)) {
1305         retval = errno;
1306         goto cleanup;
1307     }
1308     if (rename(tpol, rpol)) {
1309         retval = errno;
1310         goto cleanup;
1311     }
1312
1313     ctx_update_age(dbc_real);
1314
1315     /* Release and remove the temporary DB lockfiles. */
1316     (void) unlink(tlock);
1317     (void) unlink(tplock);
1318
1319 cleanup:
1320     free(tdb);
1321     free(tlock);
1322     free(tpol);
1323     free(tplock);
1324     free(rdb);
1325     free(rlock);
1326     free(rpol);
1327     free(rplock);
1328     return retval;
1329 }
1330
1331 krb5_error_code
1332 krb5_db2_promote_db(krb5_context context, char *conf_section, char **db_args)
1333 {
1334     krb5_error_code retval;
1335     krb5_boolean merge_nra = FALSE, real_locked = FALSE;
1336     krb5_db2_context *dbc_temp, *dbc_real = NULL;
1337     char **db_argp;
1338
1339     /* context must be initialized with an exclusively locked temp DB. */
1340     if (!inited(context))
1341         return KRB5_KDB_DBNOTINITED;
1342     dbc_temp = context->dal_handle->db_context;
1343     if (dbc_temp->db_lock_mode != KRB5_LOCKMODE_EXCLUSIVE)
1344         return KRB5_KDB_NOTLOCKED;
1345     if (!dbc_temp->tempdb)
1346         return EINVAL;
1347
1348     /* Check db_args for whether we should merge non-replicated attributes. */
1349     for (db_argp = db_args; *db_argp; db_argp++) {
1350         if (!strcmp(*db_argp, "merge_nra")) {
1351             merge_nra = TRUE;
1352             break;
1353         }
1354     }
1355
1356     /* Make a db2 context for the real DB. */
1357     dbc_real = k5alloc(sizeof(*dbc_real), &retval);
1358     if (dbc_real == NULL)
1359         return retval;
1360     ctx_clear(dbc_real);
1361
1362     /* Try creating the real DB. */
1363     dbc_real->db_name = strdup(dbc_temp->db_name);
1364     if (dbc_real->db_name == NULL)
1365         goto cleanup;
1366     dbc_real->tempdb = FALSE;
1367     retval = ctx_create_db(context, dbc_real);
1368     if (retval == EEXIST) {
1369         /* The real database already exists, so open and lock it. */
1370         dbc_real->db_name = strdup(dbc_temp->db_name);
1371         if (dbc_real->db_name == NULL)
1372             goto cleanup;
1373         dbc_real->tempdb = FALSE;
1374         retval = ctx_init(dbc_real);
1375         if (retval)
1376             goto cleanup;
1377         retval = ctx_lock(context, dbc_real, KRB5_DB_LOCKMODE_EXCLUSIVE);
1378         if (retval)
1379             goto cleanup;
1380     } else if (retval)
1381         goto cleanup;
1382     real_locked = TRUE;
1383
1384     if (merge_nra) {
1385         retval = ctx_merge_nra(context, dbc_temp, dbc_real);
1386         if (retval)
1387             goto cleanup;
1388     }
1389
1390     /* Perform filesystem manipulations for the promotion. */
1391     retval = ctx_promote(context, dbc_temp, dbc_real);
1392     if (retval)
1393         goto cleanup;
1394
1395     /* Unlock and finalize context since the temp DB is gone. */
1396     (void) krb5_db2_unlock(context);
1397     krb5_db2_fini(context);
1398
1399 cleanup:
1400     if (real_locked)
1401         (void) ctx_unlock(context, dbc_real);
1402     if (dbc_real)
1403         ctx_fini(dbc_real);
1404     return retval;
1405 }
1406
1407 krb5_error_code
1408 krb5_db2_check_policy_as(krb5_context kcontext, krb5_kdc_req *request,
1409                          krb5_db_entry *client, krb5_db_entry *server,
1410                          krb5_timestamp kdc_time, const char **status,
1411                          krb5_pa_data ***e_data)
1412 {
1413     krb5_error_code retval;
1414
1415     retval = krb5_db2_lockout_check_policy(kcontext, client, kdc_time);
1416     if (retval == KRB5KDC_ERR_CLIENT_REVOKED)
1417         *status = "LOCKED_OUT";
1418     return retval;
1419 }
1420
1421 void
1422 krb5_db2_audit_as_req(krb5_context kcontext, krb5_kdc_req *request,
1423                       krb5_db_entry *client, krb5_db_entry *server,
1424                       krb5_timestamp authtime, krb5_error_code error_code)
1425 {
1426     (void) krb5_db2_lockout_audit(kcontext, client, authtime, error_code);
1427 }