Allow authentication retry in winbind
[freeradius.git] / src / modules / rlm_mschap / auth_wbclient.c
1 /*
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.
6  *
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.
11  *
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
15  */
16
17 /**
18  * $Id$
19  * @file auth_wbclient.c
20  * @brief NTLM authentication against the wbclient library
21  *
22  * @copyright 2015  Matthew Newton
23  */
24
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/rad_assert.h>
29
30 #include <core/ntstatus.h>
31
32 #include "rlm_mschap.h"
33 #include "mschap.h"
34 #include "auth_wbclient.h"
35
36 #define NT_LENGTH 24
37
38 /** Use Winbind to normalise a username
39  *
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.
46  */
47 static char *wbclient_normalise_username(TALLOC_CTX *tctx, struct wbcContext *ctx, char const *dom_name, char const *name)
48 {
49         struct wbcDomainSid sid;
50         enum wbcSidType name_type;
51         wbcErr err;
52         char *res_domain = NULL;
53         char *res_name = NULL;
54         char *res = NULL;
55
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))
59                 return NULL;
60
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))
64                 return NULL;
65
66         MEM(res = talloc_strdup(tctx, res_name));
67
68         wbcFreeMemory(res_domain);
69         wbcFreeMemory(res_name);
70
71         return res;
72 }
73
74 /*
75  *      Check NTLM authentication direct to winbind via
76  *      Samba's libwbclient library
77  *
78  *      Returns:
79  *       0    success
80  *       -1   auth failure
81  *       -648 password expired
82  */
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])
86 {
87         int rcode = -1;
88         struct wbcContext *wb_ctx = NULL;
89         struct wbcAuthUserParams authparams;
90         wbcErr err;
91         int len;
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];
97
98         /*
99          * Clear the auth parameters - this is important, as
100          * there are options that will cause wbcAuthenticateUserEx
101          * to bomb out if not zero.
102          */
103         memset(&authparams, 0, sizeof(authparams));
104
105         /*
106          * wb_username must be set for this function to be called
107          */
108         rad_assert(inst->wb_username);
109
110         /*
111          * Get the username and domain from the configuration
112          */
113         len = tmpl_expand(&authparams.account_name, user_name_buf, sizeof(user_name_buf),
114                           request, inst->wb_username, NULL, NULL);
115         if (len < 0) {
116                 REDEBUG2("Unable to expand winbind_username");
117                 goto done;
118         }
119
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);
123                 if (len < 0) {
124                         REDEBUG2("Unable to expand winbind_domain");
125                         goto done;
126                 }
127         } else {
128                 RWDEBUG2("No domain specified; authentication may fail because of this");
129         }
130
131
132         /*
133          * Build the wbcAuthUserParams structure with what we know
134          */
135         authparams.level = WBC_AUTH_USER_LEVEL_RESPONSE;
136         authparams.password.response.nt_length = NT_LENGTH;
137
138         memcpy(resp, response, NT_LENGTH);
139         authparams.password.response.nt_data = resp;
140
141         memcpy(authparams.password.response.challenge, challenge,
142                sizeof(authparams.password.response.challenge));
143
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;
147
148
149         /*
150          * Send auth request across to winbind
151          */
152         wb_ctx = fr_connection_get(inst->wb_pool);
153         if (wb_ctx == NULL) {
154                 RERROR("Unable to get winbind connection from pool");
155                 goto done;
156         }
157
158         RDEBUG2("sending authentication request user='%s' domain='%s'", authparams.account_name,
159                                                                         authparams.domain_name);
160
161         err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
162
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;
170
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;
175                                 }
176
177                                 RDEBUG2("retrying authentication request user='%s' domain='%s'", authparams.account_name,
178                                                                                                 authparams.domain_name);
179
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;
184                                 }
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;
188                                 }
189                                 mschap_challenge_hash(vp_response->vp_octets + 2,
190                                                                         vp_challenge->vp_octets,
191                                                                         normalised_username,
192                                                                         authparams.password.response.challenge);
193
194                                 err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
195                         }
196 normalised_username_retry_failure:
197                         talloc_free(normalised_username);
198                 }
199         }
200
201         fr_connection_release(inst->wb_pool, wb_ctx);
202
203         /*
204          * Try and give some useful feedback on what happened. There are only
205          * a few errors that can actually be returned from wbcCtxAuthenticateUserEx.
206          */
207         switch (err) {
208         case WBC_ERR_SUCCESS:
209                 rcode = 0;
210                 RDEBUG2("Authenticated successfully");
211                 /* Grab the nthashhash from the result */
212                 memcpy(nthashhash, info->user_session_key, NT_DIGEST_LENGTH);
213                 break;
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.");
218                 break;
219         case WBC_ERR_DOMAIN_NOT_FOUND:
220                 REDEBUG2("Domain not found");
221                 break;
222         case WBC_ERR_AUTH_ERROR:
223                 if (!error) {
224                         REDEBUG2("Authentication failed");
225                         break;
226                 }
227
228                 /*
229                  * The password needs to be changed, so set rcode appropriately.
230                  */
231                 if (error->nt_status == NT_STATUS_PASSWORD_EXPIRED ||
232                     error->nt_status == NT_STATUS_PASSWORD_MUST_CHANGE) {
233                         rcode = -648;
234                 }
235
236                 /*
237                  * Return the NT_STATUS human readable error string, if there is one.
238                  */
239                 if (error->display_string) {
240                         REDEBUG2("%s [0x%X]", error->display_string, error->nt_status);
241                 } else {
242                         REDEBUG2("Authentication failed [0x%X]", error->nt_status);
243                 }
244                 break;
245         default:
246                 /*
247                  * Only errors left are 
248                  *   WBC_ERR_INVALID_PARAM
249                  *   WBC_ERR_NO_MEMORY
250                  * neither of which are particularly likely.
251                  */
252                 if (error && error->display_string) {
253                         REDEBUG2("libwbclient error: wbcErr %d (%s)", err, error->display_string);
254                 } else {
255                         REDEBUG2("libwbclient error: wbcErr %d", err);
256                 }
257                 break;
258         }
259
260
261 done:
262         if (info) wbcFreeMemory(info);
263         if (error) wbcFreeMemory(error);
264
265         return rcode;
266 }
267