Reduce number of places knowing about filename-construction for mbox file-for-scanning
[exim.git] / src / src / auths / spa.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
0a49a7a4 5/* Copyright (c) University of Cambridge 1995 - 2009 */
0756eb3c
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
8/* This file, which provides support for Microsoft's Secure Password
9Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA
10server support. I (PH) have only modified it in very trivial ways.
11
12References:
13 http://www.innovation.ch/java/ntlm.html
14 http://www.kuro5hin.org/story/2002/4/28/1436/66154
55c75993 15 http://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/%5bMS-SMTP%5d.pdf
0756eb3c
PH
16
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.
21
22 * typedef signed short int16;
23 * typedef unsigned short uint16;
24 * typedef unsigned uint32;
25 * typedef unsigned char uint8;
26
16ff981e
PH
2707-August-2003: PH: Patched up the code to avoid assert bombouts for stupid
28 input data. Find appropriate comment by grepping for "PH".
2916-October-2006: PH: Added a call to auth_check_serv_cond() at the end
55c75993 3005-June-2010: PP: handle SASL initial response
0756eb3c
PH
31*/
32
33
34#include "../exim.h"
35#include "spa.h"
36
37/* #define DEBUG_SPA */
38
39#ifdef DEBUG_SPA
40#define DSPA(x,y,z) debug_printf(x,y,z)
41#else
42#define DSPA(x,y,z)
43#endif
44
45/* Options specific to the spa authentication mechanism. */
46
47optionlist 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)) }
56};
57
58/* Size of the options list. An extern variable has to be used so that its
59address can appear in the tables drtables.c. */
60
61int auth_spa_options_count =
62 sizeof(auth_spa_options)/sizeof(optionlist);
63
4c04137d 64/* Default private options block for the condition authentication method. */
0756eb3c
PH
65
66auth_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) */
71};
72
73
74/*************************************************
75* Initialization entry point *
76*************************************************/
77
78/* Called for each instance, after its options have been read, to
79enable consistency checks to be done, or anything else that needs
80to be set up. */
81
82void
83auth_spa_init(auth_instance *ablock)
84{
85auth_spa_options_block *ob =
86 (auth_spa_options_block *)(ablock->options_block);
87
88/* The public name defaults to the authenticator name */
89
90if (ablock->public_name == NULL) ablock->public_name = ablock->name;
91
92/* Both username and password must be set for a client */
93
94if ((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);
98ablock->client = ob->spa_username != NULL;
99
100/* For a server we have just one option */
101
102ablock->server = ob->spa_serverpassword != NULL;
103}
104
105
106
107/*************************************************
108* Server entry point *
109*************************************************/
110
111/* For interface, see auths/README */
112
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)
117
118int
119auth_spa_server(auth_instance *ablock, uschar *data)
120{
121auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
122uint8x lmRespData[24];
123uint8x ntRespData[24];
124SPAAuthRequest request;
125SPAAuthChallenge challenge;
126SPAAuthResponse response;
127SPAAuthResponse *responseptr = &response;
128uschar msgbuf[2048];
129uschar *clearpass;
130
55c75993
PP
131/* send a 334, MS Exchange style, and grab the client's request,
132unless we already have it via an initial response. */
0756eb3c 133
55c75993
PP
134if ((*data == '\0') &&
135 (auth_get_no64_data(&data, US"NTLM supported") != OK))
0756eb3c
PH
136 {
137 /* something borked */
138 return FAIL;
139 }
140
85b87bc2 141if (spa_base64_to_bits((char *)(&request), sizeof(request), (const char *)(data)) < 0)
0756eb3c
PH
142 {
143 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
144 "request: %s\n", data);
145 return FAIL;
146 }
147
148/* create a challenge and send it back */
149
150spa_build_auth_challenge(&request,&challenge);
151spa_bits_to_base64 (msgbuf, (unsigned char*)&challenge,
152 spa_request_length(&challenge));
153
154if (auth_get_no64_data(&data, msgbuf) != OK)
155 {
156 /* something borked */
157 return FAIL;
158 }
159
160/* dump client response */
85b87bc2 161if (spa_base64_to_bits((char *)(&response), sizeof(response), (const char *)(data)) < 0)
0756eb3c
PH
162 {
163 DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
164 "response: %s\n", data);
165 return FAIL;
166 }
167
0756eb3c
PH
168/***************************************************************
169PH 07-Aug-2003: The original code here was this:
170
171Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
172 IVAL(&responseptr->uUser.offset,0),
173 SVAL(&responseptr->uUser.len,0)/2) );
174
175However, if the response data is too long, unicodeToString bombs out on
176an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
177idea. It's too messy to try to rework that function to return an error because
178it is called from a number of other places in the auth-spa.c module. Instead,
179since it is a very small function, I reproduce its code here, with a size check
180that causes failure if the size of msgbuf is exceeded. ****/
181
182 {
183 int i;
184 char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
185 int len = SVAL(&responseptr->uUser.len,0)/2;
186
187 if (len + 1 >= sizeof(msgbuf)) return FAIL;
188 for (i = 0; i < len; ++i)
189 {
190 msgbuf[i] = *p & 0x7f;
191 p += 2;
192 }
193 msgbuf[i] = 0;
194 }
195
196/***************************************************************/
197
f78eb7c6 198/* Put the username in $auth1 and $1. The former is now the preferred variable;
f68fe5f6
PP
199the latter is the original variable. These have to be out of stack memory, and
200need 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) */
f78eb7c6 202
f68fe5f6 203auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
0756eb3c
PH
204expand_nlength[1] = Ustrlen(msgbuf);
205expand_nmax = 1;
206
f78eb7c6
PH
207debug_print_string(ablock->server_debug_string); /* customized debug */
208
0756eb3c
PH
209/* look up password */
210
211clearpass = expand_string(ob->spa_serverpassword);
212if (clearpass == NULL)
213 {
214 if (expand_string_forcedfail)
215 {
216 DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
217 "expanding spa_serverpassword\n");
f68fe5f6 218 return FAIL;
0756eb3c
PH
219 }
220 else
221 {
222 DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
223 "spa_serverpassword: %s\n", expand_string_message);
f68fe5f6 224 return DEFER;
0756eb3c
PH
225 }
226 }
227
228/* create local hash copy */
229
230spa_smb_encrypt (clearpass, challenge.challengeData, lmRespData);
231spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData);
232
233/* compare NT hash (LM may not be available) */
234
235if (memcmp(ntRespData,
236 ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0),
237 24) == 0)
238 /* success. we have a winner. */
08488c86 239 {
f68fe5f6 240 return auth_check_serv_cond(ablock);
08488c86 241 }
16ff981e
PH
242
243 /* Expand server_condition as an authorization check (PH) */
0756eb3c 244
f68fe5f6 245return FAIL;
0756eb3c
PH
246}
247
248
249/*************************************************
250* Client entry point *
251*************************************************/
252
253/* For interface, see auths/README */
254
255int
256auth_spa_client(
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 */
263{
264 auth_spa_options_block *ob =
265 (auth_spa_options_block *)(ablock->options_block);
266 SPAAuthRequest request;
267 SPAAuthChallenge challenge;
268 SPAAuthResponse response;
269 char msgbuf[2048];
270 char *domain = NULL;
271 char *username, *password;
272
0756eb3c
PH
273 /* Code added by PH to expand the options */
274
4730f942
PH
275 *buffer = 0; /* Default no message when cancelled */
276
0756eb3c
PH
277 username = CS expand_string(ob->spa_username);
278 if (username == NULL)
279 {
b1206957 280 if (expand_string_forcedfail) return CANCELLED;
0756eb3c
PH
281 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
282 "authenticator: %s", ob->spa_username, ablock->name,
283 expand_string_message);
284 return ERROR;
285 }
286
287 password = CS expand_string(ob->spa_password);
288 if (password == NULL)
289 {
b1206957 290 if (expand_string_forcedfail) return CANCELLED;
0756eb3c
PH
291 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
292 "authenticator: %s", ob->spa_password, ablock->name,
293 expand_string_message);
294 return ERROR;
295 }
296
297 if (ob->spa_domain != NULL)
298 {
299 domain = CS expand_string(ob->spa_domain);
300 if (domain == NULL)
301 {
b1206957 302 if (expand_string_forcedfail) return CANCELLED;
0756eb3c
PH
303 string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
304 "authenticator: %s", ob->spa_domain, ablock->name,
305 expand_string_message);
306 return ERROR;
307 }
308 }
309
310 /* Original code */
311
b1206957
PH
312 if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n",
313 ablock->public_name) < 0)
314 return FAIL_SEND;
315
316 /* wait for the 3XX OK message */
317 if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
318 return FAIL;
319
0756eb3c
PH
320 DSPA("\n\n%s authenticator: using domain %s\n\n",
321 ablock->name, domain);
322
323 spa_build_auth_request (&request, CS username, domain);
324 spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
325 spa_request_length(&request));
326
327 DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name,
328 msgbuf);
329
330 /* send the encrypted password */
331 if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
332 return FAIL_SEND;
333
334 /* wait for the auth challenge */
335 if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
336 return FAIL;
337
338 /* convert the challenge into the challenge struct */
339 DSPA("\n\n%s authenticator: challenge (%s)\n\n",
340 ablock->name, buffer + 4);
85b87bc2 341 spa_base64_to_bits ((char *)(&challenge), sizeof(challenge), (const char *)(buffer + 4));
0756eb3c
PH
342
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,
348 msgbuf);
349
350 /* send the challenge response */
351 if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
352 return FAIL_SEND;
353
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))
358 return OK;
359
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')
364 return FAIL;
365
366 return FAIL;
367}
368
369/* End of spa.c */