Path: blob/main/crypto/krb5/src/plugins/kdb/db2/kdb_db2.c
34923 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* plugins/kdb/db2/kdb_db2.c */2/*3* Copyright 1997,2006,2007-2009 by the Massachusetts Institute of Technology.4* All Rights Reserved.5*6* Export of this software from the United States of America may7* require a specific license from the United States Government.8* It is the responsibility of any person or organization contemplating9* export to obtain such a license before exporting.10*11* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and12* distribute this software and its documentation for any purpose and13* without fee is hereby granted, provided that the above copyright14* notice appear in all copies and that both that copyright notice and15* this permission notice appear in supporting documentation, and that16* the name of M.I.T. not be used in advertising or publicity pertaining17* to distribution of the software without specific, written prior18* permission. Furthermore if you modify this software you must label19* your software as modified software and not distribute it in such a20* fashion that it might be confused with the original M.I.T. software.21* M.I.T. makes no representations about the suitability of22* this software for any purpose. It is provided "as is" without express23* or implied warranty.24*25*/2627/*28* Copyright (C) 1998 by the FundsXpress, INC.29*30* All rights reserved.31*32* Export of this software from the United States of America may require33* a specific license from the United States Government. It is the34* responsibility of any person or organization contemplating export to35* obtain such a license before exporting.36*37* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and38* distribute this software and its documentation for any purpose and39* without fee is hereby granted, provided that the above copyright40* notice appear in all copies and that both that copyright notice and41* this permission notice appear in supporting documentation, and that42* the name of FundsXpress. not be used in advertising or publicity pertaining43* to distribution of the software without specific, written prior44* permission. FundsXpress makes no representations about the suitability of45* this software for any purpose. It is provided "as is" without express46* or implied warranty.47*48* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR49* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED50* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.51*/5253#include "k5-int.h"5455#if HAVE_UNISTD_H56#include <unistd.h>57#endif5859#include <db.h>60#include <stdio.h>61#include <errno.h>62#include <utime.h>63#include "kdb5.h"64#include "kdb_db2.h"65#include "kdb_xdr.h"66#include "policy_db.h"6768#define KDB_DB2_DATABASE_NAME "database_name"6970#define SUFFIX_DB ""71#define SUFFIX_LOCK ".ok"72#define SUFFIX_POLICY ".kadm5"73#define SUFFIX_POLICY_LOCK ".kadm5.lock"7475/*76* Locking:77*78* There are two distinct locking protocols used. One is designed to79* lock against processes (the admin_server, for one) which make80* incremental changes to the database; the other is designed to lock81* against utilities (kdb5_edit, kpropd, kdb5_convert) which replace the82* entire database in one fell swoop.83*84* The first locking protocol is implemented using flock() in the85* krb_dbl_lock() and krb_dbl_unlock routines.86*87* The second locking protocol is necessary because DBM "files" are88* actually implemented as two separate files, and it is impossible to89* atomically rename two files simultaneously. It assumes that the90* database is replaced only very infrequently in comparison to the time91* needed to do a database read operation.92*93* A third file is used as a "version" semaphore; the modification94* time of this file is the "version number" of the database.95* At the start of a read operation, the reader checks the version96* number; at the end of the read operation, it checks again. If the97* version number changed, or if the semaphore was nonexistent at98* either time, the reader sleeps for a second to let things99* stabilize, and then tries again; if it does not succeed after100* KRB5_DBM_MAX_RETRY attempts, it gives up.101*102* On update, the semaphore file is deleted (if it exists) before any103* update takes place; at the end of the update, it is replaced, with104* a version number strictly greater than the version number which105* existed at the start of the update.106*107* If the system crashes in the middle of an update, the semaphore108* file is not automatically created on reboot; this is a feature, not109* a bug, since the database may be inconsistent. Note that the110* absence of a semaphore file does not prevent another _update_ from111* taking place later. Database replacements take place automatically112* only on replica servers; a crash in the middle of an update will be113* fixed by the next propagation. A crash in the middle of an on the114* master would be somewhat more serious, but this would likely be115* noticed by an administrator, who could fix the problem and retry116* the operation.117*/118119/* Evaluate to true if the krb5_context c contains an initialized db2120* context. */121#define inited(c) ((c)->dal_handle->db_context && \122((krb5_db2_context *)(c)->dal_handle->db_context)-> \123db_inited)124125static krb5_error_code126get_db_opt(char *input, char **opt, char **val)127{128char *pos = strchr(input, '=');129if (pos == NULL) {130*opt = NULL;131*val = strdup(input);132if (*val == NULL) {133return ENOMEM;134}135} else {136*opt = malloc((pos - input) + 1);137*val = strdup(pos + 1);138if (!*opt || !*val) {139free(*opt);140*opt = NULL;141free(*val);142*val = NULL;143return ENOMEM;144}145memcpy(*opt, input, pos - input);146(*opt)[pos - input] = '\0';147}148return (0);149150}151152/* Restore dbctx to the uninitialized state. */153static void154ctx_clear(krb5_db2_context *dbc)155{156/*157* Free any dynamically allocated memory. File descriptors and locks158* are the caller's problem.159*/160free(dbc->db_lf_name);161free(dbc->db_name);162/*163* Clear the structure and reset the defaults.164*/165memset(dbc, 0, sizeof(krb5_db2_context));166dbc->db = NULL;167dbc->db_lf_name = NULL;168dbc->db_lf_file = -1;169dbc->db_name = NULL;170dbc->db_nb_locks = FALSE;171dbc->tempdb = FALSE;172}173174/* Set *dbc_out to the db2 database context for context. If one does not175* exist, create one in the uninitialized state. */176static krb5_error_code177ctx_get(krb5_context context, krb5_db2_context **dbc_out)178{179krb5_db2_context *dbc;180kdb5_dal_handle *dal_handle;181182dal_handle = context->dal_handle;183184if (dal_handle->db_context == NULL) {185dbc = (krb5_db2_context *) malloc(sizeof(krb5_db2_context));186if (dbc == NULL)187return ENOMEM;188else {189memset(dbc, 0, sizeof(krb5_db2_context));190ctx_clear(dbc);191dal_handle->db_context = dbc;192}193}194*dbc_out = dal_handle->db_context;195return 0;196}197198/* Using db_args and the profile, initialize the configurable parameters of the199* DB context inside context. */200static krb5_error_code201configure_context(krb5_context context, char *conf_section, char **db_args)202{203krb5_error_code status;204krb5_db2_context *dbc;205char **t_ptr, *opt = NULL, *val = NULL, *pval = NULL;206profile_t profile = KRB5_DB_GET_PROFILE(context);207int bval;208209status = ctx_get(context, &dbc);210if (status != 0)211return status;212213/* Allow unlockiter to be overridden by command line db_args. */214status = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,215KRB5_CONF_UNLOCKITER, FALSE, &bval);216if (status != 0)217goto cleanup;218dbc->unlockiter = bval;219220for (t_ptr = db_args; t_ptr && *t_ptr; t_ptr++) {221free(opt);222free(val);223status = get_db_opt(*t_ptr, &opt, &val);224if (opt && !strcmp(opt, "dbname")) {225dbc->db_name = strdup(val);226if (dbc->db_name == NULL) {227status = ENOMEM;228goto cleanup;229}230}231else if (!opt && !strcmp(val, "temporary")) {232dbc->tempdb = 1;233} else if (!opt && !strcmp(val, "merge_nra")) {234;235} else if (opt && !strcmp(opt, "hash")) {236dbc->hashfirst = TRUE;237} else if (!opt && !strcmp(val, "unlockiter")) {238dbc->unlockiter = TRUE;239} else if (!opt && !strcmp(val, "lockiter")) {240dbc->unlockiter = FALSE;241} else {242status = EINVAL;243k5_setmsg(context, status,244_("Unsupported argument \"%s\" for db2"),245opt ? opt : val);246goto cleanup;247}248}249250if (dbc->db_name == NULL) {251/* Check for database_name in the db_module section. */252status = profile_get_string(profile, KDB_MODULE_SECTION, conf_section,253KDB_DB2_DATABASE_NAME, NULL, &pval);254if (status == 0 && pval == NULL) {255/* For compatibility, check for database_name in the realm. */256status = profile_get_string(profile, KDB_REALM_SECTION,257KRB5_DB_GET_REALM(context),258KDB_DB2_DATABASE_NAME,259DEFAULT_KDB_FILE, &pval);260}261if (status != 0)262goto cleanup;263dbc->db_name = strdup(pval);264}265266status = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,267KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE, &bval);268if (status != 0)269goto cleanup;270dbc->disable_last_success = bval;271272status = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,273KRB5_CONF_DISABLE_LOCKOUT, FALSE, &bval);274if (status != 0)275goto cleanup;276dbc->disable_lockout = bval;277278cleanup:279free(opt);280free(val);281profile_release_string(pval);282return status;283}284285/*286* Set *out to one of the filenames used for the DB described by dbc. sfx287* should be one of SUFFIX_DB, SUFFIX_LOCK, SUFFIX_POLICY, or288* SUFFIX_POLICY_LOCK.289*/290static krb5_error_code291ctx_dbsuffix(krb5_db2_context *dbc, const char *sfx, char **out)292{293char *result;294const char *tilde;295296*out = NULL;297tilde = dbc->tempdb ? "~" : "";298if (asprintf(&result, "%s%s%s", dbc->db_name, tilde, sfx) < 0)299return ENOMEM;300*out = result;301return 0;302}303304/* Generate all four files corresponding to dbc. */305static krb5_error_code306ctx_allfiles(krb5_db2_context *dbc, char **dbname_out, char **lockname_out,307char **polname_out, char **plockname_out)308{309char *a = NULL, *b = NULL, *c = NULL, *d = NULL;310311*dbname_out = *lockname_out = *polname_out = *plockname_out = NULL;312if (ctx_dbsuffix(dbc, SUFFIX_DB, &a))313goto error;314if (ctx_dbsuffix(dbc, SUFFIX_LOCK, &b))315goto error;316if (ctx_dbsuffix(dbc, SUFFIX_POLICY, &c))317goto error;318if (ctx_dbsuffix(dbc, SUFFIX_POLICY_LOCK, &d))319goto error;320*dbname_out = a;321*lockname_out = b;322*polname_out = c;323*plockname_out = d;324return 0;325error:326free(a);327free(b);328free(c);329free(d);330return ENOMEM;331}332333/*334* Open the DB2 database described by dbc, using the specified flags and mode,335* and return the resulting handle. Try both hash and btree database types;336* dbc->hashfirst determines which is attempted first. If dbc->hashfirst337* indicated the wrong type, update it to indicate the correct type.338*/339static krb5_error_code340open_db(krb5_context context, krb5_db2_context *dbc, int flags, int mode,341DB **db_out)342{343char *fname = NULL;344DB *db;345BTREEINFO bti;346HASHINFO hashi;347bti.flags = 0;348bti.cachesize = 0;349bti.psize = 4096;350bti.lorder = 0;351bti.minkeypage = 0;352bti.compare = NULL;353bti.prefix = NULL;354355*db_out = NULL;356357if (ctx_dbsuffix(dbc, SUFFIX_DB, &fname) != 0)358return ENOMEM;359360hashi.bsize = 4096;361hashi.cachesize = 0;362hashi.ffactor = 40;363hashi.hash = NULL;364hashi.lorder = 0;365hashi.nelem = 1;366367/* Try our best guess at the database type. */368db = dbopen(fname, flags, mode,369dbc->hashfirst ? DB_HASH : DB_BTREE,370dbc->hashfirst ? (void *) &hashi : (void *) &bti);371372if (db == NULL && IS_EFTYPE(errno)) {373db = dbopen(fname, flags, mode,374dbc->hashfirst ? DB_BTREE : DB_HASH,375dbc->hashfirst ? (void *) &bti : (void *) &hashi);376/* If that worked, update our guess for next time. */377if (db != NULL)378dbc->hashfirst = !dbc->hashfirst;379}380381/* Don't try unlocked iteration with a hash database. */382if (db != NULL && dbc->hashfirst)383dbc->unlockiter = FALSE;384385if (db == NULL) {386k5_prependmsg(context, errno, _("Cannot open DB2 database '%s'"),387fname);388}389390*db_out = db;391free(fname);392return (db == NULL) ? errno : 0;393}394395static krb5_error_code396ctx_unlock(krb5_context context, krb5_db2_context *dbc)397{398krb5_error_code retval, retval2;399DB *db;400401retval = osa_adb_release_lock(dbc->policy_db);402403if (!dbc->db_locks_held) /* lock already unlocked */404return KRB5_KDB_NOTLOCKED;405406db = dbc->db;407if (--(dbc->db_locks_held) == 0) {408db->close(db);409dbc->db = NULL;410dbc->db_lock_mode = 0;411412retval2 = krb5_lock_file(context, dbc->db_lf_file,413KRB5_LOCKMODE_UNLOCK);414if (retval2)415return retval2;416}417418/* We may be unlocking because osa_adb_get_lock() failed. */419if (retval == OSA_ADB_NOTLOCKED)420return 0;421return retval;422}423424static krb5_error_code425ctx_lock(krb5_context context, krb5_db2_context *dbc, int lockmode)426{427krb5_error_code retval;428int kmode;429430if (lockmode == KRB5_DB_LOCKMODE_PERMANENT ||431lockmode == KRB5_DB_LOCKMODE_EXCLUSIVE)432kmode = KRB5_LOCKMODE_EXCLUSIVE;433else if (lockmode == KRB5_DB_LOCKMODE_SHARED)434kmode = KRB5_LOCKMODE_SHARED;435else436return EINVAL;437438if (dbc->db_locks_held == 0 || dbc->db_lock_mode < kmode) {439/* Acquire or upgrade the lock. */440retval = krb5_lock_file(context, dbc->db_lf_file, kmode);441/* Check if we tried to lock something not open for write. */442if (retval == EBADF && kmode == KRB5_LOCKMODE_EXCLUSIVE)443return KRB5_KDB_CANTLOCK_DB;444else if (retval == EACCES || retval == EAGAIN || retval == EWOULDBLOCK)445return KRB5_KDB_CANTLOCK_DB;446else if (retval)447return retval;448449/* Open the DB (or re-open it for read/write). */450if (dbc->db != NULL)451dbc->db->close(dbc->db);452retval = open_db(context, dbc,453kmode == KRB5_LOCKMODE_SHARED ? O_RDONLY : O_RDWR,4540600, &dbc->db);455if (retval) {456dbc->db_locks_held = 0;457dbc->db_lock_mode = 0;458(void) osa_adb_release_lock(dbc->policy_db);459(void) krb5_lock_file(context, dbc->db_lf_file,460KRB5_LOCKMODE_UNLOCK);461return retval;462}463464dbc->db_lock_mode = kmode;465}466dbc->db_locks_held++;467468/* Acquire or upgrade the policy lock. */469retval = osa_adb_get_lock(dbc->policy_db, lockmode);470if (retval) {471(void) ctx_unlock(context, dbc);472if (retval == OSA_ADB_NOEXCL_PERM || retval == OSA_ADB_CANTLOCK_DB ||473retval == OSA_ADB_NOLOCKFILE)474retval = KRB5_KDB_CANTLOCK_DB;475}476return retval;477}478479/* Initialize the lock file and policy database fields of dbc. The db_name and480* tempdb fields must already be set. */481static krb5_error_code482ctx_init(krb5_db2_context *dbc)483{484krb5_error_code retval;485char *polname = NULL, *plockname = NULL;486487retval = ctx_dbsuffix(dbc, SUFFIX_LOCK, &dbc->db_lf_name);488if (retval)489return retval;490491/*492* should be opened read/write so that write locking can work with493* POSIX systems494*/495if ((dbc->db_lf_file = open(dbc->db_lf_name, O_RDWR, 0666)) < 0) {496if ((dbc->db_lf_file = open(dbc->db_lf_name, O_RDONLY, 0666)) < 0) {497retval = errno;498goto cleanup;499}500}501set_cloexec_fd(dbc->db_lf_file);502dbc->db_inited++;503504retval = ctx_dbsuffix(dbc, SUFFIX_POLICY, &polname);505if (retval)506goto cleanup;507retval = ctx_dbsuffix(dbc, SUFFIX_POLICY_LOCK, &plockname);508if (retval)509goto cleanup;510retval = osa_adb_init_db(&dbc->policy_db, polname, plockname,511OSA_ADB_POLICY_DB_MAGIC);512513cleanup:514free(polname);515free(plockname);516if (retval)517ctx_clear(dbc);518return retval;519}520521static void522ctx_fini(krb5_db2_context *dbc)523{524if (dbc->db_lf_file != -1)525(void) close(dbc->db_lf_file);526if (dbc->policy_db)527(void) osa_adb_fini_db(dbc->policy_db, OSA_ADB_POLICY_DB_MAGIC);528ctx_clear(dbc);529free(dbc);530}531532krb5_error_code533krb5_db2_fini(krb5_context context)534{535if (context->dal_handle->db_context != NULL) {536ctx_fini(context->dal_handle->db_context);537context->dal_handle->db_context = NULL;538}539return 0;540}541542/* Return successfully if the db2 name set in context can be opened. */543static krb5_error_code544check_openable(krb5_context context)545{546krb5_error_code retval;547DB *db;548krb5_db2_context *dbc;549550dbc = context->dal_handle->db_context;551retval = open_db(context, dbc, O_RDONLY, 0, &db);552if (retval)553return retval;554db->close(db);555return 0;556}557558/*559* Return the last modification time of the database.560*561* Think about using fstat.562*/563564krb5_error_code565krb5_db2_get_age(krb5_context context, char *db_name, time_t *age)566{567krb5_db2_context *dbc;568struct stat st;569570if (!inited(context))571return (KRB5_KDB_DBNOTINITED);572dbc = context->dal_handle->db_context;573574if (fstat(dbc->db_lf_file, &st) < 0)575*age = -1;576else577*age = st.st_mtime;578return 0;579}580581/* Try to update the timestamp on dbc's lockfile. */582static void583ctx_update_age(krb5_db2_context *dbc)584{585struct stat st;586time_t now;587struct utimbuf utbuf;588589now = time((time_t *) NULL);590if (fstat(dbc->db_lf_file, &st) != 0)591return;592if (st.st_mtime >= now) {593utbuf.actime = st.st_mtime + 1;594utbuf.modtime = st.st_mtime + 1;595(void) utime(dbc->db_lf_name, &utbuf);596} else597(void) utime(dbc->db_lf_name, (struct utimbuf *) NULL);598}599600krb5_error_code601krb5_db2_lock(krb5_context context, int lockmode)602{603if (!inited(context))604return KRB5_KDB_DBNOTINITED;605return ctx_lock(context, context->dal_handle->db_context, lockmode);606}607608krb5_error_code609krb5_db2_unlock(krb5_context context)610{611if (!inited(context))612return KRB5_KDB_DBNOTINITED;613return ctx_unlock(context, context->dal_handle->db_context);614}615616/* Zero out and unlink filename. */617static krb5_error_code618destroy_file(char *filename)619{620struct stat statb;621int dowrite, j, nb, fd, retval;622off_t pos;623char buf[BUFSIZ], zbuf[BUFSIZ];624625fd = open(filename, O_RDWR, 0);626if (fd < 0)627return errno;628set_cloexec_fd(fd);629/* fstat() will probably not fail unless using a remote filesystem630* (which is inappropriate for the kerberos database) so this check631* is mostly paranoia. */632if (fstat(fd, &statb) == -1)633goto error;634/*635* Stroll through the file, reading in BUFSIZ chunks. If everything636* is zero, then we're done for that block, otherwise, zero the block.637* We would like to just blast through everything, but some DB638* implementations make holey files and writing data to the holes639* causes actual blocks to be allocated which is no good, since640* we're just about to unlink it anyways.641*/642memset(zbuf, 0, BUFSIZ);643pos = 0;644while (pos < statb.st_size) {645dowrite = 0;646nb = read(fd, buf, BUFSIZ);647if (nb < 0)648goto error;649for (j = 0; j < nb; j++) {650if (buf[j] != '\0') {651dowrite = 1;652break;653}654}655/* For signedness */656j = nb;657if (dowrite) {658lseek(fd, pos, SEEK_SET);659nb = write(fd, zbuf, j);660if (nb < 0)661goto error;662}663pos += nb;664}665/* ??? Is fsync really needed? I don't know of any non-networked666* filesystem which will discard queued writes to disk if a file667* is deleted after it is closed. --jfc */668#ifndef NOFSYNC669fsync(fd);670#endif671close(fd);672673if (unlink(filename))674return errno;675return 0;676677error:678retval = errno;679close(fd);680return retval;681}682683/* Initialize dbc by locking and creating the DB. If the DB already exists,684* clear it out if dbc->tempdb is set; otherwise return EEXIST. */685static krb5_error_code686ctx_create_db(krb5_context context, krb5_db2_context *dbc)687{688krb5_error_code retval = 0;689char *dbname = NULL, *polname = NULL, *plockname = NULL;690691retval = ctx_allfiles(dbc, &dbname, &dbc->db_lf_name, &polname,692&plockname);693if (retval)694return retval;695696dbc->db_lf_file = open(dbc->db_lf_name, O_CREAT | O_RDWR | O_TRUNC,6970600);698if (dbc->db_lf_file < 0) {699retval = errno;700goto cleanup;701}702retval = krb5_lock_file(context, dbc->db_lf_file, KRB5_LOCKMODE_EXCLUSIVE);703if (retval != 0)704goto cleanup;705set_cloexec_fd(dbc->db_lf_file);706dbc->db_lock_mode = KRB5_LOCKMODE_EXCLUSIVE;707dbc->db_locks_held = 1;708709if (dbc->tempdb) {710/* Temporary DBs are locked for their whole lifetime. Since we have711* the lock, any remnant files can be safely destroyed. */712(void) destroy_file(dbname);713(void) unlink(polname);714(void) unlink(plockname);715}716717retval = open_db(context, dbc, O_RDWR | O_CREAT | O_EXCL, 0600, &dbc->db);718if (retval)719goto cleanup;720721/* Create the policy database, initialize a handle to it, and lock it. */722retval = osa_adb_create_db(polname, plockname, OSA_ADB_POLICY_DB_MAGIC);723if (retval)724goto cleanup;725retval = osa_adb_init_db(&dbc->policy_db, polname, plockname,726OSA_ADB_POLICY_DB_MAGIC);727if (retval)728goto cleanup;729retval = osa_adb_get_lock(dbc->policy_db, KRB5_DB_LOCKMODE_EXCLUSIVE);730if (retval)731goto cleanup;732733dbc->db_inited = 1;734735cleanup:736if (retval) {737if (dbc->db != NULL)738dbc->db->close(dbc->db);739if (dbc->db_locks_held > 0) {740(void) krb5_lock_file(context, dbc->db_lf_file,741KRB5_LOCKMODE_UNLOCK);742}743if (dbc->db_lf_file >= 0)744close(dbc->db_lf_file);745ctx_clear(dbc);746}747free(dbname);748free(polname);749free(plockname);750return retval;751}752753krb5_error_code754krb5_db2_get_principal(krb5_context context, krb5_const_principal searchfor,755unsigned int flags, krb5_db_entry **entry)756{757krb5_db2_context *dbc;758krb5_error_code retval;759DB *db;760DBT key, contents;761krb5_data keydata, contdata;762int dbret;763764*entry = NULL;765if (!inited(context))766return KRB5_KDB_DBNOTINITED;767768dbc = context->dal_handle->db_context;769770retval = ctx_lock(context, dbc, KRB5_LOCKMODE_SHARED);771if (retval)772return retval;773774/* XXX deal with wildcard lookups */775retval = krb5_encode_princ_dbkey(context, &keydata, searchfor);776if (retval)777goto cleanup;778key.data = keydata.data;779key.size = keydata.length;780781db = dbc->db;782dbret = (*db->get)(db, &key, &contents, 0);783retval = errno;784krb5_free_data_contents(context, &keydata);785switch (dbret) {786case 1:787retval = KRB5_KDB_NOENTRY;788/* Fall through. */789case -1:790default:791goto cleanup;792case 0:793contdata.data = contents.data;794contdata.length = contents.size;795retval = krb5_decode_princ_entry(context, &contdata, entry);796break;797}798799cleanup:800(void) krb5_db2_unlock(context); /* unlock read lock */801return retval;802}803804krb5_error_code805krb5_db2_put_principal(krb5_context context, krb5_db_entry *entry,806char **db_args)807{808int dbret;809DB *db;810DBT key, contents;811krb5_data contdata, keydata;812krb5_error_code retval;813krb5_db2_context *dbc;814815krb5_clear_error_message (context);816if (db_args) {817/* DB2 does not support db_args DB arguments for principal */818k5_setmsg(context, EINVAL, _("Unsupported argument \"%s\" for db2"),819db_args[0]);820return EINVAL;821}822823if (!inited(context))824return KRB5_KDB_DBNOTINITED;825826dbc = context->dal_handle->db_context;827if ((retval = ctx_lock(context, dbc, KRB5_LOCKMODE_EXCLUSIVE)))828return retval;829830db = dbc->db;831832retval = krb5_encode_princ_entry(context, &contdata, entry);833if (retval)834goto cleanup;835contents.data = contdata.data;836contents.size = contdata.length;837retval = krb5_encode_princ_dbkey(context, &keydata, entry->princ);838if (retval) {839krb5_free_data_contents(context, &contdata);840goto cleanup;841}842843key.data = keydata.data;844key.size = keydata.length;845dbret = (*db->put)(db, &key, &contents, 0);846retval = dbret ? errno : 0;847krb5_free_data_contents(context, &keydata);848krb5_free_data_contents(context, &contdata);849850cleanup:851ctx_update_age(dbc);852(void) krb5_db2_unlock(context); /* unlock database */853return (retval);854}855856krb5_error_code857krb5_db2_delete_principal(krb5_context context, krb5_const_principal searchfor)858{859krb5_error_code retval;860krb5_db_entry *entry;861krb5_db2_context *dbc;862DB *db;863DBT key, contents;864krb5_data keydata, contdata;865int i, dbret;866867if (!inited(context))868return KRB5_KDB_DBNOTINITED;869870dbc = context->dal_handle->db_context;871if ((retval = ctx_lock(context, dbc, KRB5_LOCKMODE_EXCLUSIVE)))872return (retval);873874if ((retval = krb5_encode_princ_dbkey(context, &keydata, searchfor)))875goto cleanup;876key.data = keydata.data;877key.size = keydata.length;878879db = dbc->db;880dbret = (*db->get) (db, &key, &contents, 0);881retval = errno;882switch (dbret) {883case 1:884retval = KRB5_KDB_NOENTRY;885/* Fall through. */886case -1:887default:888goto cleankey;889case 0:890;891}892contdata.data = contents.data;893contdata.length = contents.size;894retval = krb5_decode_princ_entry(context, &contdata, &entry);895if (retval)896goto cleankey;897898/* Clear encrypted key contents */899for (i = 0; i < entry->n_key_data; i++) {900if (entry->key_data[i].key_data_length[0]) {901memset(entry->key_data[i].key_data_contents[0], 0,902(unsigned) entry->key_data[i].key_data_length[0]);903}904}905906retval = krb5_encode_princ_entry(context, &contdata, entry);907krb5_db_free_principal(context, entry);908if (retval)909goto cleankey;910911contents.data = contdata.data;912contents.size = contdata.length;913dbret = (*db->put) (db, &key, &contents, 0);914retval = dbret ? errno : 0;915krb5_free_data_contents(context, &contdata);916if (retval)917goto cleankey;918dbret = (*db->del) (db, &key, 0);919retval = dbret ? errno : 0;920cleankey:921krb5_free_data_contents(context, &keydata);922923cleanup:924ctx_update_age(dbc);925(void) krb5_db2_unlock(context); /* unlock write lock */926return retval;927}928929typedef krb5_error_code (*ctx_iterate_cb)(krb5_pointer, krb5_db_entry *);930931/* Cursor structure for ctx_iterate() */932typedef struct iter_curs {933DBT key;934DBT data;935DBT keycopy;936unsigned int startflag;937unsigned int stepflag;938krb5_context ctx;939krb5_db2_context *dbc;940int lockmode;941krb5_boolean islocked;942} iter_curs;943944/* Lock DB handle of curs, updating curs->islocked. */945static krb5_error_code946curs_lock(iter_curs *curs)947{948krb5_error_code retval;949950retval = ctx_lock(curs->ctx, curs->dbc, curs->lockmode);951if (retval)952return retval;953curs->islocked = TRUE;954return 0;955}956957/* Unlock DB handle of curs, updating curs->islocked. */958static void959curs_unlock(iter_curs *curs)960{961ctx_unlock(curs->ctx, curs->dbc);962curs->islocked = FALSE;963}964965/* Set up curs and lock DB. */966static krb5_error_code967curs_init(iter_curs *curs, krb5_context ctx, krb5_db2_context *dbc,968krb5_flags iterflags)969{970int isrecurse = iterflags & KRB5_DB_ITER_RECURSE;971unsigned int prevflag = R_PREV;972unsigned int nextflag = R_NEXT;973974curs->keycopy.size = 0;975curs->keycopy.data = NULL;976curs->islocked = FALSE;977curs->ctx = ctx;978curs->dbc = dbc;979980if (iterflags & KRB5_DB_ITER_WRITE)981curs->lockmode = KRB5_LOCKMODE_EXCLUSIVE;982else983curs->lockmode = KRB5_LOCKMODE_SHARED;984985if (isrecurse) {986#ifdef R_RNEXT987if (dbc->hashfirst) {988k5_setmsg(ctx, EINVAL, _("Recursive iteration is not supported "989"for hash databases"));990return EINVAL;991}992prevflag = R_RPREV;993nextflag = R_RNEXT;994#else995k5_setmsg(ctx, EINVAL, _("Recursive iteration not supported "996"in this version of libdb"));997return EINVAL;998#endif999}1000if (iterflags & KRB5_DB_ITER_REV) {1001curs->startflag = R_LAST;1002curs->stepflag = prevflag;1003} else {1004curs->startflag = R_FIRST;1005curs->stepflag = nextflag;1006}1007return curs_lock(curs);1008}10091010/* Get initial entry. */1011static int1012curs_start(iter_curs *curs)1013{1014DB *db = curs->dbc->db;10151016return db->seq(db, &curs->key, &curs->data, curs->startflag);1017}10181019/* Save iteration state so DB can be unlocked/closed. */1020static krb5_error_code1021curs_save(iter_curs *curs)1022{1023if (!curs->dbc->unlockiter)1024return 0;10251026curs->keycopy.data = malloc(curs->key.size);1027if (curs->keycopy.data == NULL)1028return ENOMEM;10291030curs->keycopy.size = curs->key.size;1031memcpy(curs->keycopy.data, curs->key.data, curs->key.size);1032return 0;1033}10341035/* Free allocated cursor resources */1036static void1037curs_free(iter_curs *curs)1038{1039free(curs->keycopy.data);1040curs->keycopy.size = 0;1041curs->keycopy.data = NULL;1042}10431044/* Move one step of iteration (forwards or backwards as requested). Free1045* curs->keycopy as a side effect, if needed. */1046static int1047curs_step(iter_curs *curs)1048{1049int dbret;1050krb5_db2_context *dbc = curs->dbc;10511052if (dbc->unlockiter) {1053/* Reacquire libdb cursor using saved copy of key. */1054curs->key = curs->keycopy;1055dbret = dbc->db->seq(dbc->db, &curs->key, &curs->data, R_CURSOR);1056curs_free(curs);1057if (dbret)1058return dbret;1059}1060return dbc->db->seq(dbc->db, &curs->key, &curs->data, curs->stepflag);1061}10621063/* Run one invocation of the callback, unlocking the mutex and possibly the DB1064* around the invocation. */1065static krb5_error_code1066curs_run_cb(iter_curs *curs, ctx_iterate_cb func, krb5_pointer func_arg)1067{1068krb5_db2_context *dbc = curs->dbc;1069krb5_error_code retval, lockerr;1070krb5_db_entry *entry;1071krb5_context ctx = curs->ctx;1072krb5_data contdata;10731074contdata = make_data(curs->data.data, curs->data.size);1075retval = krb5_decode_princ_entry(ctx, &contdata, &entry);1076if (retval)1077return retval;1078/* Save libdb key across possible DB closure. */1079retval = curs_save(curs);1080if (retval)1081return retval;10821083if (dbc->unlockiter)1084curs_unlock(curs);10851086k5_mutex_unlock(krb5_db2_mutex);1087retval = (*func)(func_arg, entry);1088krb5_db_free_principal(ctx, entry);1089k5_mutex_lock(krb5_db2_mutex);1090if (dbc->unlockiter) {1091lockerr = curs_lock(curs);1092if (lockerr)1093return lockerr;1094}1095return retval;1096}10971098/* Free cursor resources and unlock the DB if needed. */1099static void1100curs_fini(iter_curs *curs)1101{1102curs_free(curs);1103if (curs->islocked)1104curs_unlock(curs);1105}11061107static krb5_error_code1108ctx_iterate(krb5_context context, krb5_db2_context *dbc,1109ctx_iterate_cb func, krb5_pointer func_arg, krb5_flags iterflags)1110{1111krb5_error_code retval;1112int dbret;1113iter_curs curs;11141115retval = curs_init(&curs, context, dbc, iterflags);1116if (retval)1117return retval;1118dbret = curs_start(&curs);1119while (dbret == 0) {1120retval = curs_run_cb(&curs, func, func_arg);1121if (retval)1122goto cleanup;1123dbret = curs_step(&curs);1124}1125switch (dbret) {1126case 1:1127case 0:1128break;1129case -1:1130default:1131retval = errno;1132}1133cleanup:1134curs_fini(&curs);1135return retval;1136}11371138krb5_error_code1139krb5_db2_iterate(krb5_context context, char *match_expr, ctx_iterate_cb func,1140krb5_pointer func_arg, krb5_flags iterflags)1141{1142if (!inited(context))1143return KRB5_KDB_DBNOTINITED;1144return ctx_iterate(context, context->dal_handle->db_context, func,1145func_arg, iterflags);1146}11471148krb5_boolean1149krb5_db2_set_lockmode(krb5_context context, krb5_boolean mode)1150{1151krb5_boolean old;1152krb5_db2_context *dbc;11531154dbc = context->dal_handle->db_context;1155old = mode;1156if (dbc) {1157old = dbc->db_nb_locks;1158dbc->db_nb_locks = mode;1159}1160return old;1161}11621163/*1164* DAL API functions1165*/1166krb5_error_code1167krb5_db2_lib_init(void)1168{1169return 0;1170}11711172krb5_error_code1173krb5_db2_lib_cleanup(void)1174{1175/* right now, no cleanup required */1176return 0;1177}11781179krb5_error_code1180krb5_db2_open(krb5_context context, char *conf_section, char **db_args,1181int mode)1182{1183krb5_error_code status = 0;11841185krb5_clear_error_message(context);1186if (inited(context))1187return 0;11881189status = configure_context(context, conf_section, db_args);1190if (status != 0)1191return status;11921193status = check_openable(context);1194if (status != 0)1195return status;11961197return ctx_init(context->dal_handle->db_context);1198}11991200krb5_error_code1201krb5_db2_create(krb5_context context, char *conf_section, char **db_args)1202{1203krb5_error_code status = 0;1204krb5_db2_context *dbc;12051206krb5_clear_error_message(context);1207if (inited(context))1208return 0;12091210status = configure_context(context, conf_section, db_args);1211if (status != 0)1212return status;12131214dbc = context->dal_handle->db_context;1215status = ctx_create_db(context, dbc);1216if (status != 0)1217return status;12181219if (!dbc->tempdb)1220krb5_db2_unlock(context);12211222return 0;1223}12241225krb5_error_code1226krb5_db2_destroy(krb5_context context, char *conf_section, char **db_args)1227{1228krb5_error_code status;1229krb5_db2_context *dbc;1230char *dbname = NULL, *lockname = NULL, *polname = NULL, *plockname = NULL;12311232if (inited(context)) {1233status = krb5_db2_fini(context);1234if (status != 0)1235return status;1236}12371238krb5_clear_error_message(context);1239status = configure_context(context, conf_section, db_args);1240if (status != 0)1241return status;12421243status = check_openable(context);1244if (status != 0)1245return status;12461247dbc = context->dal_handle->db_context;12481249status = ctx_allfiles(dbc, &dbname, &lockname, &polname, &plockname);1250if (status)1251goto cleanup;1252status = destroy_file(dbname);1253if (status)1254goto cleanup;1255status = unlink(lockname);1256if (status)1257goto cleanup;1258status = osa_adb_destroy_db(polname, plockname, OSA_ADB_POLICY_DB_MAGIC);1259if (status)1260goto cleanup;12611262status = krb5_db2_fini(context);12631264cleanup:1265free(dbname);1266free(lockname);1267free(polname);1268free(plockname);1269return status;1270}12711272/* policy functions */1273krb5_error_code1274krb5_db2_create_policy(krb5_context context, osa_policy_ent_t policy)1275{1276krb5_db2_context *dbc = context->dal_handle->db_context;12771278return osa_adb_create_policy(dbc->policy_db, policy);1279}12801281krb5_error_code1282krb5_db2_get_policy(krb5_context context,1283char *name, osa_policy_ent_t *policy)1284{1285krb5_db2_context *dbc = context->dal_handle->db_context;12861287return osa_adb_get_policy(dbc->policy_db, name, policy);1288}12891290krb5_error_code1291krb5_db2_put_policy(krb5_context context, osa_policy_ent_t policy)1292{1293krb5_db2_context *dbc = context->dal_handle->db_context;12941295return osa_adb_put_policy(dbc->policy_db, policy);1296}12971298krb5_error_code1299krb5_db2_iter_policy(krb5_context context,1300char *match_entry,1301osa_adb_iter_policy_func func, void *data)1302{1303krb5_db2_context *dbc = context->dal_handle->db_context;13041305return osa_adb_iter_policy(dbc->policy_db, func, data);1306}13071308krb5_error_code1309krb5_db2_delete_policy(krb5_context context, char *policy)1310{1311krb5_db2_context *dbc = context->dal_handle->db_context;13121313return osa_adb_destroy_policy(dbc->policy_db, policy);1314}13151316/*1317* Merge non-replicated attributes from src into dst, setting1318* changed to non-zero if dst was changed.1319*1320* Non-replicated attributes are: last_success, last_failed,1321* fail_auth_count, and any negative TL data values.1322*/1323static krb5_error_code1324krb5_db2_merge_principal(krb5_context context,1325krb5_db_entry *src,1326krb5_db_entry *dst,1327int *changed)1328{1329*changed = 0;13301331if (dst->last_success != src->last_success) {1332dst->last_success = src->last_success;1333(*changed)++;1334}13351336if (dst->last_failed != src->last_failed) {1337dst->last_failed = src->last_failed;1338(*changed)++;1339}13401341if (dst->fail_auth_count != src->fail_auth_count) {1342dst->fail_auth_count = src->fail_auth_count;1343(*changed)++;1344}13451346return 0;1347}13481349struct nra_context {1350krb5_context kcontext;1351krb5_db2_context *db_context;1352};13531354/*1355* Iteration callback merges non-replicated attributes from1356* old database.1357*/1358static krb5_error_code1359krb5_db2_merge_nra_iterator(krb5_pointer ptr, krb5_db_entry *entry)1360{1361struct nra_context *nra = (struct nra_context *)ptr;1362kdb5_dal_handle *dal_handle = nra->kcontext->dal_handle;1363krb5_error_code retval;1364int changed;1365krb5_db_entry *s_entry;1366krb5_db2_context *dst_db;13671368memset(&s_entry, 0, sizeof(s_entry));13691370dst_db = dal_handle->db_context;1371dal_handle->db_context = nra->db_context;13721373/* look up the new principal in the old DB */1374retval = krb5_db2_get_principal(nra->kcontext, entry->princ, 0, &s_entry);1375if (retval != 0) {1376/* principal may be newly created, so ignore */1377dal_handle->db_context = dst_db;1378return 0;1379}13801381/* merge non-replicated attributes from the old entry in */1382krb5_db2_merge_principal(nra->kcontext, s_entry, entry, &changed);13831384dal_handle->db_context = dst_db;13851386/* if necessary, commit the modified new entry to the new DB */1387if (changed) {1388retval = krb5_db2_put_principal(nra->kcontext, entry, NULL);1389} else {1390retval = 0;1391}13921393krb5_db_free_principal(nra->kcontext, s_entry);1394return retval;1395}13961397/*1398* Merge non-replicated attributes (that is, lockout-related1399* attributes and negative TL data types) from the real database1400* into the temporary one.1401*/1402static krb5_error_code1403ctx_merge_nra(krb5_context context, krb5_db2_context *dbc_temp,1404krb5_db2_context *dbc_real)1405{1406struct nra_context nra;14071408nra.kcontext = context;1409nra.db_context = dbc_real;1410return ctx_iterate(context, dbc_temp, krb5_db2_merge_nra_iterator, &nra, 0);1411}14121413/*1414* In the filesystem, promote the temporary database described by dbc_temp to1415* the real database described by dbc_real. Both must be exclusively locked.1416*/1417static krb5_error_code1418ctx_promote(krb5_context context, krb5_db2_context *dbc_temp,1419krb5_db2_context *dbc_real)1420{1421krb5_error_code retval;1422char *tdb = NULL, *tlock = NULL, *tpol = NULL, *tplock = NULL;1423char *rdb = NULL, *rlock = NULL, *rpol = NULL, *rplock = NULL;14241425/* Generate all filenames of interest (including a few we don't need). */1426retval = ctx_allfiles(dbc_temp, &tdb, &tlock, &tpol, &tplock);1427if (retval)1428return retval;1429retval = ctx_allfiles(dbc_real, &rdb, &rlock, &rpol, &rplock);1430if (retval)1431goto cleanup;14321433/* Rename the principal and policy databases into place. */1434if (rename(tdb, rdb)) {1435retval = errno;1436goto cleanup;1437}1438if (rename(tpol, rpol)) {1439retval = errno;1440goto cleanup;1441}14421443ctx_update_age(dbc_real);14441445/* Release and remove the temporary DB lockfiles. */1446(void) unlink(tlock);1447(void) unlink(tplock);14481449cleanup:1450free(tdb);1451free(tlock);1452free(tpol);1453free(tplock);1454free(rdb);1455free(rlock);1456free(rpol);1457free(rplock);1458return retval;1459}14601461krb5_error_code1462krb5_db2_promote_db(krb5_context context, char *conf_section, char **db_args)1463{1464krb5_error_code retval;1465krb5_boolean merge_nra = FALSE, real_locked = FALSE;1466krb5_db2_context *dbc_temp, *dbc_real = NULL;1467char **db_argp;14681469/* context must be initialized with an exclusively locked temp DB. */1470if (!inited(context))1471return KRB5_KDB_DBNOTINITED;1472dbc_temp = context->dal_handle->db_context;1473if (dbc_temp->db_lock_mode != KRB5_LOCKMODE_EXCLUSIVE)1474return KRB5_KDB_NOTLOCKED;1475if (!dbc_temp->tempdb)1476return EINVAL;14771478/* Check db_args for whether we should merge non-replicated attributes. */1479for (db_argp = db_args; *db_argp; db_argp++) {1480if (!strcmp(*db_argp, "merge_nra")) {1481merge_nra = TRUE;1482break;1483}1484}14851486/* Make a db2 context for the real DB. */1487dbc_real = k5alloc(sizeof(*dbc_real), &retval);1488if (dbc_real == NULL)1489return retval;1490ctx_clear(dbc_real);14911492/* Try creating the real DB. */1493dbc_real->db_name = strdup(dbc_temp->db_name);1494if (dbc_real->db_name == NULL)1495goto cleanup;1496dbc_real->tempdb = FALSE;1497retval = ctx_create_db(context, dbc_real);1498if (retval == EEXIST) {1499/* The real database already exists, so open and lock it. */1500dbc_real->db_name = strdup(dbc_temp->db_name);1501if (dbc_real->db_name == NULL)1502goto cleanup;1503dbc_real->tempdb = FALSE;1504retval = ctx_init(dbc_real);1505if (retval)1506goto cleanup;1507retval = ctx_lock(context, dbc_real, KRB5_DB_LOCKMODE_EXCLUSIVE);1508if (retval)1509goto cleanup;1510} else if (retval)1511goto cleanup;1512real_locked = TRUE;15131514if (merge_nra) {1515retval = ctx_merge_nra(context, dbc_temp, dbc_real);1516if (retval)1517goto cleanup;1518}15191520/* Perform filesystem manipulations for the promotion. */1521retval = ctx_promote(context, dbc_temp, dbc_real);1522if (retval)1523goto cleanup;15241525/* Unlock and finalize context since the temp DB is gone. */1526(void) krb5_db2_unlock(context);1527krb5_db2_fini(context);15281529cleanup:1530if (real_locked)1531(void) ctx_unlock(context, dbc_real);1532if (dbc_real)1533ctx_fini(dbc_real);1534return retval;1535}15361537krb5_error_code1538krb5_db2_check_policy_as(krb5_context kcontext, krb5_kdc_req *request,1539krb5_db_entry *client, krb5_db_entry *server,1540krb5_timestamp kdc_time, const char **status,1541krb5_pa_data ***e_data)1542{1543krb5_error_code retval;15441545retval = krb5_db2_lockout_check_policy(kcontext, client, kdc_time);1546if (retval == KRB5KDC_ERR_CLIENT_REVOKED)1547*status = "LOCKED_OUT";1548return retval;1549}15501551void1552krb5_db2_audit_as_req(krb5_context kcontext, krb5_kdc_req *request,1553const krb5_address *local_addr,1554const krb5_address *remote_addr, krb5_db_entry *client,1555krb5_db_entry *server, krb5_timestamp authtime,1556krb5_error_code error_code)1557{1558(void) krb5_db2_lockout_audit(kcontext, client, authtime, error_code);1559}156015611562