1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* 3 * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved 4 * 5 * $Header$ 6 */ 7 8 #include <k5-int.h> 9 #include <sys/file.h> 10 #include <fcntl.h> 11 #include <unistd.h> 12 #include "policy_db.h" 13 #include <stdlib.h> 14 #include <db.h> 15 16 struct _locklist { 17 osa_adb_lock_ent lockinfo; 18 struct _locklist *next; 19 }; 20 21 krb5_error_code 22 osa_adb_create_db(char *filename, char *lockfilename, int magic) 23 { 24 int lf; 25 DB *db; 26 BTREEINFO btinfo; 27 28 memset(&btinfo, 0, sizeof(btinfo)); 29 btinfo.flags = 0; 30 btinfo.cachesize = 0; 31 btinfo.psize = 4096; 32 btinfo.lorder = 0; 33 btinfo.minkeypage = 0; 34 btinfo.compare = NULL; 35 btinfo.prefix = NULL; 36 db = dbopen(filename, O_RDWR | O_CREAT | O_EXCL, 0600, DB_BTREE, &btinfo); 37 if (db == NULL) 38 return errno; 39 if (db->close(db) < 0) 40 return errno; 41 42 /* only create the lock file if we successfully created the db */ 43 lf = THREEPARAMOPEN(lockfilename, O_RDWR | O_CREAT | O_EXCL, 0600); 44 if (lf == -1) 45 return errno; 46 (void) close(lf); 47 48 return OSA_ADB_OK; 49 } 50 51 krb5_error_code 52 osa_adb_destroy_db(char *filename, char *lockfilename, int magic) 53 { 54 /* the admin databases do not contain security-critical data */ 55 if (unlink(filename) < 0 || 56 unlink(lockfilename) < 0) 57 return errno; 58 return OSA_ADB_OK; 59 } 60 61 krb5_error_code 62 osa_adb_init_db(osa_adb_db_t *dbp, char *filename, char *lockfilename, 63 int magic) 64 { 65 osa_adb_db_t db; 66 static struct _locklist *locklist = NULL; 67 struct _locklist *lockp; 68 krb5_error_code code; 69 70 if (dbp == NULL || filename == NULL) 71 return EINVAL; 72 73 db = (osa_adb_princ_t) malloc(sizeof(osa_adb_db_ent)); 74 if (db == NULL) 75 return ENOMEM; 76 77 memset(db, 0, sizeof(*db)); 78 db->info.hash = NULL; 79 db->info.bsize = 256; 80 db->info.ffactor = 8; 81 db->info.nelem = 25000; 82 db->info.lorder = 0; 83 84 db->btinfo.flags = 0; 85 db->btinfo.cachesize = 0; 86 db->btinfo.psize = 4096; 87 db->btinfo.lorder = 0; 88 db->btinfo.minkeypage = 0; 89 db->btinfo.compare = NULL; 90 db->btinfo.prefix = NULL; 91 /* 92 * A process is allowed to open the same database multiple times 93 * and access it via different handles. If the handles use 94 * distinct lockinfo structures, things get confused: lock(A), 95 * lock(B), release(B) will result in the kernel unlocking the 96 * lock file but handle A will still think the file is locked. 97 * Therefore, all handles using the same lock file must share a 98 * single lockinfo structure. 99 * 100 * It is not sufficient to have a single lockinfo structure, 101 * however, because a single process may also wish to open 102 * multiple different databases simultaneously, with different 103 * lock files. This code used to use a single static lockinfo 104 * structure, which means that the second database opened used 105 * the first database's lock file. This was Bad. 106 * 107 * We now maintain a linked list of lockinfo structures, keyed by 108 * lockfilename. An entry is added when this function is called 109 * with a new lockfilename, and all subsequent calls with that 110 * lockfilename use the existing entry, updating the refcnt. 111 * When the database is closed with fini_db(), the refcnt is 112 * decremented, and when it is zero the lockinfo structure is 113 * freed and reset. The entry in the linked list, however, is 114 * never removed; it will just be reinitialized the next time 115 * init_db is called with the right lockfilename. 116 */ 117 118 /* find or create the lockinfo structure for lockfilename */ 119 lockp = locklist; 120 while (lockp) { 121 if (strcmp(lockp->lockinfo.filename, lockfilename) == 0) 122 break; 123 else 124 lockp = lockp->next; 125 } 126 if (lockp == NULL) { 127 /* doesn't exist, create it, add to list */ 128 lockp = (struct _locklist *) malloc(sizeof(*lockp)); 129 if (lockp == NULL) { 130 free(db); 131 return ENOMEM; 132 } 133 memset(lockp, 0, sizeof(*lockp)); 134 lockp->lockinfo.filename = strdup(lockfilename); 135 if (lockp->lockinfo.filename == NULL) { 136 free(lockp); 137 free(db); 138 return ENOMEM; 139 } 140 lockp->next = locklist; 141 locklist = lockp; 142 } 143 144 /* now initialize lockp->lockinfo if necessary */ 145 if (lockp->lockinfo.lockfile == NULL) { 146 if ((code = krb5int_init_context_kdc(&lockp->lockinfo.context))) { 147 free(db); 148 return((krb5_error_code) code); 149 } 150 151 /* 152 * needs be open read/write so that write locking can work with 153 * POSIX systems 154 */ 155 if ((lockp->lockinfo.lockfile = fopen(lockfilename, "r+")) == NULL) { 156 /* 157 * maybe someone took away write permission so we could only 158 * get shared locks? 159 */ 160 if ((lockp->lockinfo.lockfile = fopen(lockfilename, "r")) 161 == NULL) { 162 free(db); 163 return OSA_ADB_NOLOCKFILE; 164 } 165 } 166 set_cloexec_file(lockp->lockinfo.lockfile); 167 lockp->lockinfo.lockmode = lockp->lockinfo.lockcnt = 0; 168 } 169 170 /* lockp is set, lockinfo is initialized, update the reference count */ 171 db->lock = &lockp->lockinfo; 172 db->lock->refcnt++; 173 174 db->opencnt = 0; 175 db->filename = strdup(filename); 176 db->magic = magic; 177 178 *dbp = db; 179 180 return OSA_ADB_OK; 181 } 182 183 krb5_error_code 184 osa_adb_fini_db(osa_adb_db_t db, int magic) 185 { 186 if (db->magic != magic) 187 return EINVAL; 188 if (db->lock->refcnt == 0) { 189 /* barry says this can't happen */ 190 return OSA_ADB_FAILURE; 191 } else { 192 db->lock->refcnt--; 193 } 194 195 if (db->lock->refcnt == 0) { 196 /* 197 * Don't free db->lock->filename, it is used as a key to 198 * find the lockinfo entry in the linked list. If the 199 * lockfile doesn't exist, we must be closing the database 200 * after trashing it. This has to be allowed, so don't 201 * generate an error. 202 */ 203 if (db->lock->lockmode != KRB5_DB_LOCKMODE_PERMANENT) 204 (void) fclose(db->lock->lockfile); 205 db->lock->lockfile = NULL; 206 krb5_free_context(db->lock->context); 207 } 208 209 db->magic = 0; 210 free(db->filename); 211 free(db); 212 return OSA_ADB_OK; 213 } 214 215 krb5_error_code 216 osa_adb_get_lock(osa_adb_db_t db, int mode) 217 { 218 int perm, krb5_mode, ret = 0; 219 220 if (db->lock->lockmode >= mode) { 221 /* No need to upgrade lock, just incr refcnt and return */ 222 db->lock->lockcnt++; 223 return(OSA_ADB_OK); 224 } 225 226 perm = 0; 227 switch (mode) { 228 case KRB5_DB_LOCKMODE_PERMANENT: 229 perm = 1; 230 case KRB5_DB_LOCKMODE_EXCLUSIVE: 231 krb5_mode = KRB5_LOCKMODE_EXCLUSIVE; 232 break; 233 case KRB5_DB_LOCKMODE_SHARED: 234 krb5_mode = KRB5_LOCKMODE_SHARED; 235 break; 236 default: 237 return(EINVAL); 238 } 239 240 ret = krb5_lock_file(db->lock->context, fileno(db->lock->lockfile), 241 krb5_mode); 242 if (ret == EBADF && mode == KRB5_DB_LOCKMODE_EXCLUSIVE) 243 return OSA_ADB_NOEXCL_PERM; 244 else if (ret == EACCES || ret == EAGAIN || ret == EWOULDBLOCK) 245 return OSA_ADB_CANTLOCK_DB; 246 else if (ret != 0) 247 return ret; 248 249 /* 250 * If the file no longer exists, someone acquired a permanent 251 * lock. If that process terminates its exclusive lock is lost, 252 * but if we already had the file open we can (probably) lock it 253 * even though it has been unlinked. So we need to insist that 254 * it exist. 255 */ 256 if (access(db->lock->filename, F_OK) < 0) { 257 (void) krb5_lock_file(db->lock->context, 258 fileno(db->lock->lockfile), 259 KRB5_LOCKMODE_UNLOCK); 260 return OSA_ADB_NOLOCKFILE; 261 } 262 263 /* we have the shared/exclusive lock */ 264 265 if (perm) { 266 if (unlink(db->lock->filename) < 0) { 267 /* somehow we can't delete the file, but we already */ 268 /* have the lock, so release it and return */ 269 270 ret = errno; 271 (void) krb5_lock_file(db->lock->context, 272 fileno(db->lock->lockfile), 273 KRB5_LOCKMODE_UNLOCK); 274 275 /* maybe we should return CANTLOCK_DB.. but that would */ 276 /* look just like the db was already locked */ 277 return ret; 278 } 279 280 /* this releases our exclusive lock.. which is okay because */ 281 /* now no one else can get one either */ 282 (void) fclose(db->lock->lockfile); 283 } 284 285 db->lock->lockmode = mode; 286 db->lock->lockcnt++; 287 return OSA_ADB_OK; 288 } 289 290 krb5_error_code 291 osa_adb_release_lock(osa_adb_db_t db) 292 { 293 int ret, fd; 294 295 if (!db->lock->lockcnt) /* lock already unlocked */ 296 return OSA_ADB_NOTLOCKED; 297 298 if (--db->lock->lockcnt == 0) { 299 if (db->lock->lockmode == KRB5_DB_LOCKMODE_PERMANENT) { 300 /* now we need to create the file since it does not exist */ 301 fd = THREEPARAMOPEN(db->lock->filename,O_RDWR | O_CREAT | O_EXCL, 302 0600); 303 if (fd < 0) 304 return OSA_ADB_NOLOCKFILE; 305 set_cloexec_fd(fd); 306 if ((db->lock->lockfile = fdopen(fd, "w+")) == NULL) 307 return OSA_ADB_NOLOCKFILE; 308 } else if ((ret = krb5_lock_file(db->lock->context, 309 fileno(db->lock->lockfile), 310 KRB5_LOCKMODE_UNLOCK))) 311 return ret; 312 313 db->lock->lockmode = 0; 314 } 315 return OSA_ADB_OK; 316 } 317 318 krb5_error_code 319 osa_adb_open_and_lock(osa_adb_princ_t db, int locktype) 320 { 321 int ret; 322 323 ret = osa_adb_get_lock(db, locktype); 324 if (ret != OSA_ADB_OK) 325 return ret; 326 if (db->opencnt) 327 goto open_ok; 328 329 db->db = dbopen(db->filename, O_RDWR, 0600, DB_BTREE, &db->btinfo); 330 if (db->db == NULL && IS_EFTYPE(errno)) 331 db->db = dbopen(db->filename, O_RDWR, 0600, DB_HASH, &db->info); 332 if (db->db == NULL) { 333 (void)osa_adb_release_lock(db); 334 return (errno == EINVAL) ? OSA_ADB_BAD_DB : errno; 335 } 336 337 open_ok: 338 db->opencnt++; 339 return OSA_ADB_OK; 340 } 341 342 krb5_error_code 343 osa_adb_close_and_unlock(osa_adb_princ_t db) 344 { 345 if (--db->opencnt) 346 return osa_adb_release_lock(db); 347 if(db->db != NULL && db->db->close(db->db) == -1) { 348 (void) osa_adb_release_lock(db); 349 return OSA_ADB_FAILURE; 350 } 351 352 db->db = NULL; 353 354 return(osa_adb_release_lock(db)); 355 } 356