Fix SPA authenticator, checking client-supplied data before using it. Bug 2571
[exim.git] / src / src / auths / spa.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
1e1ddfac 6/* Copyright (c) The Exim Maintainers 2020 */
0756eb3c
PH
7/* See the file NOTICE for conditions of use and distribution. */
8
9/* This file, which provides support for Microsoft's Secure Password
10Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA
11server support. I (PH) have only modified it in very trivial ways.
12
13References:
14 http://www.innovation.ch/java/ntlm.html
15 http://www.kuro5hin.org/story/2002/4/28/1436/66154
55c75993 16 http://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/%5bMS-SMTP%5d.pdf
0756eb3c
PH
17
18 * It seems that some systems have existing but different definitions of some
19 * of the following types. I received a complaint about "int16" causing
20 * compilation problems. So I (PH) have renamed them all, to be on the safe
21 * side, by adding 'x' on the end. See auths/auth-spa.h.
22
23 * typedef signed short int16;
24 * typedef unsigned short uint16;
25 * typedef unsigned uint32;
26 * typedef unsigned char uint8;
27
16ff981e
PH
2807-August-2003: PH: Patched up the code to avoid assert bombouts for stupid
29 input data. Find appropriate comment by grepping for "PH".
3016-October-2006: PH: Added a call to auth_check_serv_cond() at the end
55c75993 3105-June-2010: PP: handle SASL initial response
0756eb3c
PH
32*/
33
34
35#include "../exim.h"
36#include "spa.h"
37
38/* #define DEBUG_SPA */
39
40#ifdef DEBUG_SPA
41#define DSPA(x,y,z) debug_printf(x,y,z)
42#else
43#define DSPA(x,y,z)
44#endif
45
46/* Options specific to the spa authentication mechanism. */
47
48optionlist auth_spa_options[] = {
49 { "client_domain", opt_stringptr,
13a4b4c1 50 OPT_OFF(auth_spa_options_block, spa_domain) },
0756eb3c 51 { "client_password", opt_stringptr,
13a4b4c1 52 OPT_OFF(auth_spa_options_block, spa_password) },
0756eb3c 53 { "client_username", opt_stringptr,
13a4b4c1 54 OPT_OFF(auth_spa_options_block, spa_username) },
0756eb3c 55 { "server_password", opt_stringptr,
13a4b4c1 56 OPT_OFF(auth_spa_options_block, spa_serverpassword) }
0756eb3c
PH
57};
58
59/* Size of the options list. An extern variable has to be used so that its
60address can appear in the tables drtables.c. */
61
62int auth_spa_options_count =
63 sizeof(auth_spa_options)/sizeof(optionlist);
64
4c04137d 65/* Default private options block for the condition authentication method. */
0756eb3c
PH
66
67auth_spa_options_block auth_spa_option_defaults = {
68 NULL, /* spa_password */
69 NULL, /* spa_username */
70 NULL, /* spa_domain */
71 NULL /* spa_serverpassword (for server side use) */
72};
73
74
d185889f
JH
75#ifdef MACRO_PREDEF
76
77/* Dummy values */
78void auth_spa_init(auth_instance *ablock) {}
79int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
251b9eb4
JH
80int auth_spa_client(auth_instance *ablock, void * sx, int timeout,
81 uschar *buffer, int buffsize) {return 0;}
d185889f
JH
82
83#else /*!MACRO_PREDEF*/
84
85
86
87
0756eb3c
PH
88/*************************************************
89* Initialization entry point *
90*************************************************/
91
92/* Called for each instance, after its options have been read, to
93enable consistency checks to be done, or anything else that needs
94to be set up. */
95
96void
97auth_spa_init(auth_instance *ablock)
98{
99auth_spa_options_block *ob =
100 (auth_spa_options_block *)(ablock->options_block);
101
102/* The public name defaults to the authenticator name */
103
104if (ablock->public_name == NULL) ablock->public_name = ablock->name;
105
106/* Both username and password must be set for a client */
107
108if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
109 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n "
110 "one of client_username and client_password cannot be set without "
111 "the other", ablock->name);
112ablock->client = ob->spa_username != NULL;
113
114/* For a server we have just one option */
115
116ablock->server = ob->spa_serverpassword != NULL;
117}
118
119
120
121/*************************************************
122* Server entry point *
123*************************************************/
124
125/* For interface, see auths/README */
126
5903c6ff 127#define CVAL(buf,pos) ((US (buf))[pos])
0756eb3c
PH
128#define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
129#define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
130#define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
131
132int
133auth_spa_server(auth_instance *ablock, uschar *data)
134{
135auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
136uint8x lmRespData[24];
137uint8x ntRespData[24];
138SPAAuthRequest request;
139SPAAuthChallenge challenge;
140SPAAuthResponse response;
141SPAAuthResponse *responseptr = &response;
142uschar msgbuf[2048];
57aa14b2 143uschar *clearpass, *s;
0756eb3c 144
55c75993
PP
145/* send a 334, MS Exchange style, and grab the client's request,
146unless we already have it via an initial response. */
0756eb3c 147
99dbdcf4 148if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK)
0756eb3c 149 return FAIL;
0756eb3c 150
99dbdcf4 151if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0)
0756eb3c
PH
152 {
153 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
99dbdcf4 154 "request: %s\n", data);
0756eb3c
PH
155 return FAIL;
156 }
157
158/* create a challenge and send it back */
159
99dbdcf4
JH
160spa_build_auth_challenge(&request, &challenge);
161spa_bits_to_base64(msgbuf, US &challenge, spa_request_length(&challenge));
0756eb3c
PH
162
163if (auth_get_no64_data(&data, msgbuf) != OK)
0756eb3c 164 return FAIL;
0756eb3c
PH
165
166/* dump client response */
99dbdcf4 167if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
0756eb3c
PH
168 {
169 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
99dbdcf4 170 "response: %s\n", data);
0756eb3c
PH
171 return FAIL;
172 }
173
0756eb3c
PH
174/***************************************************************
175PH 07-Aug-2003: The original code here was this:
176
177Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
178 IVAL(&responseptr->uUser.offset,0),
179 SVAL(&responseptr->uUser.len,0)/2) );
180
181However, if the response data is too long, unicodeToString bombs out on
182an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
183idea. It's too messy to try to rework that function to return an error because
184it is called from a number of other places in the auth-spa.c module. Instead,
185since it is a very small function, I reproduce its code here, with a size check
186that causes failure if the size of msgbuf is exceeded. ****/
187
188 {
189 int i;
99dbdcf4 190 char * p = (CS responseptr) + IVAL(&responseptr->uUser.offset,0);
0756eb3c
PH
191 int len = SVAL(&responseptr->uUser.len,0)/2;
192
57aa14b2
JH
193 if (p + len*2 >= CS (responseptr+1))
194 {
195 DEBUG(D_auth)
196 debug_printf("auth_spa_server(): bad uUser spec in response\n");
197 return FAIL;
198 }
199
0756eb3c
PH
200 if (len + 1 >= sizeof(msgbuf)) return FAIL;
201 for (i = 0; i < len; ++i)
202 {
203 msgbuf[i] = *p & 0x7f;
204 p += 2;
205 }
206 msgbuf[i] = 0;
207 }
208
209/***************************************************************/
210
f78eb7c6 211/* Put the username in $auth1 and $1. The former is now the preferred variable;
f68fe5f6
PP
212the latter is the original variable. These have to be out of stack memory, and
213need to be available once known even if not authenticated, for error messages
214(server_set_id, which only makes it to authenticated_id if we return OK) */
f78eb7c6 215
f68fe5f6 216auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
0756eb3c
PH
217expand_nlength[1] = Ustrlen(msgbuf);
218expand_nmax = 1;
219
f78eb7c6
PH
220debug_print_string(ablock->server_debug_string); /* customized debug */
221
0756eb3c
PH
222/* look up password */
223
99dbdcf4 224if (!(clearpass = expand_string(ob->spa_serverpassword)))
8768d548 225 if (f.expand_string_forcedfail)
0756eb3c
PH
226 {
227 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
228 "expanding spa_serverpassword\n");
f68fe5f6 229 return FAIL;
0756eb3c
PH
230 }
231 else
232 {
233 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
234 "spa_serverpassword: %s\n", expand_string_message);
f68fe5f6 235 return DEFER;
0756eb3c 236 }
0756eb3c
PH
237
238/* create local hash copy */
239
99dbdcf4
JH
240spa_smb_encrypt(clearpass, challenge.challengeData, lmRespData);
241spa_smb_nt_encrypt(clearpass, challenge.challengeData, ntRespData);
0756eb3c
PH
242
243/* compare NT hash (LM may not be available) */
244
57aa14b2
JH
245s = (US responseptr) + IVAL(&responseptr->ntResponse.offset,0);
246if (s + 24 >= US (responseptr+1))
247 {
248 DEBUG(D_auth)
249 debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
250 return FAIL;
251 }
252
253if (memcmp(ntRespData, s, 24) == 0)
99dbdcf4 254 return auth_check_serv_cond(ablock); /* success. we have a winner. */
16ff981e
PH
255
256 /* Expand server_condition as an authorization check (PH) */
0756eb3c 257
f68fe5f6 258return FAIL;
0756eb3c
PH
259}
260
261
262/*************************************************
263* Client entry point *
264*************************************************/
265
266/* For interface, see auths/README */
267
268int
269auth_spa_client(
270 auth_instance *ablock, /* authenticator block */
251b9eb4 271 void * sx, /* connection */
0756eb3c
PH
272 int timeout, /* command timeout */
273 uschar *buffer, /* buffer for reading response */
274 int buffsize) /* size of buffer */
275{
4e910c01
JH
276auth_spa_options_block *ob =
277 (auth_spa_options_block *)(ablock->options_block);
278SPAAuthRequest request;
279SPAAuthChallenge challenge;
280SPAAuthResponse response;
281char msgbuf[2048];
282char *domain = NULL;
283char *username, *password;
284
285/* Code added by PH to expand the options */
286
287*buffer = 0; /* Default no message when cancelled */
288
289if (!(username = CS expand_string(ob->spa_username)))
290 {
8768d548 291 if (f.expand_string_forcedfail) return CANCELLED;
4e910c01
JH
292 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
293 "authenticator: %s", ob->spa_username, ablock->name,
294 expand_string_message);
295 return ERROR;
296 }
297
298if (!(password = CS expand_string(ob->spa_password)))
299 {
8768d548 300 if (f.expand_string_forcedfail) return CANCELLED;
4e910c01
JH
301 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
302 "authenticator: %s", ob->spa_password, ablock->name,
303 expand_string_message);
304 return ERROR;
305 }
306
307if (ob->spa_domain)
4e910c01
JH
308 if (!(domain = CS expand_string(ob->spa_domain)))
309 {
8768d548 310 if (f.expand_string_forcedfail) return CANCELLED;
4e910c01
JH
311 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
312 "authenticator: %s", ob->spa_domain, ablock->name,
313 expand_string_message);
314 return ERROR;
315 }
4e910c01
JH
316
317/* Original code */
318
251b9eb4 319if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
4e910c01
JH
320 return FAIL_SEND;
321
322/* wait for the 3XX OK message */
251b9eb4 323if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
4e910c01
JH
324 return FAIL;
325
326DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
327
99dbdcf4
JH
328spa_build_auth_request(&request, CS username, domain);
329spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request));
4e910c01
JH
330
331DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
332
333/* send the encrypted password */
251b9eb4 334if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
4e910c01
JH
335 return FAIL_SEND;
336
337/* wait for the auth challenge */
251b9eb4 338if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
4e910c01
JH
339 return FAIL;
340
341/* convert the challenge into the challenge struct */
342DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
99dbdcf4 343spa_base64_to_bits(CS (&challenge), sizeof(challenge), CCS (buffer + 4));
4e910c01 344
99dbdcf4
JH
345spa_build_auth_response(&challenge, &response, CS username, CS password);
346spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response));
4e910c01
JH
347DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
348
349/* send the challenge response */
251b9eb4 350if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
4e910c01
JH
351 return FAIL_SEND;
352
353/* If we receive a success response from the server, authentication
354has succeeded. There may be more data to send, but is there any point
355in provoking an error here? */
356
251b9eb4 357if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
4e910c01
JH
358 return OK;
359
360/* Not a success response. If errno != 0 there is some kind of transmission
361error. Otherwise, check the response code in the buffer. If it starts with
362'3', more data is expected. */
363
364if (errno != 0 || buffer[0] != '3')
365 return FAIL;
366
367return FAIL;
0756eb3c
PH
368}
369
d185889f 370#endif /*!MACRO_PREDEF*/
0756eb3c 371/* End of spa.c */