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