diff options
Diffstat (limited to 'plugin')
-rw-r--r-- | plugin/Makefile.am | 10 | ||||
-rw-r--r-- | plugin/delegateldap.c | 604 |
2 files changed, 614 insertions, 0 deletions
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 <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; +} |