#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_DEBUG, "%d bytes of client request", ccred.bv_len); res = ldap_sasl_bind_s (ctx->ldap, NULL, ctx->mechanism, &ccred, NULL, NULL, &scred); if (res == LDAP_SUCCESS || res == LDAP_SASL_BIND_IN_PROGRESS) { /* 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 { ret = SASL_CONTINUE; } /* Copy the response from the server into SASL allocated memory */ if (ret == SASL_OK || ret == SASL_CONTINUE) { if (scred) log_message (sparams->utils, SASL_LOG_DEBUG, "%d bytes of server response", scred->bv_len); /* Send back the response to the client */ 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; } else { log_message (sparams->utils, SASL_LOG_ERR, "couldn't allocate memory for server response"); ret = SASL_NOMEM; } /* No response from the server */ } else { *serverout = NULL; *serveroutlen = 0; /* We should always have a response when the dialog is going on */ if (ret == SASL_CONTINUE) { 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); } /* * In theory you're supposed to be able to return the context * which then can be reused for efficiency, but in reality * cyrus-sasl2 is too fragile and dumb for this, and does strange * double frees and stuff. */ *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; }