]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Backport SASL interactive bind (non interactive didn't work anyway...) Fixes #991
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Sun, 14 Jun 2015 02:06:42 +0000 (22:06 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Sun, 14 Jun 2015 02:06:52 +0000 (22:06 -0400)
raddb/mods-available/ldap
src/modules/rlm_ldap/clients.c
src/modules/rlm_ldap/config.h.in
src/modules/rlm_ldap/configure
src/modules/rlm_ldap/configure.ac
src/modules/rlm_ldap/ldap.c
src/modules/rlm_ldap/ldap.h
src/modules/rlm_ldap/rlm_ldap.c
src/modules/rlm_ldap/sasl.c [new file with mode: 0644]

index f30382d309f5278049020d91401fd2d330ed784a..71670643546be0ad0400c9e6da0ada01bf7bab5f 100644 (file)
@@ -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'
index 4566fbe581b10401d051de09be9024853e4da5b2..5b9d88273c205ef0281909fc83bb9b992157e07f 100644 (file)
@@ -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;
index a9c61ac2f17c1158113ede5e375a5a0250d6b991..12fadca2382434146f58ebc7f97d48fa385234da 100644 (file)
@@ -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
index e23adcf1684821fdbfa1102221689ffbd49b5b92..7a6cf23eb31335607d688ab815ceab2470187eae 100755 (executable)
@@ -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 \
index 86953e82d87de7c591b0aee9d145302973dbf5d7..8bdcf48d171f83654e973f02f30214b568635c2b 100644 (file)
@@ -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 \
index 8c495c0f06ac024474bc9ef84eeb3eb8856b915a..416a1226a02579ba4b7a40b684a40b9160c56c60 100644 (file)
@@ -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;
        }
index 8f9496327603b8480aa1967e5e4195c3648664a3..bcd021926a3d7d7b353310d2e769581d094d7b9f 100644 (file)
@@ -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
 #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
index b5757a97734ce8047498d776803382cb81ec33b4..d62de35af51df27f31c58f671daf1ac2f4a858f0 100644 (file)
@@ -36,6 +36,8 @@ RCSID("$Id$")
 
 #include       "ldap.h"
 
+#include       <freeradius-devel/map_proc.h>
+
 /*
  *     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 (file)
index 0000000..0e58bba
--- /dev/null
@@ -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 <a.cudbardb@freeradius.org>
+ * @copyright 2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ * @copyright 2015 The FreeRADIUS Server Project.
+ */
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <sasl/sasl.h>
+
+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 */