2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @file auth_wbclient.c
20 * @brief NTLM authentication against the wbclient library
22 * @copyright 2015 Matthew Newton
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/rad_assert.h>
30 #include <core/ntstatus.h>
32 #include "rlm_mschap.h"
34 #include "auth_wbclient.h"
38 /** Use Winbind to normalise a username
40 * @param[in] tctx The talloc context where the result is parented from
41 * @param[in] ctx The winbind context
42 * @param[in] dom_name The domain of the user
43 * @param[in] name The username (without the domain) to be normalised
44 * @return The username with the casing according to the Winbind remote server,
45 * or NULL if the username could not be found.
47 static char *wbclient_normalise_username(TALLOC_CTX *tctx, struct wbcContext *ctx, char const *dom_name, char const *name)
49 struct wbcDomainSid sid;
50 enum wbcSidType name_type;
52 char *res_domain = NULL;
53 char *res_name = NULL;
56 /* Step 1: Convert a name to a sid */
57 err = wbcCtxLookupName(ctx, dom_name, name, &sid, &name_type);
58 if (!WBC_ERROR_IS_OK(err))
61 /* Step 2: Convert the sid back to a name */
62 err = wbcCtxLookupSid(ctx, &sid, &res_domain, &res_name, &name_type);
63 if (!WBC_ERROR_IS_OK(err))
66 MEM(res = talloc_strdup(tctx, res_name));
68 wbcFreeMemory(res_domain);
69 wbcFreeMemory(res_name);
75 * Check NTLM authentication direct to winbind via
76 * Samba's libwbclient library
81 * -648 password expired
83 int do_auth_wbclient(rlm_mschap_t *inst, REQUEST *request,
84 uint8_t const *challenge, uint8_t const *response,
85 uint8_t nthashhash[NT_DIGEST_LENGTH])
88 struct wbcContext *wb_ctx = NULL;
89 struct wbcAuthUserParams authparams;
92 struct wbcAuthUserInfo *info = NULL;
93 struct wbcAuthErrorInfo *error = NULL;
94 char user_name_buf[500];
95 char domain_name_buf[500];
96 uint8_t resp[NT_LENGTH];
99 * Clear the auth parameters - this is important, as
100 * there are options that will cause wbcAuthenticateUserEx
101 * to bomb out if not zero.
103 memset(&authparams, 0, sizeof(authparams));
106 * wb_username must be set for this function to be called
108 rad_assert(inst->wb_username);
111 * Get the username and domain from the configuration
113 len = tmpl_expand(&authparams.account_name, user_name_buf, sizeof(user_name_buf),
114 request, inst->wb_username, NULL, NULL);
116 REDEBUG2("Unable to expand winbind_username");
120 if (inst->wb_domain) {
121 len = tmpl_expand(&authparams.domain_name, domain_name_buf, sizeof(domain_name_buf),
122 request, inst->wb_domain, NULL, NULL);
124 REDEBUG2("Unable to expand winbind_domain");
128 RWDEBUG2("No domain specified; authentication may fail because of this");
133 * Build the wbcAuthUserParams structure with what we know
135 authparams.level = WBC_AUTH_USER_LEVEL_RESPONSE;
136 authparams.password.response.nt_length = NT_LENGTH;
138 memcpy(resp, response, NT_LENGTH);
139 authparams.password.response.nt_data = resp;
141 memcpy(authparams.password.response.challenge, challenge,
142 sizeof(authparams.password.response.challenge));
144 authparams.parameter_control |= WBC_MSV1_0_ALLOW_MSVCHAPV2 |
145 WBC_MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT |
146 WBC_MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT;
150 * Send auth request across to winbind
152 wb_ctx = fr_connection_get(inst->wb_pool);
153 if (wb_ctx == NULL) {
154 RERROR("Unable to get winbind connection from pool");
158 RDEBUG2("sending authentication request user='%s' domain='%s'", authparams.account_name,
159 authparams.domain_name);
161 err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
163 if (err == WBC_ERR_AUTH_ERROR && inst->wb_retry_with_normalised_username) {
164 VALUE_PAIR *vp_response, *vp_challenge;
165 char *normalised_username = wbclient_normalise_username(request, wb_ctx, authparams.domain_name, authparams.account_name);
166 if (normalised_username) {
167 RDEBUG2("Starting retry, normalised username %s to %s", authparams.account_name, normalised_username);
168 if (strcmp(authparams.account_name, normalised_username) != 0) {
169 authparams.account_name = normalised_username;
171 /* Set PW_MS_CHAP_USER_NAME */
172 if (!fr_pair_make(request->packet, &request->packet->vps, "MS-CHAP-User-Name", normalised_username, T_OP_SET)) {
173 RERROR("Failed creating MS-CHAP-User-Name");
174 goto normalised_username_retry_failure;
177 RDEBUG2("retrying authentication request user='%s' domain='%s'", authparams.account_name,
178 authparams.domain_name);
180 /* Recalculate hash */
181 if (!(vp_challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY))) {
182 RERROR("Unable to get MS-CHAP-Challenge");
183 goto normalised_username_retry_failure;
185 if (!(vp_response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY))) {
186 RERROR("Unable to get MS-CHAP2-Response");
187 goto normalised_username_retry_failure;
189 mschap_challenge_hash(vp_response->vp_octets + 2,
190 vp_challenge->vp_octets,
192 authparams.password.response.challenge);
194 err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
196 normalised_username_retry_failure:
197 talloc_free(normalised_username);
201 fr_connection_release(inst->wb_pool, wb_ctx);
204 * Try and give some useful feedback on what happened. There are only
205 * a few errors that can actually be returned from wbcCtxAuthenticateUserEx.
208 case WBC_ERR_SUCCESS:
210 RDEBUG2("Authenticated successfully");
211 /* Grab the nthashhash from the result */
212 memcpy(nthashhash, info->user_session_key, NT_DIGEST_LENGTH);
214 case WBC_ERR_WINBIND_NOT_AVAILABLE:
215 RERROR("Unable to contact winbind!");
216 RDEBUG2("Check that winbind is running and that FreeRADIUS has");
217 RDEBUG2("permission to connect to the winbind privileged socket.");
219 case WBC_ERR_DOMAIN_NOT_FOUND:
220 REDEBUG2("Domain not found");
222 case WBC_ERR_AUTH_ERROR:
224 REDEBUG2("Authentication failed");
229 * The password needs to be changed, so set rcode appropriately.
231 if (error->nt_status == NT_STATUS_PASSWORD_EXPIRED ||
232 error->nt_status == NT_STATUS_PASSWORD_MUST_CHANGE) {
237 * Return the NT_STATUS human readable error string, if there is one.
239 if (error->display_string) {
240 REDEBUG2("%s [0x%X]", error->display_string, error->nt_status);
242 REDEBUG2("Authentication failed [0x%X]", error->nt_status);
247 * Only errors left are
248 * WBC_ERR_INVALID_PARAM
250 * neither of which are particularly likely.
252 if (error && error->display_string) {
253 REDEBUG2("libwbclient error: wbcErr %d (%s)", err, error->display_string);
255 REDEBUG2("libwbclient error: wbcErr %d", err);
262 if (info) wbcFreeMemory(info);
263 if (error) wbcFreeMemory(error);