c5e6d83d5bfbdaec8d3afcf6d3cb53677f908657
[exim.git] / src / src / auths / spa.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
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-only */
9
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.
13
14 References:
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
18
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.
23
24  * typedef signed short int16;
25  * typedef unsigned short uint16;
26  * typedef unsigned uint32;
27  * typedef unsigned char  uint8;
28
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
33 */
34
35
36 #include "../exim.h"
37 #include "spa.h"
38
39 /* #define DEBUG_SPA */
40
41 #ifdef DEBUG_SPA
42 #define DSPA(x,y,z)   debug_printf(x,y,z)
43 #else
44 #define DSPA(x,y,z)
45 #endif
46
47 /* Options specific to the spa authentication mechanism. */
48
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) }
58 };
59
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. */
62
63 int auth_spa_options_count =
64   sizeof(auth_spa_options)/sizeof(optionlist);
65
66 /* Default private options block for the condition authentication method. */
67
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) */
73 };
74
75
76 #ifdef MACRO_PREDEF
77
78 /* Dummy values */
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;}
83
84 #else   /*!MACRO_PREDEF*/
85
86
87
88
89 /*************************************************
90 *          Initialization entry point            *
91 *************************************************/
92
93 /* Called for each instance, after its options have been read, to
94 enable consistency checks to be done, or anything else that needs
95 to be set up. */
96
97 void
98 auth_spa_init(auth_instance *ablock)
99 {
100 auth_spa_options_block *ob =
101   (auth_spa_options_block *)(ablock->options_block);
102
103 /* The public name defaults to the authenticator name */
104
105 if (ablock->public_name == NULL) ablock->public_name = ablock->name;
106
107 /* Both username and password must be set for a client */
108
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;
114
115 /* For a server we have just one option */
116
117 ablock->server = ob->spa_serverpassword != NULL;
118 }
119
120
121
122 /*************************************************
123 *             Server entry point                 *
124 *************************************************/
125
126 /* For interface, see auths/README */
127
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)
132
133 int
134 auth_spa_server(auth_instance *ablock, uschar *data)
135 {
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;
143 uschar msgbuf[2048];
144 uschar *clearpass, *s;
145 unsigned off;
146
147 /* send a 334, MS Exchange style, and grab the client's request,
148 unless we already have it via an initial response. */
149
150 if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK)
151   return FAIL;
152
153 if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0)
154   {
155   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
156     "request: %s\n", data);
157   return FAIL;
158   }
159
160 /* create a challenge and send it back */
161
162 spa_build_auth_challenge(&request, &challenge);
163 spa_bits_to_base64(msgbuf, US &challenge, spa_request_length(&challenge));
164
165 if (auth_get_no64_data(&data, msgbuf) != OK)
166   return FAIL;
167
168 /* dump client response */
169 if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
170   {
171   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
172     "response: %s\n", data);
173   return FAIL;
174   }
175
176 /***************************************************************
177 PH 07-Aug-2003: The original code here was this:
178
179 Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
180   IVAL(&responseptr->uUser.offset,0),
181   SVAL(&responseptr->uUser.len,0)/2) );
182
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. ****/
189
190   {
191   int i;
192   char * p;
193   int len = SVAL(&responseptr->uUser.len,0)/2;
194
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)
198      )
199     {
200     DEBUG(D_auth)
201       debug_printf("auth_spa_server(): bad uUser spec in response\n");
202     return FAIL;
203     }
204
205   if (len + 1 >= sizeof(msgbuf)) return FAIL;
206   for (i = 0; i < len; ++i)
207     {
208     msgbuf[i] = *p & 0x7f;
209     p += 2;
210     }
211   msgbuf[i] = 0;
212   }
213
214 /***************************************************************/
215
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) */
220
221 auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
222 expand_nlength[1] = Ustrlen(msgbuf);
223 expand_nmax = 1;
224
225 debug_print_string(ablock->server_debug_string);    /* customized debug */
226
227 /* look up password */
228
229 if (!(clearpass = expand_string(ob->spa_serverpassword)))
230   if (f.expand_string_forcedfail)
231     {
232     DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
233       "expanding spa_serverpassword\n");
234     return FAIL;
235     }
236   else
237     {
238     DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
239       "spa_serverpassword: %s\n", expand_string_message);
240     return DEFER;
241     }
242
243 /* create local hash copy */
244
245 spa_smb_encrypt(clearpass, challenge.challengeData, lmRespData);
246 spa_smb_nt_encrypt(clearpass, challenge.challengeData, ntRespData);
247
248 /* compare NT hash (LM may not be available) */
249
250 off = IVAL(&responseptr->ntResponse.offset,0);
251 if (off >= sizeof(SPAAuthResponse) - 24)
252   {
253   DEBUG(D_auth)
254     debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
255   return FAIL;
256   }
257 s = (US responseptr) + off;
258
259 if (memcmp(ntRespData, s, 24) == 0)
260   return auth_check_serv_cond(ablock);  /* success. we have a winner. */
261
262   /* Expand server_condition as an authorization check (PH) */
263
264 return FAIL;
265 }
266
267
268 /*************************************************
269 *              Client entry point                *
270 *************************************************/
271
272 /* For interface, see auths/README */
273
274 int
275 auth_spa_client(
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 */
281 {
282 auth_spa_options_block *ob =
283        (auth_spa_options_block *)(ablock->options_block);
284 SPAAuthRequest   request;
285 SPAAuthChallenge challenge;
286 SPAAuthResponse  response;
287 char msgbuf[2048];
288 char *domain = NULL;
289 char *username, *password;
290
291 /* Code added by PH to expand the options */
292
293 *buffer = 0;    /* Default no message when cancelled */
294
295 if (!(username = CS expand_string(ob->spa_username)))
296   {
297   if (f.expand_string_forcedfail) return CANCELLED;
298   string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
299    "authenticator: %s", ob->spa_username, ablock->name,
300    expand_string_message);
301   return ERROR;
302   }
303
304 if (!(password = CS expand_string(ob->spa_password)))
305   {
306   if (f.expand_string_forcedfail) return CANCELLED;
307   string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
308    "authenticator: %s", ob->spa_password, ablock->name,
309    expand_string_message);
310   return ERROR;
311   }
312
313 if (ob->spa_domain)
314   if (!(domain = CS expand_string(ob->spa_domain)))
315     {
316     if (f.expand_string_forcedfail) return CANCELLED;
317     string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
318                   "authenticator: %s", ob->spa_domain, ablock->name,
319                   expand_string_message);
320     return ERROR;
321     }
322
323 /* Original code */
324
325 if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
326   return FAIL_SEND;
327
328 /* wait for the 3XX OK message */
329 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
330   return FAIL;
331
332 DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
333
334 spa_build_auth_request(&request, CS username, domain);
335 spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request));
336
337 DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
338
339 /* send the encrypted password */
340 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
341   return FAIL_SEND;
342
343 /* wait for the auth challenge */
344 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
345   return FAIL;
346
347 /* convert the challenge into the challenge struct */
348 DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
349 spa_base64_to_bits(CS (&challenge), sizeof(challenge), CCS (buffer + 4));
350
351 spa_build_auth_response(&challenge, &response, CS username, CS password);
352 spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response));
353 DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
354
355 /* send the challenge response */
356 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
357        return FAIL_SEND;
358
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? */
362
363 if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
364   return OK;
365
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. */
369
370 if (errno != 0 || buffer[0] != '3')
371   return FAIL;
372
373 return FAIL;
374 }
375
376 #endif   /*!MACRO_PREDEF*/
377 /* End of spa.c */