1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
8 /* This file, which provides support for Microsoft's Secure Password
9 Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA
10 server support. I (PH) have only modified it in very trivial ways.
13 http://www.innovation.ch/java/ntlm.html
14 http://www.kuro5hin.org/story/2002/4/28/1436/66154
15 http://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/%5bMS-SMTP%5d.pdf
17 * It seems that some systems have existing but different definitions of some
18 * of the following types. I received a complaint about "int16" causing
19 * compilation problems. So I (PH) have renamed them all, to be on the safe
20 * side, by adding 'x' on the end. See auths/auth-spa.h.
22 * typedef signed short int16;
23 * typedef unsigned short uint16;
24 * typedef unsigned uint32;
25 * typedef unsigned char uint8;
27 07-August-2003: PH: Patched up the code to avoid assert bombouts for stupid
28 input data. Find appropriate comment by grepping for "PH".
29 16-October-2006: PH: Added a call to auth_check_serv_cond() at the end
30 05-June-2010: PP: handle SASL initial response
37 /* #define DEBUG_SPA */
40 #define DSPA(x,y,z) debug_printf(x,y,z)
45 /* Options specific to the spa authentication mechanism. */
47 optionlist auth_spa_options[] = {
48 { "client_domain", opt_stringptr,
49 (void *)(offsetof(auth_spa_options_block, spa_domain)) },
50 { "client_password", opt_stringptr,
51 (void *)(offsetof(auth_spa_options_block, spa_password)) },
52 { "client_username", opt_stringptr,
53 (void *)(offsetof(auth_spa_options_block, spa_username)) },
54 { "server_password", opt_stringptr,
55 (void *)(offsetof(auth_spa_options_block, spa_serverpassword)) }
58 /* Size of the options list. An extern variable has to be used so that its
59 address can appear in the tables drtables.c. */
61 int auth_spa_options_count =
62 sizeof(auth_spa_options)/sizeof(optionlist);
64 /* Default private options block for the condition authentication method. */
66 auth_spa_options_block auth_spa_option_defaults = {
67 NULL, /* spa_password */
68 NULL, /* spa_username */
69 NULL, /* spa_domain */
70 NULL /* spa_serverpassword (for server side use) */
77 void auth_spa_init(auth_instance *ablock) {}
78 int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
79 int auth_spa_client(auth_instance *ablock, smtp_inblock *inblock,
80 smtp_outblock *outblock, int timeout, uschar *buffer, int buffsize) {return 0;}
82 #else /*!MACRO_PREDEF*/
87 /*************************************************
88 * Initialization entry point *
89 *************************************************/
91 /* Called for each instance, after its options have been read, to
92 enable consistency checks to be done, or anything else that needs
96 auth_spa_init(auth_instance *ablock)
98 auth_spa_options_block *ob =
99 (auth_spa_options_block *)(ablock->options_block);
101 /* The public name defaults to the authenticator name */
103 if (ablock->public_name == NULL) ablock->public_name = ablock->name;
105 /* Both username and password must be set for a client */
107 if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
108 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n "
109 "one of client_username and client_password cannot be set without "
110 "the other", ablock->name);
111 ablock->client = ob->spa_username != NULL;
113 /* For a server we have just one option */
115 ablock->server = ob->spa_serverpassword != NULL;
120 /*************************************************
121 * Server entry point *
122 *************************************************/
124 /* For interface, see auths/README */
126 #define CVAL(buf,pos) ((US (buf))[pos])
127 #define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
128 #define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
129 #define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
132 auth_spa_server(auth_instance *ablock, uschar *data)
134 auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
135 uint8x lmRespData[24];
136 uint8x ntRespData[24];
137 SPAAuthRequest request;
138 SPAAuthChallenge challenge;
139 SPAAuthResponse response;
140 SPAAuthResponse *responseptr = &response;
144 /* send a 334, MS Exchange style, and grab the client's request,
145 unless we already have it via an initial response. */
147 if ((*data == '\0') &&
148 (auth_get_no64_data(&data, US"NTLM supported") != OK))
150 /* something borked */
154 if (spa_base64_to_bits(CS (&request), sizeof(request), CCS (data)) < 0)
156 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
157 "request: %s\n", data);
161 /* create a challenge and send it back */
163 spa_build_auth_challenge(&request,&challenge);
164 spa_bits_to_base64 (msgbuf, (unsigned char*)&challenge,
165 spa_request_length(&challenge));
167 if (auth_get_no64_data(&data, msgbuf) != OK)
169 /* something borked */
173 /* dump client response */
174 if (spa_base64_to_bits(CS (&response), sizeof(response), CCS (data)) < 0)
176 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
177 "response: %s\n", data);
181 /***************************************************************
182 PH 07-Aug-2003: The original code here was this:
184 Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
185 IVAL(&responseptr->uUser.offset,0),
186 SVAL(&responseptr->uUser.len,0)/2) );
188 However, if the response data is too long, unicodeToString bombs out on
189 an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
190 idea. It's too messy to try to rework that function to return an error because
191 it is called from a number of other places in the auth-spa.c module. Instead,
192 since it is a very small function, I reproduce its code here, with a size check
193 that causes failure if the size of msgbuf is exceeded. ****/
197 char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
198 int len = SVAL(&responseptr->uUser.len,0)/2;
200 if (len + 1 >= sizeof(msgbuf)) return FAIL;
201 for (i = 0; i < len; ++i)
203 msgbuf[i] = *p & 0x7f;
209 /***************************************************************/
211 /* Put the username in $auth1 and $1. The former is now the preferred variable;
212 the latter is the original variable. These have to be out of stack memory, and
213 need to be available once known even if not authenticated, for error messages
214 (server_set_id, which only makes it to authenticated_id if we return OK) */
216 auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
217 expand_nlength[1] = Ustrlen(msgbuf);
220 debug_print_string(ablock->server_debug_string); /* customized debug */
222 /* look up password */
224 clearpass = expand_string(ob->spa_serverpassword);
225 if (clearpass == NULL)
227 if (expand_string_forcedfail)
229 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
230 "expanding spa_serverpassword\n");
235 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
236 "spa_serverpassword: %s\n", expand_string_message);
241 /* create local hash copy */
243 spa_smb_encrypt (clearpass, challenge.challengeData, lmRespData);
244 spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData);
246 /* compare NT hash (LM may not be available) */
248 if (memcmp(ntRespData,
249 ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0),
251 /* success. we have a winner. */
253 return auth_check_serv_cond(ablock);
256 /* Expand server_condition as an authorization check (PH) */
262 /*************************************************
263 * Client entry point *
264 *************************************************/
266 /* For interface, see auths/README */
270 auth_instance *ablock, /* authenticator block */
271 smtp_inblock *inblock, /* connection inblock */
272 smtp_outblock *outblock, /* connection outblock */
273 int timeout, /* command timeout */
274 uschar *buffer, /* buffer for reading response */
275 int buffsize) /* size of buffer */
277 auth_spa_options_block *ob =
278 (auth_spa_options_block *)(ablock->options_block);
279 SPAAuthRequest request;
280 SPAAuthChallenge challenge;
281 SPAAuthResponse response;
284 char *username, *password;
286 /* Code added by PH to expand the options */
288 *buffer = 0; /* Default no message when cancelled */
290 if (!(username = CS expand_string(ob->spa_username)))
292 if (expand_string_forcedfail) return CANCELLED;
293 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
294 "authenticator: %s", ob->spa_username, ablock->name,
295 expand_string_message);
299 if (!(password = CS expand_string(ob->spa_password)))
301 if (expand_string_forcedfail) return CANCELLED;
302 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
303 "authenticator: %s", ob->spa_password, ablock->name,
304 expand_string_message);
310 if (!(domain = CS expand_string(ob->spa_domain)))
312 if (expand_string_forcedfail) return CANCELLED;
313 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
314 "authenticator: %s", ob->spa_domain, ablock->name,
315 expand_string_message);
322 if (smtp_write_command(outblock, SCMD_FLUSH, "AUTH %s\r\n",
323 ablock->public_name) < 0)
326 /* wait for the 3XX OK message */
327 if (!smtp_read_response(inblock, US buffer, buffsize, '3', timeout))
330 DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
332 spa_build_auth_request (&request, CS username, domain);
333 spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
334 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(outblock, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
342 /* wait for the auth challenge */
343 if (!smtp_read_response(inblock, 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, CS username, CS password);
351 spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
352 spa_request_length(&response));
353 DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
355 /* send the challenge response */
356 if (smtp_write_command(outblock, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
359 /* If we receive a success response from the server, authentication
360 has succeeded. There may be more data to send, but is there any point
361 in provoking an error here? */
363 if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout))
366 /* Not a success response. If errno != 0 there is some kind of transmission
367 error. Otherwise, check the response code in the buffer. If it starts with
368 '3', more data is expected. */
370 if (errno != 0 || buffer[0] != '3')
376 #endif /*!MACRO_PREDEF*/