summaryrefslogtreecommitdiff
path: root/plugin/delegateldap.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/delegateldap.c')
-rw-r--r--plugin/delegateldap.c604
1 files changed, 604 insertions, 0 deletions
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 <sasl/sasl.h>
+#include <sasl/saslplug.h>
+
+#define LDAP_DEPRECATED 1
+#include <ldap.h>
+#include <lber.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#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;
+}