xref: /illumos-gate/usr/src/lib/krb5/plugins/kdb/db2/adb_openclose.c (revision 8a2b682e57a046b828f37bcde1776f131ef4629f)
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 
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 
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 
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 
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 
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 
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 
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 
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 
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