debian experimental exim-daemon-heavy config
[exim.git] / src / src / auths / spa.c
... / ...
CommitLineData
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
5/* Copyright (c) University of Cambridge 1995 - 2018 */
6/* Copyright (c) The Exim Maintainers 2020 */
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
16 http://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/%5bMS-SMTP%5d.pdf
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
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
3105-June-2010: PP: handle SASL initial response
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,
50 OPT_OFF(auth_spa_options_block, spa_domain) },
51 { "client_password", opt_stringptr,
52 OPT_OFF(auth_spa_options_block, spa_password) },
53 { "client_username", opt_stringptr,
54 OPT_OFF(auth_spa_options_block, spa_username) },
55 { "server_password", opt_stringptr,
56 OPT_OFF(auth_spa_options_block, spa_serverpassword) }
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
65/* Default private options block for the condition authentication method. */
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
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;}
80int auth_spa_client(auth_instance *ablock, void * sx, int timeout,
81 uschar *buffer, int buffsize) {return 0;}
82
83#else /*!MACRO_PREDEF*/
84
85
86
87
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
127#define CVAL(buf,pos) ((US (buf))[pos])
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, *s;
144unsigned off;
145
146/* send a 334, MS Exchange style, and grab the client's request,
147unless we already have it via an initial response. */
148
149if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK)
150 return FAIL;
151
152if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0)
153 {
154 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
155 "request: %s\n", data);
156 return FAIL;
157 }
158
159/* create a challenge and send it back */
160
161spa_build_auth_challenge(&request, &challenge);
162spa_bits_to_base64(msgbuf, US &challenge, spa_request_length(&challenge));
163
164if (auth_get_no64_data(&data, msgbuf) != OK)
165 return FAIL;
166
167/* dump client response */
168if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
169 {
170 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
171 "response: %s\n", data);
172 return FAIL;
173 }
174
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;
191 char * p;
192 int len = SVAL(&responseptr->uUser.len,0)/2;
193
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 )
198 {
199 DEBUG(D_auth)
200 debug_printf("auth_spa_server(): bad uUser spec in response\n");
201 return FAIL;
202 }
203
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
215/* Put the username in $auth1 and $1. The former is now the preferred variable;
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) */
219
220auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
221expand_nlength[1] = Ustrlen(msgbuf);
222expand_nmax = 1;
223
224debug_print_string(ablock->server_debug_string); /* customized debug */
225
226/* look up password */
227
228if (!(clearpass = expand_string(ob->spa_serverpassword)))
229 if (f.expand_string_forcedfail)
230 {
231 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
232 "expanding spa_serverpassword\n");
233 return FAIL;
234 }
235 else
236 {
237 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
238 "spa_serverpassword: %s\n", expand_string_message);
239 return DEFER;
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
249off = IVAL(&responseptr->ntResponse.offset,0);
250if (off >= sizeof(SPAAuthResponse) - 24)
251 {
252 DEBUG(D_auth)
253 debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
254 return FAIL;
255 }
256s = (US responseptr) + off;
257
258if (memcmp(ntRespData, s, 24) == 0)
259 return auth_check_serv_cond(ablock); /* success. we have a winner. */
260
261 /* Expand server_condition as an authorization check (PH) */
262
263return FAIL;
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 */
276 void * sx, /* connection */
277 int timeout, /* command timeout */
278 uschar *buffer, /* buffer for reading response */
279 int buffsize) /* size of buffer */
280{
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 {
296 if (f.expand_string_forcedfail) return CANCELLED;
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 {
305 if (f.expand_string_forcedfail) return CANCELLED;
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)
313 if (!(domain = CS expand_string(ob->spa_domain)))
314 {
315 if (f.expand_string_forcedfail) return CANCELLED;
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 }
321
322/* Original code */
323
324if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
325 return FAIL_SEND;
326
327/* wait for the 3XX OK message */
328if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
329 return FAIL;
330
331DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
332
333spa_build_auth_request(&request, CS username, domain);
334spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request));
335
336DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
337
338/* send the encrypted password */
339if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
340 return FAIL_SEND;
341
342/* wait for the auth challenge */
343if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
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);
348spa_base64_to_bits(CS (&challenge), sizeof(challenge), CCS (buffer + 4));
349
350spa_build_auth_response(&challenge, &response, CS username, CS password);
351spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response));
352DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
353
354/* send the challenge response */
355if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
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
362if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
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;
373}
374
375#endif /*!MACRO_PREDEF*/
376/* End of spa.c */