1/* db_berkeley.c--SASL berkeley db interface
2 * Rob Siemborski
3 * Tim Martin
4 * $Id: db_berkeley.c,v 1.11 2011/09/12 08:50:47 mel Exp $
5 */
6/*
7 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in
18 *    the documentation and/or other materials provided with the
19 *    distribution.
20 *
21 * 3. The name "Carnegie Mellon University" must not be used to
22 *    endorse or promote products derived from this software without
23 *    prior written permission. For permission or any other legal
24 *    details, please contact
25 *      Office of Technology Transfer
26 *      Carnegie Mellon University
27 *      5000 Forbes Avenue
28 *      Pittsburgh, PA  15213-3890
29 *      (412) 268-4387, fax: (412) 268-7395
30 *      tech-transfer@andrew.cmu.edu
31 *
32 * 4. Redistributions of any form whatsoever must retain the following
33 *    acknowledgment:
34 *    "This product includes software developed by Computing Services
35 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
36 *
37 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
38 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
39 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
40 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
41 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
42 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
43 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
44 */
45
46#include <config.h>
47
48#include <db.h>
49
50#include <sys/stat.h>
51#include <stdlib.h>
52#include <assert.h>
53#include <errno.h>
54#include "sasldb.h"
55
56#define DB_VERSION_FULL ((DB_VERSION_MAJOR << 24) | (DB_VERSION_MINOR << 16) | DB_VERSION_PATCH)
57
58static int db_ok = 0;
59#if defined(KEEP_DB_OPEN)
60static DB * g_db = NULL;
61#endif
62
63/*
64 * Open the database
65 */
66static int berkeleydb_open(const sasl_utils_t *utils,
67			   sasl_conn_t *conn,
68			   int rdwr, DB **mbdb)
69{
70    const char *path = SASL_DB_PATH;
71    int ret;
72    int flags;
73    void *cntxt;
74    sasl_getopt_t *getopt;
75
76#if defined(KEEP_DB_OPEN)
77    if (g_db) {
78	*mbdb = g_db;
79	return SASL_OK;
80    }
81#endif
82
83    if (utils->getcallback(conn, SASL_CB_GETOPT,
84			   (sasl_callback_ft *)&getopt, &cntxt) == SASL_OK) {
85	const char *p;
86	if (getopt(cntxt, NULL, "sasldb_path", &p, NULL) == SASL_OK
87	    && p != NULL && *p != 0) {
88	    path = p;
89	}
90    }
91
92    if (rdwr) flags = DB_CREATE;
93    else flags = DB_RDONLY;
94#if defined(KEEP_DB_OPEN)
95#if defined(DB_THREAD)
96    flags |= DB_THREAD;
97#endif
98#endif
99
100#if DB_VERSION_FULL < 0x03000000
101    ret = db_open(path, DB_HASH, flags, 0660, NULL, NULL, mbdb);
102#else /* DB_VERSION_FULL < 0x03000000 */
103    ret = db_create(mbdb, NULL, 0);
104    if (ret == 0 && *mbdb != NULL)
105    {
106#if DB_VERSION_FULL >= 0x04010000
107	ret = (*mbdb)->open(*mbdb, NULL, path, NULL, DB_HASH, flags, 0660);
108#else
109	ret = (*mbdb)->open(*mbdb, path, NULL, DB_HASH, flags, 0660);
110#endif
111	if (ret != 0)
112	{
113	    (void) (*mbdb)->close(*mbdb, 0);
114	    *mbdb = NULL;
115	}
116    }
117#endif /* DB_VERSION_FULL < 0x03000000 */
118
119    if (ret != 0) {
120	if (rdwr == 0 && ret == ENOENT) {
121	    /* File not found and we are only reading the data.
122	       Treat as SASL_NOUSER. */
123	    return SASL_NOUSER;
124	}
125	utils->log(conn, SASL_LOG_ERR,
126		   "unable to open Berkeley db %s: %s",
127		   path, db_strerror(ret));
128	utils->seterror(conn, SASL_NOLOG, "Unable to open DB");
129	return SASL_FAIL;
130    }
131
132#if defined(KEEP_DB_OPEN)
133    /* Save the DB handle for later use */
134    g_db = *mbdb;
135#endif
136    return SASL_OK;
137}
138
139/*
140 * Close the database
141 */
142static void berkeleydb_close(const sasl_utils_t *utils, DB *mbdb)
143{
144    int ret;
145
146#if defined(KEEP_DB_OPEN)
147    /* Prevent other threads from reusing the same handle */
148    /* msdb == g_db */
149    g_db = NULL;
150#endif
151
152    ret = mbdb->close(mbdb, 0);
153    if (ret!=0) {
154	/* error closing! */
155	utils->log(NULL, SASL_LOG_ERR,
156		   "error closing sasldb: %s",
157		   db_strerror(ret));
158    }
159}
160
161
162/*
163 * Retrieve the secret from the database.
164 *
165 * Return SASL_NOUSER if the entry doesn't exist,
166 * SASL_OK on success.
167 *
168 */
169int _sasldb_getdata(const sasl_utils_t *utils,
170		    sasl_conn_t *context,
171		    const char *auth_identity,
172		    const char *realm,
173		    const char *propName,
174		    char *out, const size_t max_out, size_t *out_len)
175{
176  int result = SASL_OK;
177  char *key;
178  size_t key_len;
179  DBT dbkey, data;
180  DB *mbdb = NULL;
181
182  if(!utils) return SASL_BADPARAM;
183
184  /* check parameters */
185  if (!auth_identity || !realm || !propName || !out || !max_out) {
186      utils->seterror(context, 0,
187		      "Bad parameter in db_berkeley.c: _sasldb_getdata");
188      return SASL_BADPARAM;
189  }
190
191  if (!db_ok) {
192      utils->seterror(context, 0,
193		      "Database not checked");
194      return SASL_FAIL;
195  }
196
197  /* allocate a key */
198  result = _sasldb_alloc_key(utils, auth_identity, realm, propName,
199			     &key, &key_len);
200  if (result != SASL_OK) {
201      utils->seterror(context, 0,
202		      "Could not allocate key in _sasldb_getdata");
203      return result;
204  }
205
206  /* zero out */
207  memset(&dbkey, 0, sizeof(dbkey));
208  memset(&data, 0, sizeof(data));
209
210  /* open the db */
211  result = berkeleydb_open(utils, context, 0, &mbdb);
212  if (result != SASL_OK) goto cleanup;
213
214  /* create the key to search for */
215  dbkey.data = key;
216  dbkey.size = (u_int32_t) key_len;
217  dbkey.flags = DB_DBT_USERMEM;
218  data.flags = DB_DBT_MALLOC;
219
220  /* ask berkeley db for the entry */
221  result = mbdb->get(mbdb, NULL, &dbkey, &data, 0);
222
223  switch (result) {
224  case 0:
225    /* success */
226    break;
227
228  case DB_NOTFOUND:
229    result = SASL_NOUSER;
230    utils->seterror(context, SASL_NOLOG,
231		    "user: %s@%s property: %s not found in sasldb",
232		    auth_identity,realm,propName);
233    goto cleanup;
234    break;
235  default:
236    utils->seterror(context, 0,
237		    "error fetching from sasldb: %s",
238		    db_strerror(result));
239    result = SASL_FAIL;
240    goto cleanup;
241    break;
242  }
243
244  if(data.size > max_out + 1)
245      return SASL_BUFOVER;
246
247  if(out_len) *out_len = data.size;
248  memcpy(out, data.data, data.size);
249  out[data.size] = '\0';
250
251 cleanup:
252
253#if !defined(KEEP_DB_OPEN)
254  if (mbdb != NULL) berkeleydb_close(utils, mbdb);
255#endif
256
257  utils->free(key);
258  utils->free(data.data);
259
260  return result;
261}
262
263/*
264 * Put or delete an entry
265 *
266 *
267 */
268
269int _sasldb_putdata(const sasl_utils_t *utils,
270		    sasl_conn_t *context,
271		    const char *authid,
272		    const char *realm,
273		    const char *propName,
274		    const char *data_in, size_t data_len)
275{
276  int result = SASL_OK;
277  char *key;
278  size_t key_len;
279  DBT dbkey;
280  DB *mbdb = NULL;
281
282  if (!utils) return SASL_BADPARAM;
283
284  if (!authid || !realm || !propName) {
285      utils->seterror(context, 0,
286		      "Bad parameter in db_berkeley.c: _sasldb_putdata");
287      return SASL_BADPARAM;
288  }
289
290  if (!db_ok) {
291      utils->seterror(context, 0,
292		      "Database not checked");
293      return SASL_FAIL;
294  }
295
296  result = _sasldb_alloc_key(utils, authid, realm, propName,
297			     &key, &key_len);
298  if (result != SASL_OK) {
299       utils->seterror(context, 0,
300		      "Could not allocate key in _sasldb_putdata");
301       return result;
302  }
303
304  /* open the db */
305  result=berkeleydb_open(utils, context, 1, &mbdb);
306  if (result!=SASL_OK) goto cleanup;
307
308  /* create the db key */
309  memset(&dbkey, 0, sizeof(dbkey));
310  dbkey.data = key;
311  dbkey.size = (u_int32_t) key_len;
312
313  if (data_in) {   /* putting secret */
314    DBT data;
315
316    memset(&data, 0, sizeof(data));
317
318    data.data = (char *)data_in;
319    if(!data_len) data_len = strlen(data_in);
320    data.size = (u_int32_t) data_len;
321
322    result = mbdb->put(mbdb, NULL, &dbkey, &data, 0);
323
324    if (result != 0)
325    {
326      utils->log(NULL, SASL_LOG_ERR,
327		 "error updating sasldb: %s", db_strerror(result));
328      utils->seterror(context, SASL_NOLOG,
329		      "Couldn't update db");
330      result = SASL_FAIL;
331      goto cleanup;
332    }
333  } else {        /* removing secret */
334    result=mbdb->del(mbdb, NULL, &dbkey, 0);
335
336    if (result != 0)
337    {
338      utils->log(NULL, SASL_LOG_ERR,
339		 "error deleting entry from sasldb: %s", db_strerror(result));
340      utils->seterror(context, SASL_NOLOG,
341		      "Couldn't update db");
342      if (result == DB_NOTFOUND)
343	  result = SASL_NOUSER;
344      else
345	  result = SASL_FAIL;
346      goto cleanup;
347    }
348  }
349
350 cleanup:
351
352#if !defined(KEEP_DB_OPEN)
353  if (mbdb != NULL) berkeleydb_close(utils, mbdb);
354#endif
355
356  utils->free(key);
357
358  return result;
359}
360
361int _sasl_check_db(const sasl_utils_t *utils,
362		   sasl_conn_t *conn)
363{
364    const char *path = SASL_DB_PATH;
365    int ret;
366    void *cntxt;
367    sasl_getopt_t *getopt;
368    sasl_verifyfile_t *vf;
369
370    if (!utils) return SASL_BADPARAM;
371
372    if (utils->getcallback(conn, SASL_CB_GETOPT,
373			   (sasl_callback_ft *)&getopt, &cntxt) == SASL_OK) {
374	const char *p;
375	if (getopt(cntxt, NULL, "sasldb_path", &p, NULL) == SASL_OK
376	    && p != NULL && *p != 0) {
377	    path = p;
378	}
379    }
380
381    ret = utils->getcallback(conn, SASL_CB_VERIFYFILE,
382			     (sasl_callback_ft *)&vf, &cntxt);
383    if (ret != SASL_OK) {
384	utils->seterror(conn, 0, "verifyfile failed");
385	return ret;
386    }
387
388    ret = vf(cntxt, path, SASL_VRFY_PASSWD);
389
390    if (ret == SASL_OK) {
391	db_ok = 1;
392    }
393
394    if (ret == SASL_OK || ret == SASL_CONTINUE) {
395        return SASL_OK;
396    } else {
397	return ret;
398    }
399}
400
401#if defined(KEEP_DB_OPEN)
402void sasldb_auxprop_free (void *glob_context,
403                          const sasl_utils_t *utils)
404{
405    if (g_db != NULL) berkeleydb_close(utils, g_db);
406}
407#endif
408
409typedef struct berkeleydb_handle
410{
411    DB *mbdb;
412    DBC *cursor;
413} berkleyhandle_t;
414
415sasldb_handle _sasldb_getkeyhandle(const sasl_utils_t *utils,
416				   sasl_conn_t *conn)
417{
418    int ret;
419    DB *mbdb;
420    berkleyhandle_t *handle;
421
422    if(!utils || !conn) return NULL;
423
424    if(!db_ok) {
425	utils->seterror(conn, 0, "Database not OK in _sasldb_getkeyhandle");
426	return NULL;
427    }
428
429    ret = berkeleydb_open(utils, conn, 0, &mbdb);
430
431    if (ret != SASL_OK) {
432	return NULL;
433    }
434
435    handle = utils->malloc(sizeof(berkleyhandle_t));
436    if(!handle) {
437#if !defined(KEEP_DB_OPEN)
438	(void)mbdb->close(mbdb, 0);
439#endif
440	utils->seterror(conn, 0, "Memory error in _sasldb_gethandle");
441	return NULL;
442    }
443
444    handle->mbdb = mbdb;
445    handle->cursor = NULL;
446
447    return (sasldb_handle)handle;
448}
449
450int _sasldb_getnextkey(const sasl_utils_t *utils __attribute__((unused)),
451		       sasldb_handle handle, char *out,
452		       const size_t max_out, size_t *out_len)
453{
454    DB *mbdb;
455    int result;
456    berkleyhandle_t *dbh = (berkleyhandle_t *)handle;
457    DBT key, data;
458
459    if(!utils || !handle || !out || !max_out)
460	return SASL_BADPARAM;
461
462    mbdb = dbh->mbdb;
463
464    memset(&key,0, sizeof(key));
465    memset(&data,0,sizeof(data));
466
467    if(!dbh->cursor) {
468        /* make cursor */
469#if DB_VERSION_FULL < 0x03060000
470	result = mbdb->cursor(mbdb, NULL,&dbh->cursor);
471#else
472	result = mbdb->cursor(mbdb, NULL,&dbh->cursor, 0);
473#endif /* DB_VERSION_FULL < 0x03000000 */
474
475	if (result!=0) {
476	    return SASL_FAIL;
477	}
478
479	/* loop thru */
480	result = dbh->cursor->c_get(dbh->cursor, &key, &data,
481				    DB_FIRST);
482    } else {
483	result = dbh->cursor->c_get(dbh->cursor, &key, &data,
484				    DB_NEXT);
485    }
486
487    if (result == DB_NOTFOUND) return SASL_OK;
488
489    if (result != 0) {
490	return SASL_FAIL;
491    }
492
493    if (key.size > max_out) {
494	return SASL_BUFOVER;
495    }
496
497    memcpy(out, key.data, key.size);
498    if (out_len) *out_len = key.size;
499
500    return SASL_CONTINUE;
501}
502
503
504int _sasldb_releasekeyhandle(const sasl_utils_t *utils,
505			     sasldb_handle handle)
506{
507    berkleyhandle_t *dbh = (berkleyhandle_t *)handle;
508    int ret = 0;
509
510    if (!utils || !dbh) return SASL_BADPARAM;
511
512    if (dbh->cursor) {
513	dbh->cursor->c_close(dbh->cursor);
514    }
515
516#if !defined(KEEP_DB_OPEN)
517    /* This is almost the same berkeleydb_close(), except that
518       berkeleydb_close logs a message on error and does not return
519       any error */
520    if (dbh->mbdb) {
521	  ret = dbh->mbdb->close(dbh->mbdb, 0);
522    }
523#endif
524
525    utils->free(dbh);
526
527    if (ret) {
528	return SASL_FAIL;
529    } else {
530	return SASL_OK;
531    }
532}
533