1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
10 /* This file, which provides support for Microsoft's Secure Password
11 Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA
12 server support. I (PH) have only modified it in very trivial ways.
15 http://www.innovation.ch/java/ntlm.html
16 http://www.kuro5hin.org/story/2002/4/28/1436/66154
17 http://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/%5bMS-SMTP%5d.pdf
19 * It seems that some systems have existing but different definitions of some
20 * of the following types. I received a complaint about "int16" causing
21 * compilation problems. So I (PH) have renamed them all, to be on the safe
22 * side, by adding 'x' on the end. See auths/auth-spa.h.
24 * typedef signed short int16;
25 * typedef unsigned short uint16;
26 * typedef unsigned uint32;
27 * typedef unsigned char uint8;
29 07-August-2003: PH: Patched up the code to avoid assert bombouts for stupid
30 input data. Find appropriate comment by grepping for "PH".
31 16-October-2006: PH: Added a call to auth_check_serv_cond() at the end
32 05-June-2010: PP: handle SASL initial response
38 #ifdef AUTH_SPA /* Remainder of file */
41 /* #define DEBUG_SPA */
44 #define DSPA(x,y,z) debug_printf(x,y,z)
49 /* Options specific to the spa authentication mechanism. */
51 optionlist auth_spa_options[] = {
52 { "client_domain", opt_stringptr,
53 OPT_OFF(auth_spa_options_block, spa_domain) },
54 { "client_password", opt_stringptr,
55 OPT_OFF(auth_spa_options_block, spa_password) },
56 { "client_username", opt_stringptr,
57 OPT_OFF(auth_spa_options_block, spa_username) },
58 { "server_password", opt_stringptr,
59 OPT_OFF(auth_spa_options_block, spa_serverpassword) }
62 /* Size of the options list. An extern variable has to be used so that its
63 address can appear in the tables drtables.c. */
65 int auth_spa_options_count =
66 sizeof(auth_spa_options)/sizeof(optionlist);
68 /* Default private options block for the condition authentication method. */
70 auth_spa_options_block auth_spa_option_defaults = {
71 NULL, /* spa_password */
72 NULL, /* spa_username */
73 NULL, /* spa_domain */
74 NULL /* spa_serverpassword (for server side use) */
81 void auth_spa_init(driver_instance *ablock) {}
82 int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
83 int auth_spa_client(auth_instance *ablock, void * sx, int timeout,
84 uschar *buffer, int buffsize) {return 0;}
86 #else /*!MACRO_PREDEF*/
91 /*************************************************
92 * Initialization entry point *
93 *************************************************/
95 /* Called for each instance, after its options have been read, to
96 enable consistency checks to be done, or anything else that needs
100 auth_spa_init(driver_instance * a)
102 auth_instance * ablock = (auth_instance *)a;
103 auth_spa_options_block * ob = a->options_block;
105 /* The public name defaults to the authenticator name */
107 if (!ablock->public_name)
108 ablock->public_name = ablock->drinst.name;
110 /* Both username and password must be set for a client */
112 if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
113 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n "
114 "one of client_username and client_password cannot be set without "
115 "the other", ablock->drinst.name);
116 ablock->client = ob->spa_username != NULL;
118 /* For a server we have just one option */
120 ablock->server = ob->spa_serverpassword != NULL;
125 /*************************************************
126 * Server entry point *
127 *************************************************/
129 /* For interface, see auths/README */
131 #define CVAL(buf,pos) ((US (buf))[pos])
132 #define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
133 #define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
134 #define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
137 auth_spa_server(auth_instance *ablock, uschar *data)
139 auth_spa_options_block * ob = ablock->drinst.options_block;
140 uint8x lmRespData[24];
141 uint8x ntRespData[24];
142 SPAAuthRequest request;
143 SPAAuthChallenge challenge;
144 SPAAuthResponse response;
145 SPAAuthResponse *responseptr = &response;
147 uschar *clearpass, *s;
150 /* send a 334, MS Exchange style, and grab the client's request,
151 unless we already have it via an initial response. */
153 if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK)
156 if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0)
158 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
159 "request: %s\n", data);
163 /* create a challenge and send it back */
165 spa_build_auth_challenge(&request, &challenge);
166 spa_bits_to_base64(msgbuf, US &challenge, spa_request_length(&challenge));
168 if (auth_get_no64_data(&data, msgbuf) != OK)
171 /* dump client response */
172 if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
174 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
175 "response: %s\n", data);
179 /***************************************************************
180 PH 07-Aug-2003: The original code here was this:
182 Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
183 IVAL(&responseptr->uUser.offset,0),
184 SVAL(&responseptr->uUser.len,0)/2) );
186 However, if the response data is too long, unicodeToString bombs out on
187 an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
188 idea. It's too messy to try to rework that function to return an error because
189 it is called from a number of other places in the auth-spa.c module. Instead,
190 since it is a very small function, I reproduce its code here, with a size check
191 that causes failure if the size of msgbuf is exceeded. ****/
196 int len = SVAL(&responseptr->uUser.len,0)/2;
198 if ( (off = IVAL(&responseptr->uUser.offset,0)) >= sizeof(SPAAuthResponse)
199 || len >= sizeof(responseptr->buf.buffer)/2
200 || (p = (CS responseptr) + off) + len*2 >= CS (responseptr+1)
204 debug_printf("auth_spa_server(): bad uUser spec in response\n");
208 if (len + 1 >= sizeof(msgbuf)) return FAIL;
209 for (i = 0; i < len; ++i)
211 msgbuf[i] = *p & 0x7f;
217 /***************************************************************/
219 /* Put the username in $auth1 and $1. The former is now the preferred variable;
220 the latter is the original variable. These have to be out of stack memory, and
221 need to be available once known even if not authenticated, for error messages
222 (server_set_id, which only makes it to authenticated_id if we return OK) */
224 auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
225 expand_nlength[1] = Ustrlen(msgbuf);
228 debug_print_string(ablock->server_debug_string); /* customized debug */
230 /* look up password */
232 if (!(clearpass = expand_string(ob->spa_serverpassword)))
233 if (f.expand_string_forcedfail)
235 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
236 "expanding spa_serverpassword\n");
241 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
242 "spa_serverpassword: %s\n", expand_string_message);
246 /* create local hash copy */
248 spa_smb_encrypt(clearpass, challenge.challengeData, lmRespData);
249 spa_smb_nt_encrypt(clearpass, challenge.challengeData, ntRespData);
251 /* compare NT hash (LM may not be available) */
253 off = IVAL(&responseptr->ntResponse.offset,0);
254 if (off >= sizeof(SPAAuthResponse) - 24)
257 debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
260 s = (US responseptr) + off;
262 if (memcmp(ntRespData, s, 24) == 0)
263 return auth_check_serv_cond(ablock); /* success. we have a winner. */
265 /* Expand server_condition as an authorization check (PH) */
271 /*************************************************
272 * Client entry point *
273 *************************************************/
275 /* For interface, see auths/README */
279 auth_instance *ablock, /* authenticator block */
280 void * sx, /* connection */
281 int timeout, /* command timeout */
282 uschar *buffer, /* buffer for reading response */
283 int buffsize) /* size of buffer */
285 auth_spa_options_block * ob = ablock->drinst.options_block;
286 const uschar * auname = ablock->drinst.name;
287 SPAAuthRequest request;
288 SPAAuthChallenge challenge;
289 SPAAuthResponse response;
291 uschar * domain = NULL, * username, * password;
293 /* Code added by PH to expand the options */
295 *buffer = 0; /* Default no message when cancelled */
297 if (!(username = expand_string(ob->spa_username)))
299 if (f.expand_string_forcedfail) return CANCELLED;
300 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
301 "authenticator: %s", ob->spa_username, auname,
302 expand_string_message);
306 if (!(password = expand_string(ob->spa_password)))
308 if (f.expand_string_forcedfail) return CANCELLED;
309 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
310 "authenticator: %s", ob->spa_password, auname,
311 expand_string_message);
316 if (!(domain = expand_string(ob->spa_domain)))
318 if (f.expand_string_forcedfail) return CANCELLED;
319 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
320 "authenticator: %s", ob->spa_domain, auname,
321 expand_string_message);
327 if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
330 /* wait for the 3XX OK message */
331 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
334 DSPA("\n\n%s authenticator: using domain %s\n\n", auname, domain);
336 spa_build_auth_request(&request, username, domain);
337 spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request));
339 DSPA("\n\n%s authenticator: sending request (%s)\n\n", auname, msgbuf);
341 /* send the encrypted password */
342 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
345 /* wait for the auth challenge */
346 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
349 /* convert the challenge into the challenge struct */
350 DSPA("\n\n%s authenticator: challenge (%s)\n\n", auname, buffer + 4);
351 spa_base64_to_bits(CS (&challenge), sizeof(challenge), CCS (buffer + 4));
353 spa_build_auth_response(&challenge, &response, username, password);
354 spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response));
355 DSPA("\n\n%s authenticator: challenge response (%s)\n\n", auname, msgbuf);
357 /* send the challenge response */
358 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
361 /* If we receive a success response from the server, authentication
362 has succeeded. There may be more data to send, but is there any point
363 in provoking an error here? */
365 if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
368 /* Not a success response. If errno != 0 there is some kind of transmission
369 error. Otherwise, check the response code in the buffer. If it starts with
370 '3', more data is expected. */
372 if (errno != 0 || buffer[0] != '3')
378 #endif /*!MACRO_PREDEF*/