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