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
38struct _locklist {
39     osa_adb_lock_ent lockinfo;
40     struct _locklist *next;
41};
42
43krb5_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
73krb5_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
83krb5_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
131krb5_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
246krb5_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
277krb5_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
363krb5_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
387krb5_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     }
415open_ok:
416     db->opencnt++;
417     return OSA_ADB_OK;
418}
419
420krb5_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