Copyright updates:
[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];
143uschar *clearpass;
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
55c75993
PP
148if ((*data == '\0') &&
149 (auth_get_no64_data(&data, US"NTLM supported") != OK))
0756eb3c
PH
150 {
151 /* something borked */
152 return FAIL;
153 }
154
5903c6ff 155if (spa_base64_to_bits(CS (&request), sizeof(request), CCS (data)) < 0)
0756eb3c
PH
156 {
157 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
158 "request: %s\n", data);
159 return FAIL;
160 }
161
162/* create a challenge and send it back */
163
164spa_build_auth_challenge(&request,&challenge);
165spa_bits_to_base64 (msgbuf, (unsigned char*)&challenge,
166 spa_request_length(&challenge));
167
168if (auth_get_no64_data(&data, msgbuf) != OK)
169 {
170 /* something borked */
171 return FAIL;
172 }
173
174/* dump client response */
5903c6ff 175if (spa_base64_to_bits(CS (&response), sizeof(response), CCS (data)) < 0)
0756eb3c
PH
176 {
177 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
178 "response: %s\n", data);
179 return FAIL;
180 }
181
0756eb3c
PH
182/***************************************************************
183PH 07-Aug-2003: The original code here was this:
184
185Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
186 IVAL(&responseptr->uUser.offset,0),
187 SVAL(&responseptr->uUser.len,0)/2) );
188
189However, if the response data is too long, unicodeToString bombs out on
190an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
191idea. It's too messy to try to rework that function to return an error because
192it is called from a number of other places in the auth-spa.c module. Instead,
193since it is a very small function, I reproduce its code here, with a size check
194that causes failure if the size of msgbuf is exceeded. ****/
195
196 {
197 int i;
198 char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
199 int len = SVAL(&responseptr->uUser.len,0)/2;
200
201 if (len + 1 >= sizeof(msgbuf)) return FAIL;
202 for (i = 0; i < len; ++i)
203 {
204 msgbuf[i] = *p & 0x7f;
205 p += 2;
206 }
207 msgbuf[i] = 0;
208 }
209
210/***************************************************************/
211
f78eb7c6 212/* Put the username in $auth1 and $1. The former is now the preferred variable;
f68fe5f6
PP
213the latter is the original variable. These have to be out of stack memory, and
214need to be available once known even if not authenticated, for error messages
215(server_set_id, which only makes it to authenticated_id if we return OK) */
f78eb7c6 216
f68fe5f6 217auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
0756eb3c
PH
218expand_nlength[1] = Ustrlen(msgbuf);
219expand_nmax = 1;
220
f78eb7c6
PH
221debug_print_string(ablock->server_debug_string); /* customized debug */
222
0756eb3c
PH
223/* look up password */
224
225clearpass = expand_string(ob->spa_serverpassword);
226if (clearpass == NULL)
227 {
8768d548 228 if (f.expand_string_forcedfail)
0756eb3c
PH
229 {
230 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
231 "expanding spa_serverpassword\n");
f68fe5f6 232 return FAIL;
0756eb3c
PH
233 }
234 else
235 {
236 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
237 "spa_serverpassword: %s\n", expand_string_message);
f68fe5f6 238 return DEFER;
0756eb3c
PH
239 }
240 }
241
242/* create local hash copy */
243
244spa_smb_encrypt (clearpass, challenge.challengeData, lmRespData);
245spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData);
246
247/* compare NT hash (LM may not be available) */
248
249if (memcmp(ntRespData,
250 ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0),
251 24) == 0)
252 /* success. we have a winner. */
08488c86 253 {
f68fe5f6 254 return auth_check_serv_cond(ablock);
08488c86 255 }
16ff981e
PH
256
257 /* Expand server_condition as an authorization check (PH) */
0756eb3c 258
f68fe5f6 259return FAIL;
0756eb3c
PH
260}
261
262
263/*************************************************
264* Client entry point *
265*************************************************/
266
267/* For interface, see auths/README */
268
269int
270auth_spa_client(
271 auth_instance *ablock, /* authenticator block */
251b9eb4 272 void * sx, /* connection */
0756eb3c
PH
273 int timeout, /* command timeout */
274 uschar *buffer, /* buffer for reading response */
275 int buffsize) /* size of buffer */
276{
4e910c01
JH
277auth_spa_options_block *ob =
278 (auth_spa_options_block *)(ablock->options_block);
279SPAAuthRequest request;
280SPAAuthChallenge challenge;
281SPAAuthResponse response;
282char msgbuf[2048];
283char *domain = NULL;
284char *username, *password;
285
286/* Code added by PH to expand the options */
287
288*buffer = 0; /* Default no message when cancelled */
289
290if (!(username = CS expand_string(ob->spa_username)))
291 {
8768d548 292 if (f.expand_string_forcedfail) return CANCELLED;
4e910c01
JH
293 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
294 "authenticator: %s", ob->spa_username, ablock->name,
295 expand_string_message);
296 return ERROR;
297 }
298
299if (!(password = CS expand_string(ob->spa_password)))
300 {
8768d548 301 if (f.expand_string_forcedfail) return CANCELLED;
4e910c01
JH
302 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
303 "authenticator: %s", ob->spa_password, ablock->name,
304 expand_string_message);
305 return ERROR;
306 }
307
308if (ob->spa_domain)
4e910c01
JH
309 if (!(domain = CS expand_string(ob->spa_domain)))
310 {
8768d548 311 if (f.expand_string_forcedfail) return CANCELLED;
4e910c01
JH
312 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
313 "authenticator: %s", ob->spa_domain, ablock->name,
314 expand_string_message);
315 return ERROR;
316 }
4e910c01
JH
317
318/* Original code */
319
251b9eb4 320if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
4e910c01
JH
321 return FAIL_SEND;
322
323/* wait for the 3XX OK message */
251b9eb4 324if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
4e910c01
JH
325 return FAIL;
326
327DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
328
329spa_build_auth_request (&request, CS username, domain);
330spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
331 spa_request_length(&request));
332
333DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
334
335/* send the encrypted password */
251b9eb4 336if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
4e910c01
JH
337 return FAIL_SEND;
338
339/* wait for the auth challenge */
251b9eb4 340if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
4e910c01
JH
341 return FAIL;
342
343/* convert the challenge into the challenge struct */
344DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
5903c6ff 345spa_base64_to_bits (CS (&challenge), sizeof(challenge), CCS (buffer + 4));
4e910c01
JH
346
347spa_build_auth_response (&challenge, &response, CS username, CS password);
348spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
349 spa_request_length(&response));
350DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
351
352/* send the challenge response */
251b9eb4 353if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
4e910c01
JH
354 return FAIL_SEND;
355
356/* If we receive a success response from the server, authentication
357has succeeded. There may be more data to send, but is there any point
358in provoking an error here? */
359
251b9eb4 360if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
4e910c01
JH
361 return OK;
362
363/* Not a success response. If errno != 0 there is some kind of transmission
364error. Otherwise, check the response code in the buffer. If it starts with
365'3', more data is expected. */
366
367if (errno != 0 || buffer[0] != '3')
368 return FAIL;
369
370return FAIL;
0756eb3c
PH
371}
372
d185889f 373#endif /*!MACRO_PREDEF*/
0756eb3c 374/* End of spa.c */