From: Arran Cudbard-Bell Date: Sun, 14 Jun 2015 02:06:42 +0000 (-0400) Subject: Backport SASL interactive bind (non interactive didn't work anyway...) Fixes #991 X-Git-Tag: release_3_0_9~182 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1204e031024fa26e1453de136e9a1010a0df02d8;p=thirdparty%2Ffreeradius-server.git Backport SASL interactive bind (non interactive didn't work anyway...) Fixes #991 --- diff --git a/raddb/mods-available/ldap b/raddb/mods-available/ldap index f30382d309f..71670643546 100644 --- a/raddb/mods-available/ldap +++ b/raddb/mods-available/ldap @@ -31,9 +31,21 @@ ldap { # searches will start from. base_dn = 'dc=example,dc=org' - # SASL mechanism to use for administrative binds. - # Uncomment for certificate auth or peercred auth (ldapi:// only). -# sasl_mech = 'EXTERNAL' + # SASL parameters to use for admin binds + # + # When we're prompted by the SASL library, these control + # the responses given. + # + sasl { + # SASL mechanism +# mech = 'PLAIN' + + # SASL authorisation identity to proxy. +# proxy = 'autz_id' + + # SASL realm. Used for kerberos. +# realm = 'example.org' + } # # Generic valuepair attribute @@ -128,8 +140,24 @@ ldap { # to identify a single user object. filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})" - # SASL mechanism to use for user binds. -# sasl_mech = 'PLAIN' + # SASL parameters to use for user binds + # + # When we're prompted by the SASL library, these control + # the responses given. + # + # Any of the config items below may be an attribute ref + # or and expansion, so different SASL mechs, proxy IDs + # and realms may be used for different users. + sasl { + # SASL mechanism +# mech = 'PLAIN' + + # SASL authorisation identity to proxy. +# proxy = &User-Name + + # SASL realm. Used for kerberos. +# realm = 'example.org' + } # Search scope, may be 'base', 'one', sub' or 'children' # scope = 'sub' diff --git a/src/modules/rlm_ldap/clients.c b/src/modules/rlm_ldap/clients.c index 4566fbe581b..5b9d88273c2 100644 --- a/src/modules/rlm_ldap/clients.c +++ b/src/modules/rlm_ldap/clients.c @@ -136,8 +136,8 @@ int rlm_ldap_client_load(rlm_ldap_t const *inst, CONF_SECTION *tmpl, CONF_SECTIO * Perform all searches as the admin user. */ if (conn->rebound) { - status = rlm_ldap_bind(inst, NULL, &conn, conn->inst->admin_dn, conn->inst->password, - conn->inst->admin_sasl_mech, true); + status = rlm_ldap_bind(inst, NULL, &conn, conn->inst->admin_identity, conn->inst->admin_password, + &(conn->inst->admin_sasl), true); if (status != LDAP_PROC_SUCCESS) { ret = -1; goto finish; diff --git a/src/modules/rlm_ldap/config.h.in b/src/modules/rlm_ldap/config.h.in index a9c61ac2f17..12fadca2382 100644 --- a/src/modules/rlm_ldap/config.h.in +++ b/src/modules/rlm_ldap/config.h.in @@ -12,8 +12,8 @@ /* Define to 1 if you have the `ldap_initialize' function. */ #undef HAVE_LDAP_INITIALIZE -/* Define to 1 if you have the `ldap_sasl_bind' function. */ -#undef HAVE_LDAP_SASL_BIND +/* Define to 1 if you have the `ldap_sasl_interactive_bind' function. */ +#undef HAVE_LDAP_SASL_INTERACTIVE_BIND /* Define to 1 if you have the `ldap_set_rebind_proc' function. */ #undef HAVE_LDAP_SET_REBIND_PROC diff --git a/src/modules/rlm_ldap/configure b/src/modules/rlm_ldap/configure index e23adcf1684..7a6cf23eb31 100755 --- a/src/modules/rlm_ldap/configure +++ b/src/modules/rlm_ldap/configure @@ -3097,7 +3097,7 @@ smart_prefix= if test "x$fail" = "x"; then - for ac_func in ldap_sasl_bind \ + for ac_func in ldap_sasl_interactive_bind \ ldap_unbind_ext_s \ ldap_start_tls_s \ ldap_initialize \ diff --git a/src/modules/rlm_ldap/configure.ac b/src/modules/rlm_ldap/configure.ac index 86953e82d87..8bdcf48d171 100644 --- a/src/modules/rlm_ldap/configure.ac +++ b/src/modules/rlm_ldap/configure.ac @@ -87,7 +87,7 @@ if test x$with_[]modname != xno; then if test "x$fail" = "x"; then AC_CHECK_FUNCS( - ldap_sasl_bind \ + ldap_sasl_interactive_bind \ ldap_unbind_ext_s \ ldap_start_tls_s \ ldap_initialize \ diff --git a/src/modules/rlm_ldap/ldap.c b/src/modules/rlm_ldap/ldap.c index 8c495c0f06a..416a1226a02 100644 --- a/src/modules/rlm_ldap/ldap.c +++ b/src/modules/rlm_ldap/ldap.c @@ -451,8 +451,8 @@ char const *rlm_ldap_error_str(ldap_handle_t const *conn) * (with talloc_free). * @return One of the LDAP_PROC_* (#ldap_rcode_t) values. */ -static ldap_rcode_t rlm_ldap_result(rlm_ldap_t const *inst, ldap_handle_t const *conn, int msgid, char const *dn, - LDAPMessage **result, char const **error, char **extra) +ldap_rcode_t rlm_ldap_result(rlm_ldap_t const *inst, ldap_handle_t const *conn, int msgid, char const *dn, + LDAPMessage **result, char const **error, char **extra) { ldap_rcode_t status = LDAP_PROC_SUCCESS; @@ -545,6 +545,11 @@ process_error: *error = "Success"; break; + case LDAP_SASL_BIND_IN_PROGRESS: + *error = "Continuing"; + status = LDAP_PROC_CONTINUE; + break; + case LDAP_NO_SUCH_OBJECT: *error = "The specified DN wasn't found"; status = LDAP_PROC_BAD_DN; @@ -660,7 +665,7 @@ process_error: talloc_free(our_err); - if ((lib_errno || srv_errno) && *result) { + if ((status < 0) && *result) { ldap_msgfree(*result); *result = NULL; } @@ -677,25 +682,29 @@ process_error: * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @param[in] dn of the user, may be NULL to bind anonymously. * @param[in] password of the user, may be NULL if no password is specified. - * @param[in] sasl_mech SASL mechanism to use for bind. + * @param[in] sasl mechanism to use for bind, and additional parameters. * @param[in] retry if the server is down. - * @return one of the LDAP_PROC_* values. + * @return One of the LDAP_PROC_* (#ldap_rcode_t) values. */ ldap_rcode_t rlm_ldap_bind(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn, - char const *password, char const *sasl_mech, bool retry) + char const *password, ldap_sasl *sasl, bool retry) { - ldap_rcode_t status = LDAP_PROC_ERROR; + ldap_rcode_t status = LDAP_PROC_ERROR; - int msgid = -1; + int msgid = -1; - char const *error = NULL; - char *extra = NULL; + char const *error = NULL; + char *extra = NULL; - int i, num; + int i, num; rad_assert(*pconn && (*pconn)->handle); rad_assert(!retry || inst->pool); +#ifndef HAVE_LDAP_SASL_INTERACTIVE_BIND + rad_assert(!sasl->mech); +#endif + /* * Bind as anonymous user */ @@ -707,31 +716,26 @@ ldap_rcode_t rlm_ldap_bind(rlm_ldap_t const *inst, REQUEST *request, ldap_handle */ num = retry ? fr_connection_get_num(inst->pool) : 0; for (i = num; i >= 0; i--) { -#ifdef HAVE_LDAP_SASL_BIND - if (sasl_mech) { - struct berval cred; - - if (password) { - memcpy(&cred.bv_val, &password, sizeof(cred.bv_val)); - cred.bv_len = talloc_array_length(password) - 1; - } else { - memset(&cred, 0, sizeof(cred)); - } - ldap_sasl_bind((*pconn)->handle, dn, sasl_mech, &cred, NULL, NULL, &msgid); +#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND + if (sasl->mech) { + status = rlm_ldap_sasl_interactive(inst, request, *pconn, dn, password, sasl, + &error, &extra); } else #endif - msgid = ldap_bind((*pconn)->handle, dn, password, LDAP_AUTH_SIMPLE); - - /* We got a valid message ID */ - if (msgid >= 0) { - if (request) { - RDEBUG2("Waiting for bind result..."); - } else { - DEBUG2("rlm_ldap (%s): Waiting for bind result...", inst->name); + { + msgid = ldap_bind((*pconn)->handle, dn, password, LDAP_AUTH_SIMPLE); + + /* We got a valid message ID */ + if (msgid >= 0) { + if (request) { + RDEBUG2("Waiting for bind result..."); + } else { + DEBUG2("rlm_ldap (%s): Waiting for bind result...", inst->name); + } } - } - status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra); + status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra); + } switch (status) { case LDAP_PROC_SUCCESS: @@ -840,8 +844,8 @@ ldap_rcode_t rlm_ldap_search(LDAPMessage **result, rlm_ldap_t const *inst, REQUE * Do all searches as the admin user. */ if ((*pconn)->rebound) { - status = rlm_ldap_bind(inst, request, pconn, (*pconn)->inst->admin_dn, (*pconn)->inst->password, - (*pconn)->inst->admin_sasl_mech, true); + status = rlm_ldap_bind(inst, request, pconn, (*pconn)->inst->admin_identity, + (*pconn)->inst->admin_password, &(*pconn)->inst->admin_sasl, true); if (status != LDAP_PROC_SUCCESS) { return LDAP_PROC_ERROR; } @@ -984,8 +988,8 @@ ldap_rcode_t rlm_ldap_modify(rlm_ldap_t const *inst, REQUEST *request, ldap_hand * Perform all modifications as the admin user. */ if ((*pconn)->rebound) { - status = rlm_ldap_bind(inst, request, pconn, (*pconn)->inst->admin_dn, (*pconn)->inst->password, - (*pconn)->inst->admin_sasl_mech, true); + status = rlm_ldap_bind(inst, request, pconn, (*pconn)->inst->admin_identity, + (*pconn)->inst->admin_password, &(*pconn)->inst->admin_sasl, true); if (status != LDAP_PROC_SUCCESS) { return LDAP_PROC_ERROR; } @@ -1109,8 +1113,8 @@ char const *rlm_ldap_find_user(rlm_ldap_t const *inst, REQUEST *request, ldap_ha * Perform all searches as the admin user. */ if ((*pconn)->rebound) { - status = rlm_ldap_bind(inst, request, pconn, (*pconn)->inst->admin_dn, (*pconn)->inst->password, - (*pconn)->inst->admin_sasl_mech, true); + status = rlm_ldap_bind(inst, request, pconn, (*pconn)->inst->admin_identity, + (*pconn)->inst->admin_password, &(*pconn)->inst->admin_sasl, true); if (status != LDAP_PROC_SUCCESS) { *rcode = RLM_MODULE_FAIL; return NULL; @@ -1318,8 +1322,8 @@ static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t DEBUG("rlm_ldap (%s): Rebinding to URL %s", conn->inst->name, url); - status = rlm_ldap_bind(conn->inst, NULL, &conn, conn->inst->admin_dn, conn->inst->password, - conn->inst->admin_sasl_mech, false); + status = rlm_ldap_bind(conn->inst, NULL, &conn, conn->inst->admin_identity, conn->inst->admin_password, + &(conn->inst->admin_sasl), false); if (status != LDAP_PROC_SUCCESS) { ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); @@ -1528,8 +1532,8 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance) } #endif /* HAVE_LDAP_START_TLS_S */ - status = rlm_ldap_bind(inst, NULL, &conn, conn->inst->admin_dn, conn->inst->password, - conn->inst->admin_sasl_mech, false); + status = rlm_ldap_bind(inst, NULL, &conn, conn->inst->admin_identity, conn->inst->admin_password, + &(conn->inst->admin_sasl), false); if (status != LDAP_PROC_SUCCESS) { goto error; } diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h index 8f949632760..bcd021926a3 100644 --- a/src/modules/rlm_ldap/ldap.h +++ b/src/modules/rlm_ldap/ldap.h @@ -67,6 +67,8 @@ # define LDAP_CONST #endif +#define MOD_PREFIX "rlm_ldap" //!< The name of the module. + #define LDAP_MAX_ATTRMAP 128 //!< Maximum number of mappings between LDAP and //!< FreeRADIUS attributes. #define LDAP_MAP_RESERVED 4 //!< Number of additional items to allocate in expanded @@ -81,27 +83,41 @@ #define LDAP_MAX_GROUP_NAME_LEN 128 //!< Maximum name of a group name. #define LDAP_MAX_ATTR_STR_LEN 256 //!< Maximum length of an xlat expanded LDAP attribute. #define LDAP_MAX_FILTER_STR_LEN 1024 //!< Maximum length of an xlat expanded filter. -#define LDAP_MAX_DN_STR_LEN 2048 //!< Maximum length of an xlat expanded DN. +#define LDAP_MAX_DN_STR_LEN 1024 //!< Maximum length of an xlat expanded DN. + +#define LDAP_VIRTUAL_DN_ATTR "dn" //!< 'Virtual' attribute which maps to the DN of the object. typedef struct ldap_acct_section { CONF_SECTION *cs; //!< Section configuration. - char const *reference; //!< Configuration reference string. + char const *reference; //!< Configuration reference string. } ldap_acct_section_t; +typedef struct ldap_sasl { + char const *mech; //!< SASL mech(s) to try. + char const *proxy; //!< Identity to proxy. + char const *realm; //!< Kerberos realm. +} ldap_sasl; + +typedef struct ldap_sasl_dynamic { + vp_tmpl_t *mech; //!< SASL mech(s) to try. + vp_tmpl_t *proxy; //!< Identity to proxy. + vp_tmpl_t *realm; //!< Kerberos realm. +} ldap_sasl_dynamic; + typedef struct ldap_instance { CONF_SECTION *cs; //!< Main configuration section for this instance. fr_connection_pool_t *pool; //!< Connection pool instance. - char const *config_server; //!< server from the config files + char const *config_server; //!< Server set in the config. char *server; //!< Initial server to bind to. uint16_t port; //!< Port to use when binding to the server. - char const *admin_dn; //!< DN we bind as when we need to query the LDAP + char const *admin_identity; //!< Identity we bind as when we need to query the LDAP //!< directory. - char const *password; //!< Password used in administrative bind. + char const *admin_password; //!< Password used in administrative bind. - char const *admin_sasl_mech; //!< SASL mechanism to use for administrative binds. + ldap_sasl admin_sasl; //!< SASL parameters used when binding as the admin. char const *dereference_str; //!< When to dereference (never, searching, finding, always) int dereference; //!< libldap value specifying dereferencing behaviour. @@ -132,7 +148,6 @@ typedef struct ldap_instance { /* * User object attributes and filters */ - char const *user_sasl_mech; //!< SASL mechanism to use for user binds. vp_tmpl_t *userobj_filter; //!< Filter to retrieve only user objects. vp_tmpl_t *userobj_base_dn; //!< DN to search for users under. char const *userobj_scope_str; //!< Scope (sub, one, base). @@ -149,10 +164,11 @@ typedef struct ldap_instance { char const *valuepair_attr; //!< Generic dynamic mapping attribute, contains a RADIUS //!< attribute and value. + ldap_sasl_dynamic user_sasl; //!< SASL parameters used when binding as the user. + /* * Group object attributes and filters */ - char const *groupobj_filter; //!< Filter to retrieve only group objects. vp_tmpl_t *groupobj_base_dn; //!< DN to search for users under. char const *groupobj_scope_str; //!< Scope (sub, one, base). @@ -309,6 +325,7 @@ typedef struct rlm_ldap_result { * */ typedef enum { + LDAP_PROC_CONTINUE = 1, //!< Operation is in progress. LDAP_PROC_SUCCESS = 0, //!< Operation was successfull. LDAP_PROC_ERROR = -1, //!< Unrecoverable library/server error. @@ -368,7 +385,7 @@ size_t rlm_ldap_normalise_dn(char *out, char const *in); ssize_t rlm_ldap_xlat_filter(REQUEST *request, char const **sub, size_t sublen, char *out, size_t outlen); ldap_rcode_t rlm_ldap_bind(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn, - char const *password, char const *sasl_mech, bool retry); + char const *password, ldap_sasl *sasl, bool retry); char const *rlm_ldap_error_str(ldap_handle_t const *conn); @@ -391,6 +408,9 @@ void rlm_ldap_check_reply(rlm_ldap_t const *inst, REQUEST *request); /* * ldap.c - Callbacks for the connection pool API. */ +ldap_rcode_t rlm_ldap_result(rlm_ldap_t const *inst, ldap_handle_t const *conn, int msgid, char const *dn, + LDAPMessage **result, char const **error, char **extra); + char *rlm_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in); void *mod_conn_create(TALLOC_CTX *ctx, void *instance); @@ -439,4 +459,11 @@ int nmasldap_get_password(LDAP *ld, char const *dn, char *password, size_t *len) char const *edir_errstr(int code); +/* + * sasl.s - SASL bind functions + */ +ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request, + ldap_handle_t *pconn, char const *dn, + char const *password, ldap_sasl *sasl, + char const **error, char **extra); #endif diff --git a/src/modules/rlm_ldap/rlm_ldap.c b/src/modules/rlm_ldap/rlm_ldap.c index b5757a97734..d62de35af51 100644 --- a/src/modules/rlm_ldap/rlm_ldap.c +++ b/src/modules/rlm_ldap/rlm_ldap.c @@ -36,6 +36,8 @@ RCSID("$Id$") #include "ldap.h" +#include + /* * Scopes */ @@ -70,6 +72,18 @@ static FR_NAME_NUMBER const ldap_dereference[] = { { NULL , -1 } }; +static CONF_PARSER sasl_mech_dynamic[] = { + { "mech", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL | PW_TYPE_NOT_EMPTY, ldap_sasl_dynamic, mech), NULL }, + { "proxy", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, ldap_sasl_dynamic, proxy), NULL }, + { "realm", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, ldap_sasl_dynamic, realm), NULL } +}; + +static CONF_PARSER sasl_mech_static[] = { + { "mech", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, ldap_sasl, mech), NULL }, + { "proxy", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_sasl, proxy), NULL }, + { "realm", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_sasl, realm), NULL } +}; + /* * TLS Configuration */ @@ -122,7 +136,8 @@ static CONF_PARSER user_config[] = { { "access_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, userobj_access_attr), NULL }, { "access_positive", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ldap_t, access_positive), "yes" }, - { "sasl_mech", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, user_sasl_mech), NULL }, + /* Should be deprecated */ + { "sasl", FR_CONF_OFFSET(PW_TYPE_SUBSECTION, rlm_ldap_t, user_sasl), (void const *) sasl_mech_dynamic }, { NULL, -1, 0, NULL, NULL } }; @@ -208,9 +223,11 @@ static const CONF_PARSER module_config[] = { { "server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, config_server), NULL }, /* Do not set to required */ { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, rlm_ldap_t, port), NULL }, - { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_ldap_t, password), NULL }, - { "identity", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, admin_dn), NULL }, - { "sasl_mech", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, admin_sasl_mech), NULL }, + { "identity", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, admin_identity), NULL }, + { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_ldap_t, admin_password), NULL }, + + /* Should be deprecated */ + { "sasl", FR_CONF_OFFSET(PW_TYPE_SUBSECTION, rlm_ldap_t, admin_sasl), (void const *) sasl_mech_static }, { "valuepair_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_ldap_t, valuepair_attr), NULL }, @@ -727,16 +744,16 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) } } -#ifndef HAVE_LDAP_SASL_BIND - if (inst->user_sasl_mech) { - cf_log_err_cs(conf, "Configuration item 'user.sasl_mech' not supported. " +#ifndef HAVE_LDAP_SASL_INTERACTIVE_BIND + if (inst->user_sasl.mech) { + cf_log_err_cs(conf, "Configuration item 'user.sasl.mech' not supported. " "Linked libldap does not provide ldap_sasl_bind function"); goto error; } - if (inst->admin_sasl_mech) { - cf_log_err_cs(conf, "Configuration item 'sasl_mech' not supported. " - "Linked libldap does not provide ldap_sasl_bind function"); + if (inst->admin_sasl.mech) { + cf_log_err_cs(conf, "Configuration item 'sasl.mech' not supported. " + "Linked libldap does not provide ldap_sasl_interactive_bind function"); goto error; } #endif @@ -765,7 +782,10 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) bool first = true; cp = cf_pair_find(conf, "server"); - rad_assert(cp != NULL); + if (!cp) { + cf_log_err_cs(conf, "Configuration item 'server' must have a value"); + return -1; + } value = cf_pair_value(cp); @@ -1132,6 +1152,11 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *re rlm_ldap_t *inst = instance; ldap_handle_t *conn; + char sasl_mech_buff[LDAP_MAX_DN_STR_LEN]; + char sasl_proxy_buff[LDAP_MAX_DN_STR_LEN]; + char sasl_realm_buff[LDAP_MAX_DN_STR_LEN]; + ldap_sasl sasl; + /* * Ensure that we're being passed a plain-text password, and not * anything else. @@ -1162,11 +1187,43 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *re return RLM_MODULE_INVALID; } - RDEBUG("Login attempt by \"%s\"", request->username->vp_strvalue); - conn = mod_conn_get(inst, request); if (!conn) return RLM_MODULE_FAIL; + /* + * Expand dynamic SASL fields + */ + if (conn->inst->user_sasl.mech) { + memset(&sasl, 0, sizeof(sasl)); + + if (tmpl_expand(&sasl.mech, sasl_mech_buff, sizeof(sasl_mech_buff), request, + conn->inst->user_sasl.mech, rlm_ldap_escape_func, inst) < 0) { + REDEBUG("Failed expanding user.sasl.mech: %s", fr_strerror()); + rcode = RLM_MODULE_FAIL; + goto finish; + } + + if (conn->inst->user_sasl.proxy) { + if (tmpl_expand(&sasl.proxy, sasl_proxy_buff, sizeof(sasl_proxy_buff), request, + conn->inst->user_sasl.proxy, rlm_ldap_escape_func, inst) < 0) { + REDEBUG("Failed expanding user.sasl.proxy: %s", fr_strerror()); + rcode = RLM_MODULE_FAIL; + goto finish; + } + } + + if (conn->inst->user_sasl.realm) { + if (tmpl_expand(&sasl.realm, sasl_realm_buff, sizeof(sasl_realm_buff), request, + conn->inst->user_sasl.realm, rlm_ldap_escape_func, inst) < 0) { + REDEBUG("Failed expanding user.sasl.realm: %s", fr_strerror()); + rcode = RLM_MODULE_FAIL; + goto finish; + } + } + } + + RDEBUG("Login attempt by \"%s\"", request->username->vp_strvalue); + /* * Get the DN by doing a search. */ @@ -1176,13 +1233,9 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *re return rcode; } - - /* - * Bind as the user - */ conn->rebound = true; status = rlm_ldap_bind(inst, request, &conn, dn, request->password->vp_strvalue, - conn->inst->user_sasl_mech, true); + conn->inst->user_sasl.mech ? &sasl : NULL, true); switch (status) { case LDAP_PROC_SUCCESS: rcode = RLM_MODULE_OK; @@ -1210,6 +1263,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *re break; }; +finish: mod_conn_release(inst, conn); return rcode; @@ -1421,8 +1475,7 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) * Bind as the user */ conn->rebound = true; - status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, - conn->inst->user_sasl_mech, true); + status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, NULL, true); switch (status) { case LDAP_PROC_SUCCESS: rcode = RLM_MODULE_OK; diff --git a/src/modules/rlm_ldap/sasl.c b/src/modules/rlm_ldap/sasl.c new file mode 100644 index 00000000000..0e58bba4341 --- /dev/null +++ b/src/modules/rlm_ldap/sasl.c @@ -0,0 +1,131 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "ldap.h" + +#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND +/** + * $Id$ + * @file sasl.c + * @brief Functions to perform SASL binds against an LDAP directory. + * + * @author Arran Cudbard-Bell + * @copyright 2015 Arran Cudbard-Bell + * @copyright 2015 The FreeRADIUS Server Project. + */ +#include +#include + +#include + +typedef struct rlm_ldap_sasl_ctx { + rlm_ldap_t const *inst; //!< LDAP instance + REQUEST *request; //!< The current request. + + char const *identity; //!< User's DN or identity. + char const *password; //!< Bind password. + + ldap_sasl *extra; //!< Extra fields (realm and proxy id). +} rlm_ldap_sasl_ctx_t; + +static int _sasl_interact(UNUSED LDAP *handle, UNUSED unsigned flags, void *ctx, void *sasl_callbacks) +{ + rlm_ldap_sasl_ctx_t *this = ctx; + REQUEST *request = this->request; + rlm_ldap_t const *inst = this->inst; + sasl_interact_t *cb = sasl_callbacks; + sasl_interact_t *cb_p; + + for (cb_p = cb; cb_p->id != SASL_CB_LIST_END; cb_p++) { + MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL challenge : %s", cb_p->challenge); + MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL prompt : %s", cb_p->prompt); + + switch (cb_p->id) { + case SASL_CB_AUTHNAME: + cb_p->result = this->identity; + break; + + case SASL_CB_PASS: + cb_p->result = this->password; + break; + + case SASL_CB_USER: + cb_p->result = this->extra->proxy ? this->extra->proxy : this->identity; + break; + + case SASL_CB_GETREALM: + if (this->extra->realm) cb_p->result = this->extra->realm; + break; + + default: + break; + } + MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL result : %s", cb_p->result ? (char const *)cb_p->result : ""); + } + return SASL_OK; +} + +ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request, + ldap_handle_t *conn, char const *identity, + char const *password, ldap_sasl *sasl, + char const **error, char **extra) +{ + ldap_rcode_t status; + int ret = 0; + int msgid; + char const *mech; + LDAPMessage *result = NULL; + rlm_ldap_sasl_ctx_t sasl_ctx; /* SASL defaults */ + + memset(&sasl_ctx, 0, sizeof(sasl_ctx)); + + sasl_ctx.inst = inst; + sasl_ctx.request = request; + sasl_ctx.identity = identity; + sasl_ctx.password = password; + + MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Starting SASL mech(s): %s", sasl->mech); + do { + ret = ldap_sasl_interactive_bind(conn->handle, NULL, sasl->mech, + NULL, NULL, LDAP_SASL_AUTOMATIC, + _sasl_interact, &sasl_ctx, result, + &mech, &msgid); + ldap_msgfree(result); /* We always need to free the old message */ + if (ret >= 0) MOD_ROPTIONAL(RDEBUG3, DEBUG3, "Continuing SASL mech %s...", mech); + + status = rlm_ldap_result(inst, conn, msgid, identity, &result, error, extra); + /* + * Write the servers response to the debug log + */ + if (((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) && result) { + struct berval *srv_cred; + + if (ldap_parse_sasl_bind_result(conn->handle, result, &srv_cred, 0) == 0) { + char *escaped; + + escaped = fr_aprints(request, srv_cred->bv_val, srv_cred->bv_len, '\0'); + MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL response : %s", escaped); + + talloc_free(escaped); + ldap_memfree(srv_cred); + } + } + } while (status == LDAP_PROC_CONTINUE); + ldap_msgfree(result); + + return status; +} +#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND */