xref: /freebsd/crypto/krb5/src/plugins/kdb/db2/adb_openclose.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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
osa_adb_create_db(char * filename,char * lockfilename,int magic)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
osa_adb_destroy_db(char * filename,char * lockfilename,int magic)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
osa_adb_init_db(osa_adb_db_t * dbp,char * filename,char * lockfilename,int magic)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
osa_adb_fini_db(osa_adb_db_t db,int magic)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
osa_adb_get_lock(osa_adb_db_t db,int mode)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
osa_adb_release_lock(osa_adb_db_t db)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
osa_adb_open_and_lock(osa_adb_princ_t db,int locktype)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
osa_adb_close_and_unlock(osa_adb_princ_t db)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