/* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 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 */ #include #include #include #include #include "policy_db.h" #include #include #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; /* FALLTHROUGH */ 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; /* FALLTHROUGH */ 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)); }