Howard Chu wrote:
Alexey Melnikov wrote:
On 09/10/2012 23:10, Howard Chu wrote:
Speaking of new releases, I'd like to see some feedback/movement on
these patches...
http://lists.andrew.cmu.edu/pipermail/cyrus-sasl/2012-March/002479.html
If you add/update makefiles, the process would be much quicker. (And if
you are not sure, ask).
http://lists.andrew.cmu.edu/pipermail/cyrus-sasl/2012-May/002490.html
This one is in GIT already.
Ah, I wasn't aware there was a git repo, I was still looking at CVS.
I'll sync up with git and provide Makefiles/etc for the MDB patch shortly.
Updated patch...
--
-- Howard Chu
CTO, Symas Corp. http://www.symas.com
Director, Highland Sun http://highlandsun.com/hyc/
Chief Architect, OpenLDAP http://www.openldap.org/project/
>From b106449b18200d3b176286575f76cbe5952e4f2a Mon Sep 17 00:00:00 2001
From: Howard Chu <hyc@xxxxxxxxx>
Date: Fri, 12 Oct 2012 11:11:31 -0700
Subject: [PATCH] Add support for OpenLDAP MDB
---
config/sasldb.m4 | 21 ++-
configure.in | 10 +-
doc/options.html | 26 +++
sasldb/Makefile.am | 2 +-
sasldb/db_mdb.c | 468 ++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 519 insertions(+), 8 deletions(-)
create mode 100644 sasldb/db_mdb.c
diff --git a/config/sasldb.m4 b/config/sasldb.m4
index b28b087..e3c9016 100644
--- a/config/sasldb.m4
+++ b/config/sasldb.m4
@@ -5,7 +5,9 @@ dnl Berkeley DB specific checks first..
dnl Figure out what database type we're using
AC_DEFUN([SASL_DB_CHECK], [
cmu_save_LIBS="$LIBS"
-AC_ARG_WITH(dblib, [ --with-dblib=DBLIB set the DB library to use [berkeley] ],
+AC_ARG_WITH(dblib,
+ [AC_HELP_STRING([--with-dblib={berkeley|gdbm|mdb|ndbm|none|auto_detect}],
+ [set the DB library to use [[berkeley]]])],
dblib=$withval,
dblib=auto_detect)
@@ -43,6 +45,11 @@ dnl named. arg.
fi
esac
;;
+ mdb)
+ AC_CHECK_HEADER(mdb.h, [
+ AC_CHECK_LIB(mdb, mdb_env_create, SASL_DB_LIB="-lmdb"; enable_keep_db_open=yes, dblib="no")],
+ dblib="no")
+ ;;
ndbm)
dnl We want to attempt to use -lndbm if we can, just in case
dnl there's some version of it installed and overriding libc
@@ -55,6 +62,12 @@ dnl named. arg.
dnl How about berkeley db?
CYRUS_BERKELEY_DB_CHK()
if test "$dblib" = no; then
+ dnl How about OpenLDAP's mdb?
+ AC_CHECK_HEADER(mdb.h, [
+ AC_CHECK_LIB(mdb, mdb_env_create, SASL_DB_LIB="-lmdb"; enable_keep_db_open=yes, dblib="no")],
+ dblib="no")
+ fi
+ if test "$dblib" = no; then
dnl How about ndbm?
AC_CHECK_HEADER(ndbm.h, [
AC_CHECK_LIB(ndbm, dbm_open,
@@ -86,7 +99,7 @@ dnl named. arg.
;;
*)
AC_MSG_WARN([Bad DB library implementation specified;])
- AC_ERROR([Use either \"berkeley\", \"gdbm\", \"ndbm\" or \"none\"])
+ AC_ERROR([Use either \"berkeley\", \"gdbm\", \"mdb\", \"ndbm\" or \"none\"])
dblib=no
;;
esac
@@ -106,6 +119,10 @@ case "$dblib" in
SASL_MECHS="$SASL_MECHS libsasldb.la"
AC_DEFINE(SASL_GDBM,[],[Use GDBM for SASLdb])
;;
+ mdb)
+ SASL_MECHS="$SASL_MECHS libsasldb.la"
+ AC_DEFINE(SASL_MDB,[],[Use MDB for SASLdb])
+ ;;
ndbm)
SASL_MECHS="$SASL_MECHS libsasldb.la"
AC_DEFINE(SASL_NDBM,[],[Use NDBM for SASLdb])
diff --git a/configure.in b/configure.in
index 465a362..5f75064 100644
--- a/configure.in
+++ b/configure.in
@@ -200,19 +200,19 @@ SASL_DB_CHECK()
# Do we not install the SASL DB man pages?
AM_CONDITIONAL(NO_SASL_DB_MANS, test "x$SASL_DB_MANS" = "x")
-AC_ARG_ENABLE(keep_db_open, [ --enable-keep-db-open keep handle to Berkeley DB open for improved performance [[no]] ],
+AC_ARG_ENABLE(keep_db_open, [ --enable-keep-db-open keep handle to DB open for improved performance [[no]] ],
keep_db_open=$enableval,
keep_db_open=no)
-# Disable if Berkeley DB is not used
-if test "$dblib" != berkeley; then
+# Disable if Berkeley DB and MDB are not used
+if test "$dblib" != berkeley -a "$dblib" != mdb; then
keep_db_open=no
fi
if test "$keep_db_open" = yes; then
- AC_DEFINE(KEEP_DB_OPEN,[],[Should we keep handle to Berkeley DB open in SASLDB plugin?])
+ AC_DEFINE(KEEP_DB_OPEN,[],[Should we keep handle to DB open in SASLDB plugin?])
fi
-AC_MSG_CHECKING(if Berkeley DB handle is kept open in SASLDB)
+AC_MSG_CHECKING(if DB handle is kept open in SASLDB)
AC_MSG_RESULT($keep_db_open)
AC_CHECK_LIB(dl, dlopen, SASL_DL_LIB="-ldl", SASL_DL_LIB="")
diff --git a/doc/options.html b/doc/options.html
index 4f195f7..66db5f6 100644
--- a/doc/options.html
+++ b/doc/options.html
@@ -148,6 +148,20 @@ cached for a fast reauth. A value of 0 will disable reauth.</TD>
<TR>
<TD>sasldb_path</TD><TD>sasldb plugin</TD>
<TD>Path to sasldb file</TD><TD><tt>/etc/sasldb2</tt> (system dependant)</TD>
+</TR>
+<TR>
+<TD>sasldb_mapsize</TD><TD>sasldb with MDB</TD>
+<TD>Size of the memory map used by the DB. This is also the maximum possible
+size of the database, so it must be set to a value large enough to contain
+all the desired user records.</TD>
+<TD>1048576 bytes</TD>
+</TR>
+<TR>
+<TD>sasldb_maxreaders</TD><TD>sasldb with MDB</TD>
+<TD>Maximum number of threads (or processes) that may concurrently read the
+database.</TD>
+<TD>126</TD>
+</TR>
<TR>
<TD>sql_engine</TD><TD>SQL plugin</TD>
<TD>Name of SQL engine to use (possible values: 'mysql', 'pgsql', 'sqlite', 'sqlite3').</TD>
@@ -342,6 +356,18 @@ sasl-regexp uid=(.*),cn=external,cn=auth
</p>
+<h2>Notes on sasldb with MDB</h2>
+
+<p>
+</p>
+
+<p>The OpenLDAP MDB library is an extremely compact, extremely high performance
+B+tree database. The code for it is available in the regular OpenLDAP source
+distributions and it is distributed under the terms of the OpenLDAP Public License.</p>
+
+<p>Full documentation, plus papers and presentations are available on
+<a href="http://highlandsun.com/hyc/mdb/">the MDB page</a>.</p>
+
<hr>
Back to the <A href=index.html>index</a>
diff --git a/sasldb/Makefile.am b/sasldb/Makefile.am
index 067477c..e1e3f56 100644
--- a/sasldb/Makefile.am
+++ b/sasldb/Makefile.am
@@ -46,7 +46,7 @@ sasl_version = 1:25:0
INCLUDES=-I$(top_srcdir)/include -I$(top_builddir)/include @SASL_DB_INC@
-extra_common_sources = db_none.c db_ndbm.c db_gdbm.c db_berkeley.c
+extra_common_sources = db_none.c db_mdb.c db_ndbm.c db_gdbm.c db_berkeley.c
EXTRA_DIST = NTMakefile
diff --git a/sasldb/db_mdb.c b/sasldb/db_mdb.c
new file mode 100644
index 0000000..c8e62c0
--- /dev/null
+++ b/sasldb/db_mdb.c
@@ -0,0 +1,468 @@
+/* db_mdb.c--SASL OpenLDAP MDB interface
+ * Howard Chu
+ * $Id$
+ */
+/*
+ * Copyright (C) 2011 Howard Chu, All rights reserved. <hyc@xxxxxxxxx>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include <config.h>
+
+#include <mdb.h>
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include "sasldb.h"
+
+static int db_ok = 0;
+static MDB_env *db_env;
+static MDB_dbi db_dbi;
+
+#define KILO 1024
+
+/*
+ * Open the environment
+ */
+static int do_open(const sasl_utils_t *utils,
+ sasl_conn_t *conn,
+ int rdwr, MDB_txn **mtxn)
+{
+ const char *path = SASL_DB_PATH;
+ void *cntxt;
+ MDB_env *env;
+ MDB_txn *txn;
+ sasl_getopt_t *getopt;
+ size_t mapsize = 0;
+ int readers = 0;
+ int ret;
+ int flags;
+
+ if (!db_env) {
+
+ if (utils->getcallback(conn, SASL_CB_GETOPT,
+ (sasl_callback_ft *)&getopt, &cntxt) == SASL_OK) {
+ const char *p;
+ if (getopt(cntxt, NULL, "sasldb_path", &p, NULL) == SASL_OK
+ && p != NULL && *p != 0) {
+ path = p;
+ }
+ if (getopt(cntxt, NULL, "sasldb_maxreaders", &p, NULL) == SASL_OK
+ && p != NULL && *p != 0) {
+ readers = atoi(p);
+ }
+ if (getopt(cntxt, NULL, "sasldb_mapsize", &p, NULL) == SASL_OK
+ && p != NULL && *p != 0) {
+ mapsize = atoi(p);
+ mapsize *= KILO;
+ }
+ }
+
+ ret = mdb_env_create(&env);
+ if (ret) {
+ utils->log(conn, SASL_LOG_ERR,
+ "unable to create MDB environment: %s",
+ mdb_strerror(ret));
+ utils->seterror(conn, SASL_NOLOG, "Unable to create MDB environment");
+ return SASL_FAIL;
+ }
+
+ if (readers) {
+ ret = mdb_env_set_maxreaders(env, readers);
+ if (ret) {
+ utils->log(conn, SASL_LOG_ERR,
+ "unable to set MDB maxreaders: %s",
+ mdb_strerror(ret));
+ utils->seterror(conn, SASL_NOLOG, "Unable to set MDB maxreaders");
+ return SASL_FAIL;
+ }
+ }
+
+ if (mapsize) {
+ ret = mdb_env_set_mapsize(env, mapsize);
+ if (ret) {
+ utils->log(conn, SASL_LOG_ERR,
+ "unable to set MDB mapsize: %s",
+ mdb_strerror(ret));
+ utils->seterror(conn, SASL_NOLOG, "Unable to set MDB mapsize");
+ return SASL_FAIL;
+ }
+ }
+
+ flags = MDB_NOSUBDIR;
+ if (!rdwr) flags |= MDB_RDONLY;
+ ret = mdb_env_open(env, path, flags, 0660);
+ if (ret) {
+ mdb_env_close(env);
+ if (!rdwr && ret == ENOENT) {
+ /* File not found and we are only reading the data.
+ Treat as SASL_NOUSER. */
+ return SASL_NOUSER;
+ }
+ utils->log(conn, SASL_LOG_ERR,
+ "unable to open MDB environment %s: %s",
+ path, mdb_strerror(ret));
+ utils->seterror(conn, SASL_NOLOG, "Unable to open MDB environment");
+ return SASL_FAIL;
+ }
+ } else {
+ env = db_env;
+ }
+
+ ret = mdb_txn_begin(env, NULL, rdwr ? 0 : MDB_RDONLY, &txn);
+ if (ret) {
+ mdb_env_close(env);
+ utils->log(conn, SASL_LOG_ERR,
+ "unable to open MDB transaction: %s",
+ mdb_strerror(ret));
+ utils->seterror(conn, SASL_NOLOG, "Unable to open MDB transaction");
+ return SASL_FAIL;
+ }
+
+ if (!db_dbi) {
+ ret = mdb_open(txn, NULL, 0, &db_dbi);
+ if (ret) {
+ mdb_txn_abort(txn);
+ mdb_env_close(env);
+ utils->log(conn, SASL_LOG_ERR,
+ "unable to open MDB database: %s",
+ mdb_strerror(ret));
+ utils->seterror(conn, SASL_NOLOG, "Unable to open MDB database");
+ return SASL_FAIL;
+ }
+ }
+
+ if (!db_env)
+ db_env = env;
+ *mtxn = txn;
+
+ return SASL_OK;
+}
+
+/*
+ * Close the environment
+ */
+static void do_close()
+{
+ mdb_env_close(db_env);
+ db_env = NULL;
+}
+
+
+/*
+ * Retrieve the secret from the database.
+ *
+ * Return SASL_NOUSER if the entry doesn't exist,
+ * SASL_OK on success.
+ *
+ */
+int _sasldb_getdata(const sasl_utils_t *utils,
+ sasl_conn_t *context,
+ const char *auth_identity,
+ const char *realm,
+ const char *propName,
+ char *out, const size_t max_out, size_t *out_len)
+{
+ int result = SASL_OK;
+ char *key;
+ size_t key_len;
+ MDB_val dbkey, data;
+ MDB_txn *txn = NULL;
+
+ if(!utils) return SASL_BADPARAM;
+
+ /* check parameters */
+ if (!auth_identity || !realm || !propName || !out || !max_out) {
+ utils->seterror(context, 0,
+ "Bad parameter in db_berkeley.c: _sasldb_getdata");
+ return SASL_BADPARAM;
+ }
+
+ if (!db_ok) {
+ utils->seterror(context, 0,
+ "Database not checked");
+ return SASL_FAIL;
+ }
+
+ /* allocate a key */
+ result = _sasldb_alloc_key(utils, auth_identity, realm, propName,
+ &key, &key_len);
+ if (result != SASL_OK) {
+ utils->seterror(context, 0,
+ "Could not allocate key in _sasldb_getdata");
+ return result;
+ }
+
+ /* open the db */
+ result = do_open(utils, context, 0, &txn);
+ if (result != SASL_OK) goto cleanup;
+
+ /* create the key to search for */
+ dbkey.mv_data = key;
+ dbkey.mv_size = key_len;
+
+ /* ask MDB for the entry */
+ result = mdb_get(txn, db_dbi, &dbkey, &data);
+
+ switch (result) {
+ case 0:
+ /* success */
+ break;
+
+ case MDB_NOTFOUND:
+ result = SASL_NOUSER;
+ utils->seterror(context, SASL_NOLOG,
+ "user: %s@%s property: %s not found in sasldb",
+ auth_identity,realm,propName);
+ goto cleanup;
+ break;
+ default:
+ utils->seterror(context, 0,
+ "error fetching from sasldb: %s",
+ mdb_strerror(result));
+ result = SASL_FAIL;
+ goto cleanup;
+ break;
+ }
+
+ if(data.mv_size > max_out + 1)
+ return SASL_BUFOVER;
+
+ if(out_len) *out_len = data.mv_size;
+ memcpy(out, data.mv_data, data.mv_size);
+ out[data.mv_size] = '\0';
+
+ cleanup:
+
+ mdb_txn_abort(txn);
+ utils->free(key);
+
+ return result;
+}
+
+/*
+ * Put or delete an entry
+ *
+ *
+ */
+
+int _sasldb_putdata(const sasl_utils_t *utils,
+ sasl_conn_t *context,
+ const char *authid,
+ const char *realm,
+ const char *propName,
+ const char *data_in, size_t data_len)
+{
+ int result = SASL_OK;
+ char *key;
+ size_t key_len;
+ MDB_val dbkey;
+ MDB_txn *txn = NULL;
+
+ if (!utils) return SASL_BADPARAM;
+
+ if (!authid || !realm || !propName) {
+ utils->seterror(context, 0,
+ "Bad parameter in db_berkeley.c: _sasldb_putdata");
+ return SASL_BADPARAM;
+ }
+
+ if (!db_ok) {
+ utils->seterror(context, 0,
+ "Database not checked");
+ return SASL_FAIL;
+ }
+
+ result = _sasldb_alloc_key(utils, authid, realm, propName,
+ &key, &key_len);
+ if (result != SASL_OK) {
+ utils->seterror(context, 0,
+ "Could not allocate key in _sasldb_putdata");
+ return result;
+ }
+
+ /* open the db */
+ result=do_open(utils, context, 1, &txn);
+ if (result!=SASL_OK) goto cleanup;
+
+ /* create the db key */
+ dbkey.mv_data = key;
+ dbkey.mv_size = key_len;
+
+ if (data_in) { /* putting secret */
+ MDB_val data;
+
+ data.mv_data = (char *)data_in;
+ if(!data_len) data_len = strlen(data_in);
+ data.mv_size = data_len;
+
+ result = mdb_put(txn, db_dbi, &dbkey, &data, 0);
+
+ if (result != 0)
+ {
+ utils->log(NULL, SASL_LOG_ERR,
+ "error updating sasldb: %s", mdb_strerror(result));
+ utils->seterror(context, SASL_NOLOG,
+ "Couldn't update db");
+ result = SASL_FAIL;
+ goto cleanup;
+ }
+ } else { /* removing secret */
+ result=mdb_del(txn, db_dbi, &dbkey, NULL);
+
+ if (result != 0)
+ {
+ utils->log(NULL, SASL_LOG_ERR,
+ "error deleting entry from sasldb: %s", mdb_strerror(result));
+ utils->seterror(context, SASL_NOLOG,
+ "Couldn't update db");
+ if (result == MDB_NOTFOUND)
+ result = SASL_NOUSER;
+ else
+ result = SASL_FAIL;
+ goto cleanup;
+ }
+ }
+ result = mdb_txn_commit(txn);
+ if (result) {
+ utils->log(NULL, SASL_LOG_ERR,
+ "error committing to sasldb: %s", mdb_strerror(result));
+ utils->seterror(context, SASL_NOLOG,
+ "Couldn't update db");
+ result = SASL_FAIL;
+ }
+ txn = NULL;
+
+ cleanup:
+
+ mdb_txn_abort(txn);
+ utils->free(key);
+
+ return result;
+}
+
+int _sasl_check_db(const sasl_utils_t *utils,
+ sasl_conn_t *conn)
+{
+ const char *path = SASL_DB_PATH;
+ int ret;
+ void *cntxt;
+ sasl_getopt_t *getopt;
+ sasl_verifyfile_t *vf;
+
+ if (!utils) return SASL_BADPARAM;
+
+ if (utils->getcallback(conn, SASL_CB_GETOPT,
+ (sasl_callback_ft *)&getopt, &cntxt) == SASL_OK) {
+ const char *p;
+ if (getopt(cntxt, NULL, "sasldb_path", &p, NULL) == SASL_OK
+ && p != NULL && *p != 0) {
+ path = p;
+ }
+ }
+
+ ret = utils->getcallback(conn, SASL_CB_VERIFYFILE,
+ (sasl_callback_ft *)&vf, &cntxt);
+ if (ret != SASL_OK) {
+ utils->seterror(conn, 0, "verifyfile failed");
+ return ret;
+ }
+
+ ret = vf(cntxt, path, SASL_VRFY_PASSWD);
+
+ if (ret == SASL_OK) {
+ db_ok = 1;
+ }
+
+ if (ret == SASL_OK || ret == SASL_CONTINUE) {
+ return SASL_OK;
+ } else {
+ return ret;
+ }
+}
+
+void sasldb_auxprop_free (void *glob_context,
+ const sasl_utils_t *utils)
+{
+ do_close();
+}
+
+sasldb_handle _sasldb_getkeyhandle(const sasl_utils_t *utils,
+ sasl_conn_t *conn)
+{
+ int ret;
+ MDB_txn *txn;
+ MDB_cursor *mc;
+
+ if(!utils || !conn) return NULL;
+
+ if(!db_ok) {
+ utils->seterror(conn, 0, "Database not OK in _sasldb_getkeyhandle");
+ return NULL;
+ }
+
+ ret = do_open(utils, conn, 0, &txn);
+
+ if (ret != SASL_OK) {
+ return NULL;
+ }
+
+ ret = mdb_cursor_open(txn, db_dbi, &mc);
+ if (ret) {
+ utils->seterror(conn, 0, "cursor_open failed in _sasldb_gekeythandle");
+ return NULL;
+ }
+
+ return (sasldb_handle)mc;
+}
+
+int _sasldb_getnextkey(const sasl_utils_t *utils __attribute__((unused)),
+ sasldb_handle handle, char *out,
+ const size_t max_out, size_t *out_len)
+{
+ int result;
+ MDB_cursor *mc = (MDB_cursor *)handle;
+ MDB_val key;
+
+ if(!utils || !handle || !out || !max_out)
+ return SASL_BADPARAM;
+
+ result = mdb_cursor_get(mc, &key, NULL, MDB_NEXT);
+
+ if (result == MDB_NOTFOUND) return SASL_OK;
+
+ if (result != 0) {
+ return SASL_FAIL;
+ }
+
+ if (key.mv_size > max_out) {
+ return SASL_BUFOVER;
+ }
+
+ memcpy(out, key.mv_data, key.mv_size);
+ if (out_len) *out_len = key.mv_size;
+
+ return SASL_CONTINUE;
+}
+
+
+int _sasldb_releasekeyhandle(const sasl_utils_t *utils,
+ sasldb_handle handle)
+{
+ MDB_cursor *mc = (MDB_cursor *)handle;
+
+ if (!utils || !handle) return SASL_BADPARAM;
+
+ mdb_cursor_close(mc);
+
+ return SASL_OK;
+}
--
1.7.9.5