From 3c75705512cafe50a9bd166e90b699e768f93160 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Mon, 2 Jun 2008 20:21:37 +0000 Subject: Initial import --- AUTHORS | 1 + COPYING | 31 +++ ChangeLog | 3 + Makefile.am | 5 + NEWS | 1 + README | 2 + autogen.sh | 11 + configure.in | 53 +++++ plugin/Makefile.am | 10 + plugin/delegateldap.c | 604 ++++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 721 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100755 autogen.sh create mode 100644 configure.in create mode 100644 plugin/Makefile.am create mode 100644 plugin/delegateldap.c diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..0f9215f --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Stefan Walter diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..2632857 --- /dev/null +++ b/COPYING @@ -0,0 +1,31 @@ + +Copyright (c) 2008, Stefan Walter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + * Redistributions in binary form must reproduce the + above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or + other materials provided with the distribution. + * The names of contributors to this software may not be + used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..1abe215 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,3 @@ +0.1: + * Initial release + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..52abc56 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = plugin + +dist-hook: + rm -rf `find $(distdir)/ -name .svn` + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..c7ab92a --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See ChangeLog \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..8ebfe92 --- /dev/null +++ b/README @@ -0,0 +1,2 @@ +This is a plugin module for Cyrus SASL. + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..100e70d --- /dev/null +++ b/autogen.sh @@ -0,0 +1,11 @@ +#!/bin/sh -ex + +set -ex + +aclocal +autoheader +libtoolize --force +automake -a +autoconf +./configure --enable-maintainer-mode "$@" + diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..4301946 --- /dev/null +++ b/configure.in @@ -0,0 +1,53 @@ +# Process this file with autoconf to produce a configure script. +AC_INIT(cyrus-distributed-sasl-plugin, 0.1, stef@memberwebs.com) +AM_INIT_AUTOMAKE(cyrus-distributed-sasl-plugin, 0.1) + +AC_CONFIG_SRCDIR([plugin/delegateldap.c]) +AM_CONFIG_HEADER([config.h]) + +LDFLAGS="$LDFLAGS -L/usr/local/lib" +CFLAGS="$CFLAGS -I/usr/local/include" + +# Checks for programs. +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LIBTOOL + +# Debug mode +AC_ARG_ENABLE(debug, + AC_HELP_STRING([--enable-debug], + [Compile binaries in debug mode])) + +if test "$enable_debug" = "yes"; then + CFLAGS="$CFLAGS -g -O0 -Wall" + AC_DEFINE_UNQUOTED(_DEBUG, 1, [In debug mode]) + echo "enabling debug compile mode" +fi + +# Checks for header files. +AC_HEADER_STDC + +AC_CHECK_LIB(ldap, ldap_initialize, , + [echo "Couldn't find the OpenLDAP library"; exit 1]) +AC_CHECK_LIB(lber, ber_bvfree, , + [echo "Couldn't find the OpenLDAP ber library"; exit 1]) + +AC_CHECK_LIB(sasl2, sasl_server_init, , + [echo "Couldn't find the Cyrus SASL 2 library"; exit 1]) +AC_CHECK_HEADERS([sasl/sasl.h sasl/saslplug.h], , + [echo "Couldn't find Cyrus SASL headers"; exit 1], + [#include ]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_FUNC_MEMCMP + +AC_CONFIG_FILES([ + Makefile + plugin/Makefile]) +AC_OUTPUT + diff --git a/plugin/Makefile.am b/plugin/Makefile.am new file mode 100644 index 0000000..3431b09 --- /dev/null +++ b/plugin/Makefile.am @@ -0,0 +1,10 @@ + +INCLUDES = -DCONF_PREFIX=\"$(sysconfdir)\" + +moduledir = $(prefix)/lib/sasl2/ +module_LTLIBRARIES = libdelegateldap.la + +libdelegateldap_la_LDFLAGS = -module -avoid-version +libdelegateldap_la_SOURCES = delegateldap.c + + diff --git a/plugin/delegateldap.c b/plugin/delegateldap.c new file mode 100644 index 0000000..170c6a7 --- /dev/null +++ b/plugin/delegateldap.c @@ -0,0 +1,604 @@ + +#include "config.h" + +#include +#include + +#define LDAP_DEPRECATED 1 +#include +#include + +#include +#include +#include +#include + +#define CANARY_VALUE "!DL!" + +#define OPTION_SERVER "delegateldap_uri" +#define OPTION_TLS "delegateldap_tls" +#define OPTION_LDAPRC "delegateldap_ldaprc" + +/* + * SETTINGS ------------------------------------------------------------------------- + * TODO: Move these to a configuration file or some such + */ + +#define SETTINGS_URL "ldap://209.66.100.155" + +typedef struct _supported_mechanisms { + void *mutex; + int loaded; + struct berval **mechs; +} supported_mechanisms; + +static supported_mechanisms *the_supported; + +typedef struct _delegate_context { + char canary[4]; + const char* mechanism; + LDAP *ldap; +} delegate_context; + +extern sasl_server_plug_init_t sasl_server_plug_init; + +static void +log_message (const sasl_utils_t *utils, int level, const char *format, ...) +{ + char buf[1024]; + va_list va; + + va_start (va, format); + vsnprintf (buf, sizeof (buf), format, va); + buf[sizeof (buf) - 1] = 0; + vfprintf (stderr, format, va); + fputc ('\n', stderr); + va_end (va); + + (utils->log) (utils->conn, level, "%s", buf); +} + +static int +report_ldap_error (const sasl_utils_t *utils, int ldap, const char *format, ...) +{ + char buf[1024]; + va_list va; + + buf[0] = 0; + + if (format) { + va_start (va, format); + vsnprintf (buf, sizeof (buf), format, va); + buf[sizeof (buf) - 1] = 0; + va_end (va); + } + + (utils->seterror) (utils->conn, 0, "%s%s%s", + buf, buf[0] ? ":" : "", ldap_err2string (ldap)); + fprintf (stderr, "%s%s%s", buf, buf[0] ? ": " : "", ldap_err2string (ldap)); + fputc ('\n', stderr); + + switch (ldap) { + case LDAP_MORE_RESULTS_TO_RETURN: + return SASL_CONTINUE; + case LDAP_SUCCESS: + return SASL_OK; + case LDAP_NO_MEMORY: + return SASL_NOMEM; + case LDAP_AUTH_UNKNOWN: + return SASL_NOMECH; + case LDAP_DECODING_ERROR: + return SASL_BADPROT; + } + + return SASL_FAIL; +} + +static void +delegateldap_dispose (void *context, const sasl_utils_t *utils) +{ + delegate_context *ctx = (delegate_context*)context; + + if (!ctx) + return; + + if (memcmp (ctx->canary, CANARY_VALUE, sizeof (ctx->canary)) != 0) { + log_message (utils, SASL_LOG_ERR, "trying to dispose of invalid delegateldap mechanism"); + return; + } + + if (ctx->ldap) { + log_message (utils, SASL_LOG_NOTE, "closing ldap connection"); + ldap_unbind_ext_s (ctx->ldap, NULL, NULL); + } + ctx->ldap = NULL; + memset (ctx, 0, sizeof (*ctx)); + + utils->free (ctx); +} + +static int +match_any_value (const char *match, ...) +{ + const char *value; + va_list va; + + va_start (va, match); + for (;;) { + value = va_arg (va, const char*); + if (!value) + return 0; + if (strcasecmp (match, value) == 0) + return 1; + } +} + +static int +delegateldap_new (void *mech_context, sasl_server_params_t *sparams, + const char *challenge, unsigned challen, void **context) +{ + const char *mechanism; + const char *option; + delegate_context *ctx; + int res, protocol, ret; + + *context = NULL; + + /* Make sure we have appropriate options */ + if ((sparams->utils->getopt) (sparams->utils->getopt_context, NULL, + OPTION_SERVER, &option, NULL) != SASL_OK) { + log_message (sparams->utils, SASL_LOG_ERR, + "missing '%s' option in config file", OPTION_SERVER); + return SASL_BADPARAM; + } + + ctx = sparams->utils->malloc (sizeof (delegate_context)); + if (!ctx) + return SASL_NOMEM; + memset (ctx, 0, sizeof (delegate_context)); + memcpy (ctx->canary, CANARY_VALUE, sizeof (ctx->canary)); + + /* We use the mech_context to boot strap the mechanism */ + mechanism = (const char*)mech_context; + + log_message (sparams->utils, SASL_LOG_NOTE, "initializing ldap connection to: %s", option); + + res = ldap_initialize (&ctx->ldap, option); + if (res != LDAP_SUCCESS) { + delegateldap_dispose (ctx, sparams->utils); + return report_ldap_error (sparams->utils, res, "couldn't initialize ldap connection: %s", option); + } + + protocol = LDAP_VERSION3; + if (ldap_set_option (ctx->ldap, LDAP_OPT_PROTOCOL_VERSION, &protocol) != LDAP_OPT_SUCCESS) { + delegateldap_dispose (ctx, sparams->utils); + log_message (sparams->utils, SASL_LOG_ERR, "couldn't setup ldap connection to use version 3 protocol"); + return SASL_FAIL; + } + + /* See if we should connect with TLS */ + (sparams->utils->getopt) (sparams->utils->getopt_context, NULL, OPTION_TLS, &option, NULL); + if (option && match_any_value (option, "yes", "y", "on", "try", "demand", "true", NULL)) { + + /* Start the TLS thingy */ + res = ldap_start_tls_s (ctx->ldap, NULL, NULL); + if (res != LDAP_SUCCESS) { + ret = report_ldap_error (sparams->utils, res, "couldn't initialize TLS on the ldap connection"); + + /* The configure requires that we have tls */ + if (match_any_value (option, "demand", NULL)) { + delegateldap_dispose (ctx, sparams->utils); + return ret; + } + } + } + + ctx->mechanism = mechanism; + *context = ctx; + + return SASL_OK; +} + +static int +calculate_user (delegate_context *ctx, sasl_server_params_t *sparams, + sasl_out_params_t *oparams) +{ + struct berval *auth; + char *dn; + char **parts; + int res, ret; + + res = ldap_whoami_s (ctx->ldap, &auth, NULL, NULL); + if (res != LDAP_SUCCESS) + return report_ldap_error (sparams->utils, res, "couldn't determine logged in user name"); + + /* Anonmyous authentication */ + if (!auth) { + log_message (sparams->utils, SASL_LOG_NOTE, "anonymous user login"); + ret = (sparams->canon_user) (sparams->utils->conn, "anonymous", 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); + + /* Use RDN as authenticator */ + } else { + dn = strchr (auth->bv_val, ':'); + if (dn) { + *dn = 0; + ++dn; + } else { + dn = auth->bv_val; + } + + parts = ldap_explode_dn (dn, 1); + if (!parts || !parts[0]) { + log_message (sparams->utils, SASL_LOG_ERR, + "couldn't parse the user's dn: %s", auth->bv_val); + ret = SASL_FAIL; + } else { + log_message (sparams->utils, SASL_LOG_NOTE, "user login: %s", parts[0]); + ret = sparams->canon_user (sparams->utils->conn, parts[0], 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); + } + + if (parts) + ldap_value_free (parts); + + ber_bvfree (auth); + } + + return ret; +} + +static int +delegateldap_step (void *context, sasl_server_params_t *sparams, + const char *clientin, unsigned clientinlen, + const char **serverout, unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + delegate_context *ctx = (delegate_context*)context; + struct berval *scred = NULL; + struct berval ccred; + char *buffer; + int ret; + int res; + + *serverout = NULL; + *serveroutlen = 0; + + ccred.bv_len = clientinlen; + ccred.bv_val = (char*)clientin; + + log_message (sparams->utils, SASL_LOG_NOTE, + "%d bytes of client request", ccred.bv_len); + + res = ldap_sasl_bind_s (ctx->ldap, NULL, ctx->mechanism, &ccred, NULL, NULL, &scred); + + /* Authentication was successful! */ + if (res == LDAP_SUCCESS) { + log_message (sparams->utils, SASL_LOG_NOTE, "successful authentication"); + ret = calculate_user (ctx, sparams, oparams); + + /* We need to keep shuttling information */ + } else if (res == LDAP_SASL_BIND_IN_PROGRESS) { + + ret = SASL_CONTINUE; + + /* Copy the response from the server into SASL allocated memory */ + if (scred && scred->bv_len) { + buffer = sparams->utils->malloc (scred->bv_len); + if (buffer) { + memcpy (buffer, scred->bv_val, scred->bv_len); + *serveroutlen = scred->bv_len; + *serverout = buffer; + + log_message (sparams->utils, SASL_LOG_NOTE, + "%d bytes of server response", scred->bv_len); + + } else { + log_message (sparams->utils, SASL_LOG_ERR, + "couldn't allocate memory for server response"); + ret = SASL_NOMEM; + } + + /* No response from the server, strange */ + } else { + *serverout = NULL; + *serveroutlen = 0; + + log_message (sparams->utils, SASL_LOG_WARN, + "no response from server during sasl step"); + } + + /* Bad login */ + } else if (res == LDAP_INVALID_CREDENTIALS){ + log_message (sparams->utils, SASL_LOG_FAIL, "user login failed: invalid credentials"); + ret = SASL_FAIL; + + /* An error condition */ + } else { + + ret = report_ldap_error (sparams->utils, res, "couldn't do sasl bind step"); + } + + if (scred) + ber_bvfree (scred); + + if (ret == SASL_OK) { + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + } + + return ret; +} + +static const char* +find_berval_value (const char *match, struct berval **values) +{ + struct berval **v; + size_t len; + + if (!values) + return NULL; + + len = strlen (match); + for (v = values; *v; ++v) { + if ((*v)->bv_len == len && + strncasecmp (match, (*v)->bv_val, len) == 0) { + return match; + } + } + + return NULL; +} + +static int +delegateldap_avail (void *mech_context, sasl_server_params_t *sparams, void **context) +{ + delegate_context *ctx = NULL; + LDAPMessage *message = NULL; + int ret = SASL_NOMECH; + + char *attrs[] = { "supportedSASLMechanisms", NULL }; + const char *mechanism; + LDAPMessage *entry; + struct berval **values; + int res, loaded; + + mechanism = (const char*)mech_context; + *context = NULL; + ret = SASL_NOMECH; + loaded = 0; + + /* Lock */ + if (the_supported->mutex) + (sparams->utils->mutex_lock) (the_supported->mutex); + + /* See if this mechanism is cached */ + loaded = the_supported->loaded; + if (loaded && find_berval_value (mechanism, the_supported->mechs)) + ret = SASL_OK; + + /* Unlock the globals */ + if (the_supported->mutex) + (sparams->utils->mutex_unlock) (the_supported->mutex); + + /* We have a cache, no need to go further */ + if (loaded) + goto cleanup; + + log_message (sparams->utils, SASL_LOG_NOTE, "loading mechanism info from server"); + + /* Create a new context, connect to the LDAP server */ + ret = delegateldap_new (mech_context, sparams, NULL, 0, context); + if (ret != SASL_OK) + goto cleanup; + + ctx = (delegate_context*)*context; + values = NULL; + + /* Query the server for supported mechanisms */ + res = ldap_search_ext_s (ctx->ldap, "", LDAP_SCOPE_BASE, NULL, attrs, 0, + NULL, NULL, LDAP_NO_LIMIT, LDAP_NO_LIMIT, &message); + + /* Couldn't find server info */ + if (res == LDAP_NO_SUCH_OBJECT) { + log_message (sparams->utils, SASL_LOG_WARN, "no base object with LDAP server info"); + + /* Some other failure */ + } else if (res != LDAP_SUCCESS) { + ret = report_ldap_error (sparams->utils, res, "couldn't search server for supported SASL mechanisms"); + goto cleanup; + + /* Found something */ + } else { + entry = ldap_first_entry (ctx->ldap, message); + if (entry == NULL) { + log_message (sparams->utils, SASL_LOG_WARN, "no base object with LDAP server info"); + } else { + values = ldap_get_values_len (ctx->ldap, entry, "supportedSASLMechanisms"); + if (values == NULL) + log_message (sparams->utils, SASL_LOG_WARN, "no supportedSASLMechanisms attribute on LDAP server info"); + } + } + + /* Lock the globals */ + if (the_supported->mutex) + (sparams->utils->mutex_lock) (the_supported->mutex); + + /* Swap in the mechanisms we just loaded */ + if (the_supported->mechs) + ldap_value_free_len (the_supported->mechs); + the_supported->mechs = values; + the_supported->loaded = 1; + + /* And search through them */ + if (find_berval_value (mechanism, the_supported->mechs)) + ret = SASL_OK; + + /* Unlock the globals */ + if (the_supported->mutex) + (sparams->utils->mutex_unlock) (the_supported->mutex); + +cleanup: + + if (message) + ldap_msgfree (message); + if (ret == SASL_OK) { + log_message (sparams->utils, SASL_LOG_DEBUG, + "server supports mechanism: %s", mechanism); + } else { + log_message (sparams->utils, SASL_LOG_FAIL, + "server doesn't support mechanism: %s", mechanism); + *context = NULL; + if (ctx) + delegateldap_dispose (ctx, sparams->utils); + } + + return ret; +} + +static void +delegateldap_free (void *mech_context, const sasl_utils_t *utils) +{ + void *mutex; + + if (!the_supported) + return; + + /* Lock globals */ + if (the_supported->mutex) + (utils->mutex_lock) (the_supported->mutex); + + /* Free mechanisms */ + if (the_supported->mechs) + ldap_value_free_len (the_supported->mechs); + the_supported->mechs = NULL; + the_supported->mechs = 0; + + /* And the main global context */ + mutex = the_supported->mutex; + (utils->free) (the_supported); + the_supported = NULL; + + /* Unlock globals */ + if (mutex) + (utils->mutex_unlock) (mutex); +} + +static sasl_server_plug_t delegateldap_server_plugins[] = +{ + { + "DIGEST-MD5", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY, /* features */ + "DIGEST-MD5", /* mech_context */ + &delegateldap_new, /* mech_new */ + &delegateldap_step, /* mech_step */ + &delegateldap_dispose, /* mech_dispose */ + &delegateldap_free, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + &delegateldap_avail, /* mech avail */ + NULL /* spare */ + }, + { + "CRAM-MD5", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS, /* security_flags */ + SASL_FEAT_SERVER_FIRST, /* features */ + "CRAM-MD5", /* mech_context */ + &delegateldap_new, /* mech_new */ + &delegateldap_step, /* mech_step */ + &delegateldap_dispose, /* mech_dispose */ + &delegateldap_free, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + &delegateldap_avail, /* mech avail */ + NULL /* spare */ + }, + { + "PLAIN", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOANONYMOUS, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY, /* features */ + "PLAIN", /* mech_context */ + &delegateldap_new, /* mech_new */ + &delegateldap_step, /* mech_step */ + &delegateldap_dispose, /* mech_dispose */ + &delegateldap_free, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + &delegateldap_avail, /* mech avail */ + NULL /* spare */ + }, + { + "LOGIN", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOANONYMOUS, /* security_flags */ + 0, /* features */ + "LOGIN", /* mech_context */ + &delegateldap_new, /* mech_new */ + &delegateldap_step, /* mech_step */ + &delegateldap_dispose, /* mech_dispose */ + &delegateldap_free, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + &delegateldap_avail, /* mech avail */ + NULL /* spare */ + } +}; + +extern int +sasl_server_plug_init (const sasl_utils_t *utils, int maxversion, int *out_version, + sasl_server_plug_t **pluglist, int *plugcount) +{ + const char *option; + + if (maxversion < SASL_SERVER_PLUG_VERSION) { + log_message (utils, SASL_LOG_ERR, "bad version: can't load delegateldap plugin"); + return SASL_BADVERS; + } + + log_message (utils, SASL_LOG_DEBUG, "loading delegateldap plugin"); + + if ((utils->getopt) (utils->getopt_context, NULL, + OPTION_SERVER, &option, NULL) != SASL_OK || !option) { + log_message (utils, SASL_LOG_ERR, + "missing '%s' option in config file", OPTION_SERVER); + return SASL_BADPARAM; + } + + /* From ldapdb plugin */ + if ((utils->getopt) (utils->getopt_context, NULL, + OPTION_LDAPRC, &option, NULL) == SASL_OK && option) { + setenv ("LDAPRC", option, 1); + } + + the_supported = (utils->malloc) (sizeof (supported_mechanisms)); + if (!the_supported) + return SASL_NOMEM; + memset (the_supported, 0, sizeof (supported_mechanisms)); + + if (utils->mutex_alloc) + the_supported->mutex = (utils->mutex_alloc) (); + + *out_version = SASL_SERVER_PLUG_VERSION; + *pluglist = delegateldap_server_plugins; + *plugcount = sizeof (delegateldap_server_plugins) / sizeof (delegateldap_server_plugins[0]); + + return SASL_OK; +} -- cgit v1.2.3