1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
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 contidion 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) */
74 /*************************************************
75 * Initialization entry point *
76 *************************************************/
78 /* Called for each instance, after its options have been read, to
79 enable consistency checks to be done, or anything else that needs
83 auth_spa_init(auth_instance *ablock)
85 auth_spa_options_block *ob =
86 (auth_spa_options_block *)(ablock->options_block);
88 /* The public name defaults to the authenticator name */
90 if (ablock->public_name == NULL) ablock->public_name = ablock->name;
92 /* Both username and password must be set for a client */
94 if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
95 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n "
96 "one of client_username and client_password cannot be set without "
97 "the other", ablock->name);
98 ablock->client = ob->spa_username != NULL;
100 /* For a server we have just one option */
102 ablock->server = ob->spa_serverpassword != NULL;
107 /*************************************************
108 * Server entry point *
109 *************************************************/
111 /* For interface, see auths/README */
113 #define CVAL(buf,pos) (((unsigned char *)(buf))[pos])
114 #define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
115 #define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
116 #define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
119 auth_spa_server(auth_instance *ablock, uschar *data)
121 auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
122 uint8x lmRespData[24];
123 uint8x ntRespData[24];
124 SPAAuthRequest request;
125 SPAAuthChallenge challenge;
126 SPAAuthResponse response;
127 SPAAuthResponse *responseptr = &response;
131 /* send a 334, MS Exchange style, and grab the client's request,
132 unless we already have it via an initial response. */
134 if ((*data == '\0') &&
135 (auth_get_no64_data(&data, US"NTLM supported") != OK))
137 /* something borked */
141 if (spa_base64_to_bits((char *)(&request), sizeof(request), (const char *)(data)) < 0)
143 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
144 "request: %s\n", data);
148 /* create a challenge and send it back */
150 spa_build_auth_challenge(&request,&challenge);
151 spa_bits_to_base64 (msgbuf, (unsigned char*)&challenge,
152 spa_request_length(&challenge));
154 if (auth_get_no64_data(&data, msgbuf) != OK)
156 /* something borked */
160 /* dump client response */
161 if (spa_base64_to_bits((char *)(&response), sizeof(response), (const char *)(data)) < 0)
163 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
164 "response: %s\n", data);
168 /***************************************************************
169 PH 07-Aug-2003: The original code here was this:
171 Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
172 IVAL(&responseptr->uUser.offset,0),
173 SVAL(&responseptr->uUser.len,0)/2) );
175 However, if the response data is too long, unicodeToString bombs out on
176 an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
177 idea. It's too messy to try to rework that function to return an error because
178 it is called from a number of other places in the auth-spa.c module. Instead,
179 since it is a very small function, I reproduce its code here, with a size check
180 that causes failure if the size of msgbuf is exceeded. ****/
184 char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
185 int len = SVAL(&responseptr->uUser.len,0)/2;
187 if (len + 1 >= sizeof(msgbuf)) return FAIL;
188 for (i = 0; i < len; ++i)
190 msgbuf[i] = *p & 0x7f;
196 /***************************************************************/
198 /* Put the username in $auth1 and $1. The former is now the preferred variable;
199 the latter is the original variable. These have to be out of stack memory, and
200 need to be available once known even if not authenticated, for error messages
201 (server_set_id, which only makes it to authenticated_id if we return OK) */
203 auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
204 expand_nlength[1] = Ustrlen(msgbuf);
207 debug_print_string(ablock->server_debug_string); /* customized debug */
209 /* look up password */
211 clearpass = expand_string(ob->spa_serverpassword);
212 if (clearpass == NULL)
214 if (expand_string_forcedfail)
216 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
217 "expanding spa_serverpassword\n");
222 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
223 "spa_serverpassword: %s\n", expand_string_message);
228 /* create local hash copy */
230 spa_smb_encrypt (clearpass, challenge.challengeData, lmRespData);
231 spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData);
233 /* compare NT hash (LM may not be available) */
235 if (memcmp(ntRespData,
236 ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0),
238 /* success. we have a winner. */
240 return auth_check_serv_cond(ablock);
243 /* Expand server_condition as an authorization check (PH) */
249 /*************************************************
250 * Client entry point *
251 *************************************************/
253 /* For interface, see auths/README */
257 auth_instance *ablock, /* authenticator block */
258 smtp_inblock *inblock, /* connection inblock */
259 smtp_outblock *outblock, /* connection outblock */
260 int timeout, /* command timeout */
261 uschar *buffer, /* buffer for reading response */
262 int buffsize) /* size of buffer */
264 auth_spa_options_block *ob =
265 (auth_spa_options_block *)(ablock->options_block);
266 SPAAuthRequest request;
267 SPAAuthChallenge challenge;
268 SPAAuthResponse response;
271 char *username, *password;
273 /* Code added by PH to expand the options */
275 *buffer = 0; /* Default no message when cancelled */
277 username = CS expand_string(ob->spa_username);
278 if (username == NULL)
280 if (expand_string_forcedfail) return CANCELLED;
281 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
282 "authenticator: %s", ob->spa_username, ablock->name,
283 expand_string_message);
287 password = CS expand_string(ob->spa_password);
288 if (password == NULL)
290 if (expand_string_forcedfail) return CANCELLED;
291 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
292 "authenticator: %s", ob->spa_password, ablock->name,
293 expand_string_message);
297 if (ob->spa_domain != NULL)
299 domain = CS expand_string(ob->spa_domain);
302 if (expand_string_forcedfail) return CANCELLED;
303 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
304 "authenticator: %s", ob->spa_domain, ablock->name,
305 expand_string_message);
312 if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n",
313 ablock->public_name) < 0)
316 /* wait for the 3XX OK message */
317 if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
320 DSPA("\n\n%s authenticator: using domain %s\n\n",
321 ablock->name, domain);
323 spa_build_auth_request (&request, CS username, domain);
324 spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
325 spa_request_length(&request));
327 DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name,
330 /* send the encrypted password */
331 if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
334 /* wait for the auth challenge */
335 if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
338 /* convert the challenge into the challenge struct */
339 DSPA("\n\n%s authenticator: challenge (%s)\n\n",
340 ablock->name, buffer + 4);
341 spa_base64_to_bits ((char *)(&challenge), sizeof(challenge), (const char *)(buffer + 4));
343 spa_build_auth_response (&challenge, &response,
344 CS username, CS password);
345 spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
346 spa_request_length(&response));
347 DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name,
350 /* send the challenge response */
351 if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
354 /* If we receive a success response from the server, authentication
355 has succeeded. There may be more data to send, but is there any point
356 in provoking an error here? */
357 if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout))
360 /* Not a success response. If errno != 0 there is some kind of transmission
361 error. Otherwise, check the response code in the buffer. If it starts with
362 '3', more data is expected. */
363 if (errno != 0 || buffer[0] != '3')