Use dedicated union member for option offsets
[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 */
0756eb3c
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
8/* This file, which provides support for Microsoft's Secure Password
9Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA
10server support. I (PH) have only modified it in very trivial ways.
11
12References:
13 http://www.innovation.ch/java/ntlm.html
14 http://www.kuro5hin.org/story/2002/4/28/1436/66154
55c75993 15 http://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/%5bMS-SMTP%5d.pdf
0756eb3c
PH
16
17 * It seems that some systems have existing but different definitions of some
18 * of the following types. I received a complaint about "int16" causing
19 * compilation problems. So I (PH) have renamed them all, to be on the safe
20 * side, by adding 'x' on the end. See auths/auth-spa.h.
21
22 * typedef signed short int16;
23 * typedef unsigned short uint16;
24 * typedef unsigned uint32;
25 * typedef unsigned char uint8;
26
16ff981e
PH
2707-August-2003: PH: Patched up the code to avoid assert bombouts for stupid
28 input data. Find appropriate comment by grepping for "PH".
2916-October-2006: PH: Added a call to auth_check_serv_cond() at the end
55c75993 3005-June-2010: PP: handle SASL initial response
0756eb3c
PH
31*/
32
33
34#include "../exim.h"
35#include "spa.h"
36
37/* #define DEBUG_SPA */
38
39#ifdef DEBUG_SPA
40#define DSPA(x,y,z) debug_printf(x,y,z)
41#else
42#define DSPA(x,y,z)
43#endif
44
45/* Options specific to the spa authentication mechanism. */
46
47optionlist auth_spa_options[] = {
48 { "client_domain", opt_stringptr,
13a4b4c1 49 OPT_OFF(auth_spa_options_block, spa_domain) },
0756eb3c 50 { "client_password", opt_stringptr,
13a4b4c1 51 OPT_OFF(auth_spa_options_block, spa_password) },
0756eb3c 52 { "client_username", opt_stringptr,
13a4b4c1 53 OPT_OFF(auth_spa_options_block, spa_username) },
0756eb3c 54 { "server_password", opt_stringptr,
13a4b4c1 55 OPT_OFF(auth_spa_options_block, spa_serverpassword) }
0756eb3c
PH
56};
57
58/* Size of the options list. An extern variable has to be used so that its
59address can appear in the tables drtables.c. */
60
61int auth_spa_options_count =
62 sizeof(auth_spa_options)/sizeof(optionlist);
63
4c04137d 64/* Default private options block for the condition authentication method. */
0756eb3c
PH
65
66auth_spa_options_block auth_spa_option_defaults = {
67 NULL, /* spa_password */
68 NULL, /* spa_username */
69 NULL, /* spa_domain */
70 NULL /* spa_serverpassword (for server side use) */
71};
72
73
d185889f
JH
74#ifdef MACRO_PREDEF
75
76/* Dummy values */
77void auth_spa_init(auth_instance *ablock) {}
78int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
251b9eb4
JH
79int auth_spa_client(auth_instance *ablock, void * sx, int timeout,
80 uschar *buffer, int buffsize) {return 0;}
d185889f
JH
81
82#else /*!MACRO_PREDEF*/
83
84
85
86
0756eb3c
PH
87/*************************************************
88* Initialization entry point *
89*************************************************/
90
91/* Called for each instance, after its options have been read, to
92enable consistency checks to be done, or anything else that needs
93to be set up. */
94
95void
96auth_spa_init(auth_instance *ablock)
97{
98auth_spa_options_block *ob =
99 (auth_spa_options_block *)(ablock->options_block);
100
101/* The public name defaults to the authenticator name */
102
103if (ablock->public_name == NULL) ablock->public_name = ablock->name;
104
105/* Both username and password must be set for a client */
106
107if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
108 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n "
109 "one of client_username and client_password cannot be set without "
110 "the other", ablock->name);
111ablock->client = ob->spa_username != NULL;
112
113/* For a server we have just one option */
114
115ablock->server = ob->spa_serverpassword != NULL;
116}
117
118
119
120/*************************************************
121* Server entry point *
122*************************************************/
123
124/* For interface, see auths/README */
125
5903c6ff 126#define CVAL(buf,pos) ((US (buf))[pos])
0756eb3c
PH
127#define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
128#define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
129#define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
130
131int
132auth_spa_server(auth_instance *ablock, uschar *data)
133{
134auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
135uint8x lmRespData[24];
136uint8x ntRespData[24];
137SPAAuthRequest request;
138SPAAuthChallenge challenge;
139SPAAuthResponse response;
140SPAAuthResponse *responseptr = &response;
141uschar msgbuf[2048];
142uschar *clearpass;
143
55c75993
PP
144/* send a 334, MS Exchange style, and grab the client's request,
145unless we already have it via an initial response. */
0756eb3c 146
55c75993
PP
147if ((*data == '\0') &&
148 (auth_get_no64_data(&data, US"NTLM supported") != OK))
0756eb3c
PH
149 {
150 /* something borked */
151 return FAIL;
152 }
153
5903c6ff 154if (spa_base64_to_bits(CS (&request), sizeof(request), CCS (data)) < 0)
0756eb3c
PH
155 {
156 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
157 "request: %s\n", data);
158 return FAIL;
159 }
160
161/* create a challenge and send it back */
162
163spa_build_auth_challenge(&request,&challenge);
164spa_bits_to_base64 (msgbuf, (unsigned char*)&challenge,
165 spa_request_length(&challenge));
166
167if (auth_get_no64_data(&data, msgbuf) != OK)
168 {
169 /* something borked */
170 return FAIL;
171 }
172
173/* dump client response */
5903c6ff 174if (spa_base64_to_bits(CS (&response), sizeof(response), CCS (data)) < 0)
0756eb3c
PH
175 {
176 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
177 "response: %s\n", data);
178 return FAIL;
179 }
180
0756eb3c
PH
181/***************************************************************
182PH 07-Aug-2003: The original code here was this:
183
184Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
185 IVAL(&responseptr->uUser.offset,0),
186 SVAL(&responseptr->uUser.len,0)/2) );
187
188However, if the response data is too long, unicodeToString bombs out on
189an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
190idea. It's too messy to try to rework that function to return an error because
191it is called from a number of other places in the auth-spa.c module. Instead,
192since it is a very small function, I reproduce its code here, with a size check
193that causes failure if the size of msgbuf is exceeded. ****/
194
195 {
196 int i;
197 char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
198 int len = SVAL(&responseptr->uUser.len,0)/2;
199
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
224clearpass = expand_string(ob->spa_serverpassword);
225if (clearpass == NULL)
226 {
8768d548 227 if (f.expand_string_forcedfail)
0756eb3c
PH
228 {
229 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
230 "expanding spa_serverpassword\n");
f68fe5f6 231 return FAIL;
0756eb3c
PH
232 }
233 else
234 {
235 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
236 "spa_serverpassword: %s\n", expand_string_message);
f68fe5f6 237 return DEFER;
0756eb3c
PH
238 }
239 }
240
241/* create local hash copy */
242
243spa_smb_encrypt (clearpass, challenge.challengeData, lmRespData);
244spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData);
245
246/* compare NT hash (LM may not be available) */
247
248if (memcmp(ntRespData,
249 ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0),
250 24) == 0)
251 /* success. we have a winner. */
08488c86 252 {
f68fe5f6 253 return auth_check_serv_cond(ablock);
08488c86 254 }
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
328spa_build_auth_request (&request, CS username, domain);
329spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
330 spa_request_length(&request));
331
332DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
333
334/* send the encrypted password */
251b9eb4 335if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
4e910c01
JH
336 return FAIL_SEND;
337
338/* wait for the auth challenge */
251b9eb4 339if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
4e910c01
JH
340 return FAIL;
341
342/* convert the challenge into the challenge struct */
343DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
5903c6ff 344spa_base64_to_bits (CS (&challenge), sizeof(challenge), CCS (buffer + 4));
4e910c01
JH
345
346spa_build_auth_response (&challenge, &response, CS username, CS password);
347spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
348 spa_request_length(&response));
349DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
350
351/* send the challenge response */
251b9eb4 352if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
4e910c01
JH
353 return FAIL_SEND;
354
355/* If we receive a success response from the server, authentication
356has succeeded. There may be more data to send, but is there any point
357in provoking an error here? */
358
251b9eb4 359if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
4e910c01
JH
360 return OK;
361
362/* Not a success response. If errno != 0 there is some kind of transmission
363error. Otherwise, check the response code in the buffer. If it starts with
364'3', more data is expected. */
365
366if (errno != 0 || buffer[0] != '3')
367 return FAIL;
368
369return FAIL;
0756eb3c
PH
370}
371
d185889f 372#endif /*!MACRO_PREDEF*/
0756eb3c 373/* End of spa.c */