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