The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
auth_wbclient.c
Go to the documentation of this file.
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: 4830fdc165af4b651b24ffb16ef4cc38db7ad38d $
19  * @file auth_wbclient.c
20  * @brief NTLM authentication against the wbclient library
21  *
22  * @copyright 2015 Matthew Newton
23  */
24 
25 RCSID("$Id: 4830fdc165af4b651b24ffb16ef4cc38db7ad38d $")
26 
27 #include <freeradius-devel/server/base.h>
28 #include <freeradius-devel/util/debug.h>
29 
30 #include <wbclient.h>
31 #include <core/ntstatus.h>
32 
33 #include "rlm_mschap.h"
34 #include "mschap.h"
35 #include "auth_wbclient.h"
36 
37 /* Samba does not export this constant yet */
38 #ifndef WBC_MSV1_0_ALLOW_MSVCHAPV2
39 #define WBC_MSV1_0_ALLOW_MSVCHAPV2 0x00010000
40 #endif
41 
42 #define NT_LENGTH 24
43 
44 /** Use Winbind to normalise a username
45  *
46  * @param[in] ctx The talloc context where the result is parented from
47  * @param[in] wb_ctx The winbind context
48  * @param[in] dom_name The domain of the user
49  * @param[in] name The username (without the domain) to be normalised
50  * @return
51  * - The username with the casing according to the Winbind remote server.
52  * - NULL if the username could not be found.
53  */
54 static char *wbclient_normalise_username(TALLOC_CTX *ctx, struct wbcContext *wb_ctx,
55  char const *dom_name, char const *name)
56 {
57  struct wbcDomainSid sid;
58  enum wbcSidType name_type;
59  wbcErr err;
60  char *res_domain = NULL;
61  char *res_name = NULL;
62  char *res = NULL;
63 
64  /* Step 1: Convert a name to a sid */
65  err = wbcCtxLookupName(wb_ctx, dom_name, name, &sid, &name_type);
66  if (!WBC_ERROR_IS_OK(err)) return NULL;
67 
68  /* Step 2: Convert the sid back to a name */
69  err = wbcCtxLookupSid(wb_ctx, &sid, &res_domain, &res_name, &name_type);
70  if (!WBC_ERROR_IS_OK(err)) return NULL;
71 
72  MEM(res = talloc_strdup(ctx, res_name));
73 
74  wbcFreeMemory(res_domain);
75  wbcFreeMemory(res_name);
76 
77  return res;
78 }
79 
80 /** Check NTLM authentication direct to winbind via Samba's libwbclient library
81  *
82  * @param[in] inst Module instance.
83  * @param[in] request Current request.
84  * @param[in] challenge MS CHAP challenge.
85  * @param[in] response MS CHAP response.
86  * @param[out] nthashhash Hash returned on success.
87  * @param[in] env_data Call_env data for current authentication.
88  * @return
89  * - 0 success.
90  * - -1 auth failure.
91  * - -648 password expired.
92  */
94  uint8_t const *challenge, uint8_t const *response,
95  uint8_t nthashhash[NT_DIGEST_LENGTH], mschap_auth_call_env_t *env_data)
96 {
97  int ret = -1;
98  struct wbcContext *wb_ctx = NULL;
99  struct wbcAuthUserParams authparams;
100  wbcErr err;
101  struct wbcAuthUserInfo *info = NULL;
102  struct wbcAuthErrorInfo *error = NULL;
103  uint8_t resp[NT_LENGTH];
104 
105  /*
106  * Clear the auth parameters - this is important, as
107  * there are options that will cause wbcAuthenticateUserEx
108  * to bomb out if not zero.
109  */
110  memset(&authparams, 0, sizeof(authparams));
111 
112  if (env_data->wb_domain.type == FR_TYPE_STRING) {
113  authparams.domain_name = env_data->wb_domain.vb_strvalue;
114  } else {
115  RWDEBUG2("No domain specified; authentication may fail because of this");
116  }
117 
118  /*
119  * Get the username from the call_env data.
120  */
121  authparams.account_name = env_data->wb_username.vb_strvalue;
122 
123  /*
124  * Build the wbcAuthUserParams structure with what we know
125  */
126  authparams.level = WBC_AUTH_USER_LEVEL_RESPONSE;
127  authparams.password.response.nt_length = NT_LENGTH;
128 
129  memcpy(resp, response, NT_LENGTH);
130  authparams.password.response.nt_data = resp;
131 
132  memcpy(authparams.password.response.challenge, challenge, sizeof(authparams.password.response.challenge));
133 
134  authparams.parameter_control |= WBC_MSV1_0_ALLOW_MSVCHAPV2 |
135  WBC_MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT |
136  WBC_MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT;
137 
138  /*
139  * Send auth request across to winbind
140  */
141  wb_ctx = fr_pool_connection_get(inst->wb_pool, request);
142  if (wb_ctx == NULL) {
143  RERROR("Unable to get winbind connection from pool");
144  goto finish;
145  }
146 
147  RDEBUG2("Sending authentication request user \"%pV\" domain \"%pV\"",
148  &env_data->wb_username, &env_data->wb_domain);
149 
150  err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
151  if (err == WBC_ERR_AUTH_ERROR && inst->wb_retry_with_normalised_username) {
152  fr_pair_t *vp_response;
153  fr_pair_t *vp_challenge;
154  fr_pair_t *vp_chap_user_name;
155  char *normalised_username = NULL;
156 
157  normalised_username = wbclient_normalise_username(env_data, wb_ctx, authparams.domain_name,
158  authparams.account_name);
159  if (!normalised_username) goto release;
160 
161  RDEBUG2("Starting retry, normalised username \"%pV\" -> \"%pV\"",
162  &env_data->wb_username,
163  fr_box_strvalue_buffer(normalised_username));
164 
165  if (talloc_memcmp_bstr(authparams.account_name, normalised_username) == 0) goto release;
166 
167  authparams.account_name = normalised_username;
168 
169  /* Set MS-CHAP-USER-NAME */
170  MEM(pair_update_request(&vp_chap_user_name, attr_ms_chap_user_name) >= 0);
171  fr_pair_value_bstrdup_buffer(vp_chap_user_name, normalised_username, true);
172 
173  RDEBUG2("Retrying authentication request user \"%pV\" domain \"%pV\"",
174  fr_box_strvalue_buffer(normalised_username),
175  &env_data->wb_domain);
176 
177  /* Recalculate hash */
178  vp_challenge = fr_pair_find_by_da_nested(&request->request_pairs, NULL,
179  tmpl_attr_tail_da(env_data->chap_challenge));
180  if (!vp_challenge) {
181  RERROR("Unable to get %s", env_data->chap_challenge->name);
182  goto release;
183  }
184 
185  vp_response = fr_pair_find_by_da_nested(&request->request_pairs, NULL,
186  tmpl_attr_tail_da(env_data->chap2_response));
187  if (!vp_response) {
188  RERROR("Unable to get MS-CHAP2-Response");
189  goto release;
190  }
191 
192  mschap_challenge_hash(authparams.password.response.challenge,
193  vp_response->vp_octets + 2,
194  vp_challenge->vp_octets,
195  vp_chap_user_name->vp_strvalue, vp_chap_user_name->vp_length);
196 
197  err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);
198 release:
199  talloc_free(normalised_username);
200  }
201 
202  fr_pool_connection_release(inst->wb_pool, request, wb_ctx);
203 
204  /*
205  * Try and give some useful feedback on what happened. There are only
206  * a few errors that can actually be returned from wbcCtxAuthenticateUserEx.
207  */
208  switch (err) {
209  case WBC_ERR_SUCCESS:
210  ret = 0;
211  RDEBUG2("Authenticated successfully");
212  /* Grab the nthashhash from the result */
213  memcpy(nthashhash, info->user_session_key, NT_DIGEST_LENGTH);
214  break;
215 
216  case WBC_ERR_WINBIND_NOT_AVAILABLE:
217  RERROR("Unable to contact winbind!");
218  RDEBUG2("Check that winbind is running and that FreeRADIUS has");
219  RDEBUG2("permission to connect to the winbind privileged socket.");
220  break;
221 
222  case WBC_ERR_DOMAIN_NOT_FOUND:
223  REDEBUG2("Domain not found");
224  break;
225 
226  case WBC_ERR_AUTH_ERROR:
227  if (!error) {
228  REDEBUG2("Authentication failed");
229  break;
230  }
231 
232  /*
233  * The password needs to be changed, so set ret appropriately.
234  */
235  if (error->nt_status == NT_STATUS_PASSWORD_EXPIRED ||
236  error->nt_status == NT_STATUS_PASSWORD_MUST_CHANGE) {
237  ret = -648;
238  }
239 
240  /*
241  * Return the NT_STATUS human readable error string, if there is one.
242  */
243  if (error->display_string) {
244  REDEBUG2("%s [0x%X]", error->display_string, error->nt_status);
245  } else {
246  REDEBUG2("Authentication failed [0x%X]", error->nt_status);
247  }
248  break;
249 
250  default:
251  /*
252  * Only errors left are
253  * WBC_ERR_INVALID_PARAM
254  * WBC_ERR_NO_MEMORY
255  * neither of which are particularly likely.
256  */
257  if (error && error->display_string) {
258  REDEBUG2("libwbclient error: wbcErr %d (%s)", err, error->display_string);
259  } else {
260  REDEBUG2("libwbclient error: wbcErr %d", err);
261  }
262  break;
263  }
264 
265 finish:
266  if (info) wbcFreeMemory(info);
267  if (error) wbcFreeMemory(error);
268 
269  return ret;
270 }
#define NT_LENGTH
Definition: auth_wbclient.c:42
#define WBC_MSV1_0_ALLOW_MSVCHAPV2
Definition: auth_wbclient.c:39
static char * wbclient_normalise_username(TALLOC_CTX *ctx, struct wbcContext *wb_ctx, char const *dom_name, char const *name)
Use Winbind to normalise a username.
Definition: auth_wbclient.c:54
int do_auth_wbclient(rlm_mschap_t const *inst, request_t *request, uint8_t const *challenge, uint8_t const *response, uint8_t nthashhash[NT_DIGEST_LENGTH], mschap_auth_call_env_t *env_data)
Check NTLM authentication direct to winbind via Samba's libwbclient library.
Definition: auth_wbclient.c:93
#define RCSID(id)
Definition: build.h:444
static fr_slen_t err
Definition: dict.h:645
#define RWDEBUG2(fmt,...)
Definition: log.h:362
#define RERROR(fmt,...)
Definition: log.h:298
#define REDEBUG2(fmt,...)
Definition: log.h:372
talloc_free(reap)
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
unsigned char uint8_t
Definition: merged_model.c:30
void mschap_challenge_hash(uint8_t challenge[static MSCHAP_CHALLENGE_LENGTH], uint8_t const peer_challenge[static MSCHAP_PEER_CHALLENGE_LENGTH], uint8_t const auth_challenge[static MSCHAP_PEER_AUTHENTICATOR_CHALLENGE_LENGTH], char const *user_name, size_t user_name_len)
Definition: mschap.c:72
#define NT_DIGEST_LENGTH
Definition: mschap.h:7
fr_pair_t * fr_pair_find_by_da_nested(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find a pair with a matching fr_dict_attr_t, by walking the nested fr_dict_attr_t tree.
Definition: pair.c:765
void fr_pool_connection_release(fr_pool_t *pool, request_t *request, void *conn)
Release a connection.
Definition: pool.c:1405
void * fr_pool_connection_get(fr_pool_t *pool, request_t *request)
Reserve a connection in the connection pool.
Definition: pool.c:1390
#define pair_update_request(_attr, _da)
Definition: radclient-ng.c:60
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
static fr_dict_attr_t const * attr_ms_chap_user_name
fr_value_box_t wb_domain
Definition: rlm_mschap.h:83
fr_value_box_t wb_username
Definition: rlm_mschap.h:82
tmpl_t const * chap_challenge
Definition: rlm_mschap.h:71
tmpl_t const * chap2_response
Definition: rlm_mschap.h:73
static char const * name
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition: tmpl.h:796
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
fr_pair_value_bstrdup_buffer(vp, eap_session->identity, true)
Stores an attribute, a value and various bits of other data.
Definition: pair.h:68
int talloc_memcmp_bstr(char const *a, char const *b)
Compares two talloced char arrays with memcmp.
Definition: talloc.c:684
#define fr_box_strvalue_buffer(_val)
Definition: value.h:282