Allow authentication retry in winbind
authorHerwin Weststrate <herwin@quarantainenet.nl>
Wed, 9 Nov 2016 09:29:08 +0000 (10:29 +0100)
committerHerwin Weststrate <herwin@quarantainenet.nl>
Tue, 20 Dec 2016 11:22:34 +0000 (12:22 +0100)
A setup with the following properties:

  * Active Directory backend
  * FreeRadius with eap-inner-proxy
  * Windows client with single sign-on
  * User using different casing in username than in backend

may result in failing connections. It looks like Windows reads the
correct username from the domain server once it has logged in, and uses
that to create the MS-CHAP2-Response attribute. The User-Name attribute
is still the one with the incorrect casing, causing the authentication
to fail.

The introduced config option kicks in after a failed authentication: it
reads the correct username from the backend, tries another
authentication, and uses the found User-Name to calculate
MS-CHAP2-Response if the second authentication works.

raddb/mods-available/mschap
src/modules/rlm_mschap/auth_wbclient.c
src/modules/rlm_mschap/rlm_mschap.c
src/modules/rlm_mschap/rlm_mschap.h

index 4673fa7..18f6005 100644 (file)
@@ -78,6 +78,15 @@ mschap {
 #      winbind_username = "%{mschap:User-Name}"
 #      winbind_domain = "%{mschap:NT-Domain}"
 
+       # When using single sign-on with a winbind connection and the
+       # client uses a different casing for the username than the
+       # casing is according to the backend, reauth may fail because
+       # of some Windows internals. This switch tries to find the
+       # user in the correct casing in the backend, and retry
+       # authentication with that username.
+       #
+#      winbind_retry_with_normalised_username = no
+
        #
        #  Information for the winbind connection pool.  The configuration
        #  items below are the same for all modules which use the new
index a836195..f0bc166 100644 (file)
@@ -35,6 +35,42 @@ RCSID("$Id$")
 
 #define NT_LENGTH 24
 
+/** Use Winbind to normalise a username
+ *
+ * @param[in] tctx The talloc context where the result is parented from
+ * @param[in] ctx The winbind context
+ * @param[in] dom_name The domain of the user
+ * @param[in] name The username (without the domain) to be normalised
+ * @return The username with the casing according to the Winbind remote server,
+ *         or NULL if the username could not be found.
+ */
+static char *wbclient_normalise_username(TALLOC_CTX *tctx, struct wbcContext *ctx, char const *dom_name, char const *name)
+{
+       struct wbcDomainSid sid;
+       enum wbcSidType name_type;
+       wbcErr err;
+       char *res_domain = NULL;
+       char *res_name = NULL;
+       char *res = NULL;
+
+       /* Step 1: Convert a name to a sid */
+       err = wbcCtxLookupName(ctx, dom_name, name, &sid, &name_type);
+       if (!WBC_ERROR_IS_OK(err))
+               return NULL;
+
+       /* Step 2: Convert the sid back to a name */
+       err = wbcCtxLookupSid(ctx, &sid, &res_domain, &res_name, &name_type);
+       if (!WBC_ERROR_IS_OK(err))
+               return NULL;
+
+       MEM(res = talloc_strdup(tctx, res_name));
+
+       wbcFreeMemory(res_domain);
+       wbcFreeMemory(res_name);
+
+       return res;
+}
+
 /*
  *     Check NTLM authentication direct to winbind via
  *     Samba's libwbclient library
@@ -49,7 +85,7 @@ int do_auth_wbclient(rlm_mschap_t *inst, REQUEST *request,
                     uint8_t nthashhash[NT_DIGEST_LENGTH])
 {
        int rcode = -1;
-       struct wbcContext *wb_ctx;
+       struct wbcContext *wb_ctx = NULL;
        struct wbcAuthUserParams authparams;
        wbcErr err;
        int len;
@@ -124,8 +160,45 @@ int do_auth_wbclient(rlm_mschap_t *inst, REQUEST *request,
 
        err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
 
-       fr_connection_release(inst->wb_pool, wb_ctx);
+       if (err == WBC_ERR_AUTH_ERROR && inst->wb_retry_with_normalised_username) {
+               VALUE_PAIR *vp_response, *vp_challenge;
+               char *normalised_username = wbclient_normalise_username(request, wb_ctx, authparams.domain_name, authparams.account_name);
+               if (normalised_username) {
+                       RDEBUG2("Starting retry, normalised username %s to %s", authparams.account_name, normalised_username);
+                       if (strcmp(authparams.account_name, normalised_username) != 0) {
+                               authparams.account_name = normalised_username;
+
+                               /* Set PW_MS_CHAP_USER_NAME */
+                               if (!fr_pair_make(request->packet, &request->packet->vps, "MS-CHAP-User-Name", normalised_username, T_OP_SET)) {
+                                       RERROR("Failed creating MS-CHAP-User-Name");
+                                       goto normalised_username_retry_failure;
+                               }
+
+                               RDEBUG2("retrying authentication request user='%s' domain='%s'", authparams.account_name,
+                                                                                               authparams.domain_name);
+
+                               /* Recalculate hash */
+                               if (!(vp_challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY))) {
+                                       RERROR("Unable to get MS-CHAP-Challenge");
+                                       goto normalised_username_retry_failure;
+                               }
+                               if (!(vp_response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY))) {
+                                       RERROR("Unable to get MS-CHAP2-Response");
+                                       goto normalised_username_retry_failure;
+                               }
+                               mschap_challenge_hash(vp_response->vp_octets + 2,
+                                                                       vp_challenge->vp_octets,
+                                                                       normalised_username,
+                                                                       authparams.password.response.challenge);
 
+                               err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
+                       }
+normalised_username_retry_failure:
+                       talloc_free(normalised_username);
+               }
+       }
+
+       fr_connection_release(inst->wb_pool, wb_ctx);
 
        /*
         * Try and give some useful feedback on what happened. There are only
index 5a3a67a..e2d4878 100644 (file)
@@ -560,6 +560,7 @@ static const CONF_PARSER module_config[] = {
        { "retry_msg", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_mschap_t, retry_msg), NULL },
        { "winbind_username", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_mschap_t, wb_username), NULL },
        { "winbind_domain", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_mschap_t, wb_domain), NULL },
+       { "winbind_retry_with_normalised_username", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, wb_retry_with_normalised_username), "no" },
 #ifdef __APPLE__
        { "use_open_directory", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, open_directory), "yes" },
 #endif
@@ -1971,6 +1972,17 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *re
                                     mschap_result, mschap_version, smb_ctrl);
                if (rcode != RLM_MODULE_OK) return rcode;
 
+#ifdef WITH_AUTH_WINBIND
+               if (inst->wb_retry_with_normalised_username) {
+                       if ((response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY))) {
+                               if (strcmp(username_string, response_name->vp_strvalue)) {
+                                       RDEBUG2("Changing username %s to %s", username_string, response_name->vp_strvalue);
+                                       username_string = response_name->vp_strvalue;
+                               }
+                       }
+               }
+#endif
+
                mschap_auth_response(username_string,           /* without the domain */
                                     nthashhash,                /* nt-hash-hash */
                                     response->vp_octets + 26,  /* peer response */
index 1ce1ad4..4109715 100644 (file)
@@ -39,6 +39,7 @@ typedef struct rlm_mschap_t {
        vp_tmpl_t               *wb_username;
        vp_tmpl_t               *wb_domain;
        fr_connection_pool_t    *wb_pool;
+       bool                    wb_retry_with_normalised_username;
 #ifdef __APPLE__
        bool                    open_directory;
 #endif