2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
2N/A *
2N/A * $Header$
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
2N/A *
2N/A * Openvision retains the copyright to derivative works of
2N/A * this source code. Do *NOT* create a derivative of this
2N/A * source code before consulting with your legal department.
2N/A * Do *NOT* integrate *ANY* of this source code into another
2N/A * product before consulting with your legal department.
2N/A *
2N/A * For further information, read the top-level Openvision
2N/A * copyright which is contained in the top-level MIT Kerberos
2N/A * copyright.
2N/A *
2N/A * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
2N/A *
2N/A */
2N/A
2N/A#if !defined(lint) && !defined(__CODECENTER__)
2N/Astatic char *rcsid = "$Header$";
2N/A#endif
2N/A
2N/A#include <sys/file.h>
2N/A#include <fcntl.h>
2N/A#include <unistd.h>
2N/A#include <k5-int.h>
2N/A#include "policy_db.h"
2N/A#include <stdlib.h>
2N/A#include <db.h>
2N/A
2N/A#define MAX_LOCK_TRIES 5
2N/A
2N/Astruct _locklist {
2N/A osa_adb_lock_ent lockinfo;
2N/A struct _locklist *next;
2N/A};
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_create_db(char *filename, char *lockfilename, int magic)
2N/A{
2N/A int lf;
2N/A DB *db;
2N/A BTREEINFO btinfo;
2N/A
2N/A memset(&btinfo, 0, sizeof(btinfo));
2N/A btinfo.flags = 0;
2N/A btinfo.cachesize = 0;
2N/A btinfo.psize = 4096;
2N/A btinfo.lorder = 0;
2N/A btinfo.minkeypage = 0;
2N/A btinfo.compare = NULL;
2N/A btinfo.prefix = NULL;
2N/A db = dbopen(filename, O_RDWR | O_CREAT | O_EXCL, 0600, DB_BTREE, &btinfo);
2N/A if (db == NULL)
2N/A return errno;
2N/A if (db->close(db) < 0)
2N/A return errno;
2N/A
2N/A /* only create the lock file if we successfully created the db */
2N/A lf = THREEPARAMOPEN(lockfilename, O_RDWR | O_CREAT | O_EXCL, 0600);
2N/A if (lf == -1)
2N/A return errno;
2N/A (void) close(lf);
2N/A
2N/A return OSA_ADB_OK;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_destroy_db(char *filename, char *lockfilename, int magic)
2N/A{
2N/A /* the admin databases do not contain security-critical data */
2N/A if (unlink(filename) < 0 ||
2N/A unlink(lockfilename) < 0)
2N/A return errno;
2N/A return OSA_ADB_OK;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_rename_db(char *filefrom, char *lockfrom, char *fileto, char *lockto,
2N/A int magic)
2N/A{
2N/A osa_adb_db_t fromdb, todb;
2N/A krb5_error_code ret;
2N/A
2N/A /* make sure todb exists */
2N/A if ((ret = osa_adb_create_db(fileto, lockto, magic)) &&
2N/A ret != EEXIST)
2N/A return ret;
2N/A
2N/A if ((ret = osa_adb_init_db(&fromdb, filefrom, lockfrom, magic)))
2N/A return ret;
2N/A if ((ret = osa_adb_init_db(&todb, fileto, lockto, magic))) {
2N/A (void) osa_adb_fini_db(fromdb, magic);
2N/A return ret;
2N/A }
2N/A if ((ret = osa_adb_get_lock(fromdb, KRB5_DB_LOCKMODE_PERMANENT))) {
2N/A (void) osa_adb_fini_db(fromdb, magic);
2N/A (void) osa_adb_fini_db(todb, magic);
2N/A return ret;
2N/A }
2N/A if ((ret = osa_adb_get_lock(todb, KRB5_DB_LOCKMODE_PERMANENT))) {
2N/A (void) osa_adb_fini_db(fromdb, magic);
2N/A (void) osa_adb_fini_db(todb, magic);
2N/A return ret;
2N/A }
2N/A if ((rename(filefrom, fileto) < 0)) {
2N/A (void) osa_adb_fini_db(fromdb, magic);
2N/A (void) osa_adb_fini_db(todb, magic);
2N/A return errno;
2N/A }
2N/A /*
2N/A * Do not release the lock on fromdb because it is being renamed
2N/A * out of existence; no one can ever use it again.
2N/A */
2N/A if ((ret = osa_adb_release_lock(todb))) {
2N/A (void) osa_adb_fini_db(fromdb, magic);
2N/A (void) osa_adb_fini_db(todb, magic);
2N/A return ret;
2N/A }
2N/A
2N/A (void) osa_adb_fini_db(fromdb, magic);
2N/A (void) osa_adb_fini_db(todb, magic);
2N/A return 0;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_init_db(osa_adb_db_t *dbp, char *filename, char *lockfilename,
2N/A int magic)
2N/A{
2N/A osa_adb_db_t db;
2N/A static struct _locklist *locklist = NULL;
2N/A struct _locklist *lockp;
2N/A krb5_error_code code;
2N/A
2N/A if (dbp == NULL || filename == NULL)
2N/A return EINVAL;
2N/A
2N/A db = (osa_adb_princ_t) malloc(sizeof(osa_adb_db_ent));
2N/A if (db == NULL)
2N/A return ENOMEM;
2N/A
2N/A memset(db, 0, sizeof(*db));
2N/A db->info.hash = NULL;
2N/A db->info.bsize = 256;
2N/A db->info.ffactor = 8;
2N/A db->info.nelem = 25000;
2N/A db->info.lorder = 0;
2N/A
2N/A db->btinfo.flags = 0;
2N/A db->btinfo.cachesize = 0;
2N/A db->btinfo.psize = 4096;
2N/A db->btinfo.lorder = 0;
2N/A db->btinfo.minkeypage = 0;
2N/A db->btinfo.compare = NULL;
2N/A db->btinfo.prefix = NULL;
2N/A /*
2N/A * A process is allowed to open the same database multiple times
2N/A * and access it via different handles. If the handles use
2N/A * distinct lockinfo structures, things get confused: lock(A),
2N/A * lock(B), release(B) will result in the kernel unlocking the
2N/A * lock file but handle A will still think the file is locked.
2N/A * Therefore, all handles using the same lock file must share a
2N/A * single lockinfo structure.
2N/A *
2N/A * It is not sufficient to have a single lockinfo structure,
2N/A * however, because a single process may also wish to open
2N/A * multiple different databases simultaneously, with different
2N/A * lock files. This code used to use a single static lockinfo
2N/A * structure, which means that the second database opened used
2N/A * the first database's lock file. This was Bad.
2N/A *
2N/A * We now maintain a linked list of lockinfo structures, keyed by
2N/A * lockfilename. An entry is added when this function is called
2N/A * with a new lockfilename, and all subsequent calls with that
2N/A * lockfilename use the existing entry, updating the refcnt.
2N/A * When the database is closed with fini_db(), the refcnt is
2N/A * decremented, and when it is zero the lockinfo structure is
2N/A * freed and reset. The entry in the linked list, however, is
2N/A * never removed; it will just be reinitialized the next time
2N/A * init_db is called with the right lockfilename.
2N/A */
2N/A
2N/A /* find or create the lockinfo structure for lockfilename */
2N/A lockp = locklist;
2N/A while (lockp) {
2N/A if (strcmp(lockp->lockinfo.filename, lockfilename) == 0)
2N/A break;
2N/A else
2N/A lockp = lockp->next;
2N/A }
2N/A if (lockp == NULL) {
2N/A /* doesn't exist, create it, add to list */
2N/A lockp = (struct _locklist *) malloc(sizeof(*lockp));
2N/A if (lockp == NULL) {
2N/A free(db);
2N/A return ENOMEM;
2N/A }
2N/A memset(lockp, 0, sizeof(*lockp));
2N/A lockp->next = locklist;
2N/A locklist = lockp;
2N/A }
2N/A
2N/A /* now initialize lockp->lockinfo if necessary */
2N/A if (lockp->lockinfo.lockfile == NULL) {
2N/A if ((code = krb5int_init_context_kdc(&lockp->lockinfo.context))) {
2N/A free(db);
2N/A return((krb5_error_code) code);
2N/A }
2N/A
2N/A /*
2N/A * needs be open read/write so that write locking can work with
2N/A * POSIX systems
2N/A */
2N/A lockp->lockinfo.filename = strdup(lockfilename);
2N/A /* Solaris Kerberos */
2N/A if ((lockp->lockinfo.lockfile = fopen(lockfilename, "r+F")) == NULL) {
2N/A /*
2N/A * maybe someone took away write permission so we could only
2N/A * get shared locks?
2N/A */
2N/A /* Solaris Kerberos */
2N/A if ((lockp->lockinfo.lockfile = fopen(lockfilename, "rF"))
2N/A == NULL) {
2N/A free(db);
2N/A return OSA_ADB_NOLOCKFILE;
2N/A }
2N/A }
2N/A set_cloexec_file(lockp->lockinfo.lockfile);
2N/A lockp->lockinfo.lockmode = lockp->lockinfo.lockcnt = 0;
2N/A }
2N/A
2N/A /* lockp is set, lockinfo is initialized, update the reference count */
2N/A db->lock = &lockp->lockinfo;
2N/A db->lock->refcnt++;
2N/A
2N/A db->opencnt = 0;
2N/A db->filename = strdup(filename);
2N/A db->magic = magic;
2N/A
2N/A *dbp = db;
2N/A
2N/A return OSA_ADB_OK;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_fini_db(osa_adb_db_t db, int magic)
2N/A{
2N/A if (db->magic != magic)
2N/A return EINVAL;
2N/A if (db->lock->refcnt == 0) {
2N/A /* barry says this can't happen */
2N/A return OSA_ADB_FAILURE;
2N/A } else {
2N/A db->lock->refcnt--;
2N/A }
2N/A
2N/A if (db->lock->refcnt == 0) {
2N/A /*
2N/A * Don't free db->lock->filename, it is used as a key to
2N/A * find the lockinfo entry in the linked list. If the
2N/A * lockfile doesn't exist, we must be closing the database
2N/A * after trashing it. This has to be allowed, so don't
2N/A * generate an error.
2N/A */
2N/A if (db->lock->lockmode != KRB5_DB_LOCKMODE_PERMANENT)
2N/A (void) fclose(db->lock->lockfile);
2N/A db->lock->lockfile = NULL;
2N/A krb5_free_context(db->lock->context);
2N/A }
2N/A
2N/A db->magic = 0;
2N/A free(db->filename);
2N/A free(db);
2N/A return OSA_ADB_OK;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_get_lock(osa_adb_db_t db, int mode)
2N/A{
2N/A int tries, gotlock, perm, krb5_mode, ret = 0;
2N/A
2N/A if (db->lock->lockmode >= mode) {
2N/A /* No need to upgrade lock, just incr refcnt and return */
2N/A db->lock->lockcnt++;
2N/A return(OSA_ADB_OK);
2N/A }
2N/A
2N/A perm = 0;
2N/A switch (mode) {
2N/A case KRB5_DB_LOCKMODE_PERMANENT:
2N/A perm = 1;
2N/A case KRB5_DB_LOCKMODE_EXCLUSIVE:
2N/A krb5_mode = KRB5_LOCKMODE_EXCLUSIVE;
2N/A break;
2N/A case KRB5_DB_LOCKMODE_SHARED:
2N/A krb5_mode = KRB5_LOCKMODE_SHARED;
2N/A break;
2N/A default:
2N/A return(EINVAL);
2N/A }
2N/A
2N/A for (gotlock = tries = 0; tries < MAX_LOCK_TRIES; tries++) {
2N/A if ((ret = krb5_lock_file(db->lock->context,
2N/A fileno(db->lock->lockfile),
2N/A krb5_mode|KRB5_LOCKMODE_DONTBLOCK)) == 0) {
2N/A gotlock++;
2N/A break;
2N/A } else if (ret == EBADF && mode == KRB5_DB_LOCKMODE_EXCLUSIVE)
2N/A /* tried to exclusive-lock something we don't have */
2N/A /* write access to */
2N/A return OSA_ADB_NOEXCL_PERM;
2N/A
2N/A sleep(1);
2N/A }
2N/A
2N/A /* test for all the likely "can't get lock" error codes */
2N/A if (ret == EACCES || ret == EAGAIN || ret == EWOULDBLOCK)
2N/A return OSA_ADB_CANTLOCK_DB;
2N/A else if (ret != 0)
2N/A return ret;
2N/A
2N/A /*
2N/A * If the file no longer exists, someone acquired a permanent
2N/A * lock. If that process terminates its exclusive lock is lost,
2N/A * but if we already had the file open we can (probably) lock it
2N/A * even though it has been unlinked. So we need to insist that
2N/A * it exist.
2N/A */
2N/A if (access(db->lock->filename, F_OK) < 0) {
2N/A (void) krb5_lock_file(db->lock->context,
2N/A fileno(db->lock->lockfile),
2N/A KRB5_LOCKMODE_UNLOCK);
2N/A return OSA_ADB_NOLOCKFILE;
2N/A }
2N/A
2N/A /* we have the shared/exclusive lock */
2N/A
2N/A if (perm) {
2N/A if (unlink(db->lock->filename) < 0) {
2N/A /* somehow we can't delete the file, but we already */
2N/A /* have the lock, so release it and return */
2N/A
2N/A ret = errno;
2N/A (void) krb5_lock_file(db->lock->context,
2N/A fileno(db->lock->lockfile),
2N/A KRB5_LOCKMODE_UNLOCK);
2N/A
2N/A /* maybe we should return CANTLOCK_DB.. but that would */
2N/A /* look just like the db was already locked */
2N/A return ret;
2N/A }
2N/A
2N/A /* this releases our exclusive lock.. which is okay because */
2N/A /* now no one else can get one either */
2N/A (void) fclose(db->lock->lockfile);
2N/A }
2N/A
2N/A db->lock->lockmode = mode;
2N/A db->lock->lockcnt++;
2N/A return OSA_ADB_OK;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_release_lock(osa_adb_db_t db)
2N/A{
2N/A int ret, fd;
2N/A
2N/A if (!db->lock->lockcnt) /* lock already unlocked */
2N/A return OSA_ADB_NOTLOCKED;
2N/A
2N/A if (--db->lock->lockcnt == 0) {
2N/A if (db->lock->lockmode == KRB5_DB_LOCKMODE_PERMANENT) {
2N/A /* now we need to create the file since it does not exist */
2N/A fd = THREEPARAMOPEN(db->lock->filename,O_RDWR | O_CREAT | O_EXCL,
2N/A 0600);
2N/A if (fd < 0)
2N/A return OSA_ADB_NOLOCKFILE;
2N/A set_cloexec_fd(fd);
2N/A /* Solaris Kerberos */
2N/A if ((db->lock->lockfile = fdopen(fd, "w+F")) == NULL)
2N/A return OSA_ADB_NOLOCKFILE;
2N/A } else if ((ret = krb5_lock_file(db->lock->context,
2N/A fileno(db->lock->lockfile),
2N/A KRB5_LOCKMODE_UNLOCK)))
2N/A return ret;
2N/A
2N/A db->lock->lockmode = 0;
2N/A }
2N/A return OSA_ADB_OK;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_open_and_lock(osa_adb_princ_t db, int locktype)
2N/A{
2N/A int ret;
2N/A
2N/A ret = osa_adb_get_lock(db, locktype);
2N/A if (ret != OSA_ADB_OK)
2N/A return ret;
2N/A if (db->opencnt)
2N/A goto open_ok;
2N/A
2N/A db->db = dbopen(db->filename, O_RDWR, 0600, DB_BTREE, &db->btinfo);
2N/A if (db->db != NULL)
2N/A goto open_ok;
2N/A switch (errno) {
2N/A#ifdef EFTYPE
2N/A case EFTYPE:
2N/A#endif
2N/A case EINVAL:
2N/A db->db = dbopen(db->filename, O_RDWR, 0600, DB_HASH, &db->info);
2N/A if (db->db != NULL)
2N/A goto open_ok;
2N/A default:
2N/A (void) osa_adb_release_lock(db);
2N/A if (errno == EINVAL)
2N/A return OSA_ADB_BAD_DB;
2N/A return errno;
2N/A }
2N/Aopen_ok:
2N/A db->opencnt++;
2N/A return OSA_ADB_OK;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Aosa_adb_close_and_unlock(osa_adb_princ_t db)
2N/A{
2N/A if (--db->opencnt)
2N/A return osa_adb_release_lock(db);
2N/A if(db->db != NULL && db->db->close(db->db) == -1) {
2N/A (void) osa_adb_release_lock(db);
2N/A return OSA_ADB_FAILURE;
2N/A }
2N/A
2N/A db->db = NULL;
2N/A
2N/A return(osa_adb_release_lock(db));
2N/A}