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