summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_mschap/auth_wbclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_mschap/auth_wbclient.c')
-rw-r--r--src/modules/rlm_mschap/auth_wbclient.c270
1 files changed, 270 insertions, 0 deletions
diff --git a/src/modules/rlm_mschap/auth_wbclient.c b/src/modules/rlm_mschap/auth_wbclient.c
new file mode 100644
index 0000000..8b4a3ee
--- /dev/null
+++ b/src/modules/rlm_mschap/auth_wbclient.c
@@ -0,0 +1,270 @@
+/*
+ * 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
+ */
+
+/**
+ * $Id$
+ * @file auth_wbclient.c
+ * @brief NTLM authentication against the wbclient library
+ *
+ * @copyright 2015 Matthew Newton
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <core/ntstatus.h>
+
+#include "rlm_mschap.h"
+#include "mschap.h"
+#include "auth_wbclient.h"
+
+#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
+ *
+ * Returns:
+ * 0 success
+ * -1 auth failure
+ * -2 failed connecting to AD
+ * -648 password expired
+ */
+int do_auth_wbclient(rlm_mschap_t *inst, REQUEST *request,
+ uint8_t const *challenge, uint8_t const *response,
+ uint8_t nthashhash[NT_DIGEST_LENGTH])
+{
+ int rcode = -1;
+ struct wbcContext *wb_ctx = NULL;
+ struct wbcAuthUserParams authparams;
+ wbcErr err;
+ int len;
+ struct wbcAuthUserInfo *info = NULL;
+ struct wbcAuthErrorInfo *error = NULL;
+ char user_name_buf[500];
+ char domain_name_buf[500];
+ uint8_t resp[NT_LENGTH];
+
+ /*
+ * Clear the auth parameters - this is important, as
+ * there are options that will cause wbcAuthenticateUserEx
+ * to bomb out if not zero.
+ */
+ memset(&authparams, 0, sizeof(authparams));
+
+ /*
+ * wb_username must be set for this function to be called
+ */
+ rad_assert(inst->wb_username);
+
+ /*
+ * Get the username and domain from the configuration
+ */
+ len = tmpl_expand(&authparams.account_name, user_name_buf, sizeof(user_name_buf),
+ request, inst->wb_username, NULL, NULL);
+ if (len < 0) {
+ REDEBUG2("Unable to expand winbind_username");
+ goto done;
+ }
+
+ if (inst->wb_domain) {
+ len = tmpl_expand(&authparams.domain_name, domain_name_buf, sizeof(domain_name_buf),
+ request, inst->wb_domain, NULL, NULL);
+ if (len < 0) {
+ REDEBUG2("Unable to expand winbind_domain");
+ goto done;
+ }
+ } else {
+ RWDEBUG2("No domain specified; authentication may fail because of this");
+ }
+
+
+ /*
+ * Build the wbcAuthUserParams structure with what we know
+ */
+ authparams.level = WBC_AUTH_USER_LEVEL_RESPONSE;
+ authparams.password.response.nt_length = NT_LENGTH;
+
+ memcpy(resp, response, NT_LENGTH);
+ authparams.password.response.nt_data = resp;
+
+ memcpy(authparams.password.response.challenge, challenge,
+ sizeof(authparams.password.response.challenge));
+
+ authparams.parameter_control |= WBC_MSV1_0_ALLOW_MSVCHAPV2 |
+ WBC_MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT |
+ WBC_MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT;
+
+
+ /*
+ * Send auth request across to winbind
+ */
+ wb_ctx = fr_connection_get(inst->wb_pool);
+ if (wb_ctx == NULL) {
+ RERROR("Unable to get winbind connection from pool");
+ goto done;
+ }
+
+ RDEBUG2("sending authentication request user='%s' domain='%s'", authparams.account_name,
+ authparams.domain_name);
+
+ err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
+
+ 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
+ * a few errors that can actually be returned from wbcCtxAuthenticateUserEx.
+ */
+ switch (err) {
+ case WBC_ERR_SUCCESS:
+ rcode = 0;
+ RDEBUG2("Authenticated successfully");
+ /* Grab the nthashhash from the result */
+ memcpy(nthashhash, info->user_session_key, NT_DIGEST_LENGTH);
+ break;
+ case WBC_ERR_WINBIND_NOT_AVAILABLE:
+ rcode = -2;
+ RERROR("Unable to contact winbind!");
+ RDEBUG2("Check that winbind is running and that FreeRADIUS has");
+ RDEBUG2("permission to connect to the winbind privileged socket.");
+ break;
+ case WBC_ERR_DOMAIN_NOT_FOUND:
+ REDEBUG2("Domain not found");
+ break;
+ case WBC_ERR_AUTH_ERROR:
+ if (!error) {
+ REDEBUG2("Authentication failed");
+ break;
+ }
+
+ /*
+ * The password needs to be changed, so set rcode appropriately.
+ */
+ if (error->nt_status == NT_STATUS_PASSWORD_EXPIRED ||
+ error->nt_status == NT_STATUS_PASSWORD_MUST_CHANGE) {
+ rcode = -648;
+ }
+
+ /*
+ * Return the NT_STATUS human readable error string, if there is one.
+ */
+ if (error->display_string) {
+ REDEBUG2("%s [0x%X]", error->display_string, error->nt_status);
+ } else {
+ REDEBUG2("Authentication failed [0x%X]", error->nt_status);
+ }
+ break;
+ default:
+ /*
+ * Only errors left are
+ * WBC_ERR_INVALID_PARAM
+ * WBC_ERR_NO_MEMORY
+ * neither of which are particularly likely.
+ */
+ rcode = -2;
+ if (error && error->display_string) {
+ REDEBUG2("libwbclient error: wbcErr %d (%s)", err, error->display_string);
+ } else {
+ REDEBUG2("libwbclient error: wbcErr %d", err);
+ }
+ break;
+ }
+
+
+done:
+ if (info) wbcFreeMemory(info);
+ if (error) wbcFreeMemory(error);
+
+ return rcode;
+}
+