1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
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 /* 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
39 /* #define DEBUG_SPA */
42 #define DSPA(x,y,z) debug_printf(x,y,z)
47 /* Options specific to the spa authentication mechanism. */
49 optionlist auth_spa_options[] = {
50 { "client_domain", opt_stringptr,
51 OPT_OFF(auth_spa_options_block, spa_domain) },
52 { "client_password", opt_stringptr,
53 OPT_OFF(auth_spa_options_block, spa_password) },
54 { "client_username", opt_stringptr,
55 OPT_OFF(auth_spa_options_block, spa_username) },
56 { "server_password", opt_stringptr,
57 OPT_OFF(auth_spa_options_block, spa_serverpassword) }
60 /* Size of the options list. An extern variable has to be used so that its
61 address can appear in the tables drtables.c. */
63 int auth_spa_options_count =
64 sizeof(auth_spa_options)/sizeof(optionlist);
66 /* Default private options block for the condition authentication method. */
68 auth_spa_options_block auth_spa_option_defaults = {
69 NULL, /* spa_password */
70 NULL, /* spa_username */
71 NULL, /* spa_domain */
72 NULL /* spa_serverpassword (for server side use) */
79 void auth_spa_init(auth_instance *ablock) {}
80 int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
81 int auth_spa_client(auth_instance *ablock, void * sx, int timeout,
82 uschar *buffer, int buffsize) {return 0;}
84 #else /*!MACRO_PREDEF*/
89 /*************************************************
90 * Initialization entry point *
91 *************************************************/
93 /* Called for each instance, after its options have been read, to
94 enable consistency checks to be done, or anything else that needs
98 auth_spa_init(auth_instance *ablock)
100 auth_spa_options_block *ob =
101 (auth_spa_options_block *)(ablock->options_block);
103 /* The public name defaults to the authenticator name */
105 if (ablock->public_name == NULL) ablock->public_name = ablock->name;
107 /* Both username and password must be set for a client */
109 if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
110 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n "
111 "one of client_username and client_password cannot be set without "
112 "the other", ablock->name);
113 ablock->client = ob->spa_username != NULL;
115 /* For a server we have just one option */
117 ablock->server = ob->spa_serverpassword != NULL;
122 /*************************************************
123 * Server entry point *
124 *************************************************/
126 /* For interface, see auths/README */
128 #define CVAL(buf,pos) ((US (buf))[pos])
129 #define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
130 #define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
131 #define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
134 auth_spa_server(auth_instance *ablock, uschar *data)
136 auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
137 uint8x lmRespData[24];
138 uint8x ntRespData[24];
139 SPAAuthRequest request;
140 SPAAuthChallenge challenge;
141 SPAAuthResponse response;
142 SPAAuthResponse *responseptr = &response;
144 uschar *clearpass, *s;
147 /* send a 334, MS Exchange style, and grab the client's request,
148 unless we already have it via an initial response. */
150 if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK)
153 if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0)
155 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
156 "request: %s\n", data);
160 /* create a challenge and send it back */
162 spa_build_auth_challenge(&request, &challenge);
163 spa_bits_to_base64(msgbuf, US &challenge, spa_request_length(&challenge));
165 if (auth_get_no64_data(&data, msgbuf) != OK)
168 /* dump client response */
169 if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
171 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
172 "response: %s\n", data);
176 /***************************************************************
177 PH 07-Aug-2003: The original code here was this:
179 Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
180 IVAL(&responseptr->uUser.offset,0),
181 SVAL(&responseptr->uUser.len,0)/2) );
183 However, if the response data is too long, unicodeToString bombs out on
184 an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
185 idea. It's too messy to try to rework that function to return an error because
186 it is called from a number of other places in the auth-spa.c module. Instead,
187 since it is a very small function, I reproduce its code here, with a size check
188 that causes failure if the size of msgbuf is exceeded. ****/
193 int len = SVAL(&responseptr->uUser.len,0)/2;
195 if ( (off = IVAL(&responseptr->uUser.offset,0)) >= sizeof(SPAAuthResponse)
196 || len >= sizeof(responseptr->buffer)/2
197 || (p = (CS responseptr) + off) + len*2 >= CS (responseptr+1)
201 debug_printf("auth_spa_server(): bad uUser spec in response\n");
205 if (len + 1 >= sizeof(msgbuf)) return FAIL;
206 for (i = 0; i < len; ++i)
208 msgbuf[i] = *p & 0x7f;
214 /***************************************************************/
216 /* Put the username in $auth1 and $1. The former is now the preferred variable;
217 the latter is the original variable. These have to be out of stack memory, and
218 need to be available once known even if not authenticated, for error messages
219 (server_set_id, which only makes it to authenticated_id if we return OK) */
221 auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
222 expand_nlength[1] = Ustrlen(msgbuf);
225 debug_print_string(ablock->server_debug_string); /* customized debug */
227 /* look up password */
229 if (!(clearpass = expand_string(ob->spa_serverpassword)))
230 if (f.expand_string_forcedfail)
232 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
233 "expanding spa_serverpassword\n");
238 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
239 "spa_serverpassword: %s\n", expand_string_message);
243 /* create local hash copy */
245 spa_smb_encrypt(clearpass, challenge.challengeData, lmRespData);
246 spa_smb_nt_encrypt(clearpass, challenge.challengeData, ntRespData);
248 /* compare NT hash (LM may not be available) */
250 off = IVAL(&responseptr->ntResponse.offset,0);
251 if (off >= sizeof(SPAAuthResponse) - 24)
254 debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
257 s = (US responseptr) + off;
259 if (memcmp(ntRespData, s, 24) == 0)
260 return auth_check_serv_cond(ablock); /* success. we have a winner. */
262 /* Expand server_condition as an authorization check (PH) */
268 /*************************************************
269 * Client entry point *
270 *************************************************/
272 /* For interface, see auths/README */
276 auth_instance *ablock, /* authenticator block */
277 void * sx, /* connection */
278 int timeout, /* command timeout */
279 uschar *buffer, /* buffer for reading response */
280 int buffsize) /* size of buffer */
282 auth_spa_options_block *ob =
283 (auth_spa_options_block *)(ablock->options_block);
284 SPAAuthRequest request;
285 SPAAuthChallenge challenge;
286 SPAAuthResponse response;
288 uschar * domain = NULL, * username, * password;
290 /* Code added by PH to expand the options */
292 *buffer = 0; /* Default no message when cancelled */
294 if (!(username = expand_string(ob->spa_username)))
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);
303 if (!(password = expand_string(ob->spa_password)))
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);
313 if (!(domain = expand_string(ob->spa_domain)))
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);
324 if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
327 /* wait for the 3XX OK message */
328 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
331 DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
333 spa_build_auth_request(&request, username, domain);
334 spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request));
336 DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
338 /* send the encrypted password */
339 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
342 /* wait for the auth challenge */
343 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
346 /* convert the challenge into the challenge struct */
347 DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
348 spa_base64_to_bits(CS (&challenge), sizeof(challenge), CCS (buffer + 4));
350 spa_build_auth_response(&challenge, &response, username, password);
351 spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response));
352 DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
354 /* send the challenge response */
355 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
358 /* If we receive a success response from the server, authentication
359 has succeeded. There may be more data to send, but is there any point
360 in provoking an error here? */
362 if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
365 /* Not a success response. If errno != 0 there is some kind of transmission
366 error. Otherwise, check the response code in the buffer. If it starts with
367 '3', more data is expected. */
369 if (errno != 0 || buffer[0] != '3')
375 #endif /*!MACRO_PREDEF*/