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. */
9 /* This file, which provides support for Microsoft's Secure Password
10 Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA
11 server support. I (PH) have only modified it in very trivial ways.
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
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.
23 * typedef signed short int16;
24 * typedef unsigned short uint16;
25 * typedef unsigned uint32;
26 * typedef unsigned char uint8;
28 07-August-2003: PH: Patched up the code to avoid assert bombouts for stupid
29 input data. Find appropriate comment by grepping for "PH".
30 16-October-2006: PH: Added a call to auth_check_serv_cond() at the end
31 05-June-2010: PP: handle SASL initial response
38 /* #define DEBUG_SPA */
41 #define DSPA(x,y,z) debug_printf(x,y,z)
46 /* Options specific to the spa authentication mechanism. */
48 optionlist 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) }
59 /* Size of the options list. An extern variable has to be used so that its
60 address can appear in the tables drtables.c. */
62 int auth_spa_options_count =
63 sizeof(auth_spa_options)/sizeof(optionlist);
65 /* Default private options block for the condition authentication method. */
67 auth_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) */
78 void auth_spa_init(auth_instance *ablock) {}
79 int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
80 int auth_spa_client(auth_instance *ablock, void * sx, int timeout,
81 uschar *buffer, int buffsize) {return 0;}
83 #else /*!MACRO_PREDEF*/
88 /*************************************************
89 * Initialization entry point *
90 *************************************************/
92 /* Called for each instance, after its options have been read, to
93 enable consistency checks to be done, or anything else that needs
97 auth_spa_init(auth_instance *ablock)
99 auth_spa_options_block *ob =
100 (auth_spa_options_block *)(ablock->options_block);
102 /* The public name defaults to the authenticator name */
104 if (ablock->public_name == NULL) ablock->public_name = ablock->name;
106 /* Both username and password must be set for a client */
108 if ((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);
112 ablock->client = ob->spa_username != NULL;
114 /* For a server we have just one option */
116 ablock->server = ob->spa_serverpassword != NULL;
121 /*************************************************
122 * Server entry point *
123 *************************************************/
125 /* For interface, see auths/README */
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)
133 auth_spa_server(auth_instance *ablock, uschar *data)
135 auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
136 uint8x lmRespData[24];
137 uint8x ntRespData[24];
138 SPAAuthRequest request;
139 SPAAuthChallenge challenge;
140 SPAAuthResponse response;
141 SPAAuthResponse *responseptr = &response;
145 /* send a 334, MS Exchange style, and grab the client's request,
146 unless we already have it via an initial response. */
148 if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK)
151 if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0)
153 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
154 "request: %s\n", data);
158 /* create a challenge and send it back */
160 spa_build_auth_challenge(&request, &challenge);
161 spa_bits_to_base64(msgbuf, US &challenge, spa_request_length(&challenge));
163 if (auth_get_no64_data(&data, msgbuf) != OK)
166 /* dump client response */
167 if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
169 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
170 "response: %s\n", data);
174 /***************************************************************
175 PH 07-Aug-2003: The original code here was this:
177 Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
178 IVAL(&responseptr->uUser.offset,0),
179 SVAL(&responseptr->uUser.len,0)/2) );
181 However, if the response data is too long, unicodeToString bombs out on
182 an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
183 idea. It's too messy to try to rework that function to return an error because
184 it is called from a number of other places in the auth-spa.c module. Instead,
185 since it is a very small function, I reproduce its code here, with a size check
186 that causes failure if the size of msgbuf is exceeded. ****/
190 char * p = (CS responseptr) + IVAL(&responseptr->uUser.offset,0);
191 int len = SVAL(&responseptr->uUser.len,0)/2;
193 if (len + 1 >= sizeof(msgbuf)) return FAIL;
194 for (i = 0; i < len; ++i)
196 msgbuf[i] = *p & 0x7f;
202 /***************************************************************/
204 /* Put the username in $auth1 and $1. The former is now the preferred variable;
205 the latter is the original variable. These have to be out of stack memory, and
206 need to be available once known even if not authenticated, for error messages
207 (server_set_id, which only makes it to authenticated_id if we return OK) */
209 auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
210 expand_nlength[1] = Ustrlen(msgbuf);
213 debug_print_string(ablock->server_debug_string); /* customized debug */
215 /* look up password */
217 if (!(clearpass = expand_string(ob->spa_serverpassword)))
218 if (f.expand_string_forcedfail)
220 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
221 "expanding spa_serverpassword\n");
226 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
227 "spa_serverpassword: %s\n", expand_string_message);
231 /* create local hash copy */
233 spa_smb_encrypt(clearpass, challenge.challengeData, lmRespData);
234 spa_smb_nt_encrypt(clearpass, challenge.challengeData, ntRespData);
236 /* compare NT hash (LM may not be available) */
238 if (memcmp(ntRespData, (US responseptr)+IVAL(&responseptr->ntResponse.offset,0),
240 return auth_check_serv_cond(ablock); /* success. we have a winner. */
242 /* Expand server_condition as an authorization check (PH) */
248 /*************************************************
249 * Client entry point *
250 *************************************************/
252 /* For interface, see auths/README */
256 auth_instance *ablock, /* authenticator block */
257 void * sx, /* connection */
258 int timeout, /* command timeout */
259 uschar *buffer, /* buffer for reading response */
260 int buffsize) /* size of buffer */
262 auth_spa_options_block *ob =
263 (auth_spa_options_block *)(ablock->options_block);
264 SPAAuthRequest request;
265 SPAAuthChallenge challenge;
266 SPAAuthResponse response;
269 char *username, *password;
271 /* Code added by PH to expand the options */
273 *buffer = 0; /* Default no message when cancelled */
275 if (!(username = CS expand_string(ob->spa_username)))
277 if (f.expand_string_forcedfail) return CANCELLED;
278 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
279 "authenticator: %s", ob->spa_username, ablock->name,
280 expand_string_message);
284 if (!(password = CS expand_string(ob->spa_password)))
286 if (f.expand_string_forcedfail) return CANCELLED;
287 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
288 "authenticator: %s", ob->spa_password, ablock->name,
289 expand_string_message);
294 if (!(domain = CS expand_string(ob->spa_domain)))
296 if (f.expand_string_forcedfail) return CANCELLED;
297 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
298 "authenticator: %s", ob->spa_domain, ablock->name,
299 expand_string_message);
305 if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
308 /* wait for the 3XX OK message */
309 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
312 DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
314 spa_build_auth_request(&request, CS username, domain);
315 spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request));
317 DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
319 /* send the encrypted password */
320 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
323 /* wait for the auth challenge */
324 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
327 /* convert the challenge into the challenge struct */
328 DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
329 spa_base64_to_bits(CS (&challenge), sizeof(challenge), CCS (buffer + 4));
331 spa_build_auth_response(&challenge, &response, CS username, CS password);
332 spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response));
333 DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
335 /* send the challenge response */
336 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
339 /* If we receive a success response from the server, authentication
340 has succeeded. There may be more data to send, but is there any point
341 in provoking an error here? */
343 if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
346 /* Not a success response. If errno != 0 there is some kind of transmission
347 error. Otherwise, check the response code in the buffer. If it starts with
348 '3', more data is expected. */
350 if (errno != 0 || buffer[0] != '3')
356 #endif /*!MACRO_PREDEF*/