Rework SPA fix to avoid overflows. Bug 2571
[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
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.
12
13 References:
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
17
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.
22
23  * typedef signed short int16;
24  * typedef unsigned short uint16;
25  * typedef unsigned uint32;
26  * typedef unsigned char  uint8;
27
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
32 */
33
34
35 #include "../exim.h"
36 #include "spa.h"
37
38 /* #define DEBUG_SPA */
39
40 #ifdef DEBUG_SPA
41 #define DSPA(x,y,z)   debug_printf(x,y,z)
42 #else
43 #define DSPA(x,y,z)
44 #endif
45
46 /* Options specific to the spa authentication mechanism. */
47
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) }
57 };
58
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. */
61
62 int auth_spa_options_count =
63   sizeof(auth_spa_options)/sizeof(optionlist);
64
65 /* Default private options block for the condition authentication method. */
66
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) */
72 };
73
74
75 #ifdef MACRO_PREDEF
76
77 /* Dummy values */
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;}
82
83 #else   /*!MACRO_PREDEF*/
84
85
86
87
88 /*************************************************
89 *          Initialization entry point            *
90 *************************************************/
91
92 /* Called for each instance, after its options have been read, to
93 enable consistency checks to be done, or anything else that needs
94 to be set up. */
95
96 void
97 auth_spa_init(auth_instance *ablock)
98 {
99 auth_spa_options_block *ob =
100   (auth_spa_options_block *)(ablock->options_block);
101
102 /* The public name defaults to the authenticator name */
103
104 if (ablock->public_name == NULL) ablock->public_name = ablock->name;
105
106 /* Both username and password must be set for a client */
107
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;
113
114 /* For a server we have just one option */
115
116 ablock->server = ob->spa_serverpassword != NULL;
117 }
118
119
120
121 /*************************************************
122 *             Server entry point                 *
123 *************************************************/
124
125 /* For interface, see auths/README */
126
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)
131
132 int
133 auth_spa_server(auth_instance *ablock, uschar *data)
134 {
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;
142 uschar msgbuf[2048];
143 uschar *clearpass, *s;
144 unsigned off;
145
146 /* send a 334, MS Exchange style, and grab the client's request,
147 unless we already have it via an initial response. */
148
149 if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK)
150   return FAIL;
151
152 if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0)
153   {
154   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
155     "request: %s\n", data);
156   return FAIL;
157   }
158
159 /* create a challenge and send it back */
160
161 spa_build_auth_challenge(&request, &challenge);
162 spa_bits_to_base64(msgbuf, US &challenge, spa_request_length(&challenge));
163
164 if (auth_get_no64_data(&data, msgbuf) != OK)
165   return FAIL;
166
167 /* dump client response */
168 if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
169   {
170   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
171     "response: %s\n", data);
172   return FAIL;
173   }
174
175 /***************************************************************
176 PH 07-Aug-2003: The original code here was this:
177
178 Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
179   IVAL(&responseptr->uUser.offset,0),
180   SVAL(&responseptr->uUser.len,0)/2) );
181
182 However, if the response data is too long, unicodeToString bombs out on
183 an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
184 idea. It's too messy to try to rework that function to return an error because
185 it is called from a number of other places in the auth-spa.c module. Instead,
186 since it is a very small function, I reproduce its code here, with a size check
187 that causes failure if the size of msgbuf is exceeded. ****/
188
189   {
190   int i;
191   char * p;
192   int len = SVAL(&responseptr->uUser.len,0)/2;
193
194   if (  (off = IVAL(&responseptr->uUser.offset,0)) >= sizeof(SPAAuthResponse)
195      || len >= sizeof(responseptr->buffer)/2
196      || (p = (CS responseptr) + off) + len*2 >= CS (responseptr+1)
197      )
198     {
199     DEBUG(D_auth)
200       debug_printf("auth_spa_server(): bad uUser spec in response\n");
201     return FAIL;
202     }
203
204   if (len + 1 >= sizeof(msgbuf)) return FAIL;
205   for (i = 0; i < len; ++i)
206     {
207     msgbuf[i] = *p & 0x7f;
208     p += 2;
209     }
210   msgbuf[i] = 0;
211   }
212
213 /***************************************************************/
214
215 /* Put the username in $auth1 and $1. The former is now the preferred variable;
216 the latter is the original variable. These have to be out of stack memory, and
217 need to be available once known even if not authenticated, for error messages
218 (server_set_id, which only makes it to authenticated_id if we return OK) */
219
220 auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
221 expand_nlength[1] = Ustrlen(msgbuf);
222 expand_nmax = 1;
223
224 debug_print_string(ablock->server_debug_string);    /* customized debug */
225
226 /* look up password */
227
228 if (!(clearpass = expand_string(ob->spa_serverpassword)))
229   if (f.expand_string_forcedfail)
230     {
231     DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
232       "expanding spa_serverpassword\n");
233     return FAIL;
234     }
235   else
236     {
237     DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
238       "spa_serverpassword: %s\n", expand_string_message);
239     return DEFER;
240     }
241
242 /* create local hash copy */
243
244 spa_smb_encrypt(clearpass, challenge.challengeData, lmRespData);
245 spa_smb_nt_encrypt(clearpass, challenge.challengeData, ntRespData);
246
247 /* compare NT hash (LM may not be available) */
248
249 off = IVAL(&responseptr->ntResponse.offset,0);
250 if (off >= sizeof(SPAAuthResponse) - 24)
251   {
252   DEBUG(D_auth)
253     debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
254   return FAIL;
255   }
256 s = (US responseptr) + off;
257
258 if (memcmp(ntRespData, s, 24) == 0)
259   return auth_check_serv_cond(ablock);  /* success. we have a winner. */
260
261   /* Expand server_condition as an authorization check (PH) */
262
263 return FAIL;
264 }
265
266
267 /*************************************************
268 *              Client entry point                *
269 *************************************************/
270
271 /* For interface, see auths/README */
272
273 int
274 auth_spa_client(
275   auth_instance *ablock,                 /* authenticator block */
276   void * sx,                             /* connection */
277   int timeout,                           /* command timeout */
278   uschar *buffer,                        /* buffer for reading response */
279   int buffsize)                          /* size of buffer */
280 {
281 auth_spa_options_block *ob =
282        (auth_spa_options_block *)(ablock->options_block);
283 SPAAuthRequest   request;
284 SPAAuthChallenge challenge;
285 SPAAuthResponse  response;
286 char msgbuf[2048];
287 char *domain = NULL;
288 char *username, *password;
289
290 /* Code added by PH to expand the options */
291
292 *buffer = 0;    /* Default no message when cancelled */
293
294 if (!(username = CS expand_string(ob->spa_username)))
295   {
296   if (f.expand_string_forcedfail) return CANCELLED;
297   string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
298    "authenticator: %s", ob->spa_username, ablock->name,
299    expand_string_message);
300   return ERROR;
301   }
302
303 if (!(password = CS expand_string(ob->spa_password)))
304   {
305   if (f.expand_string_forcedfail) return CANCELLED;
306   string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
307    "authenticator: %s", ob->spa_password, ablock->name,
308    expand_string_message);
309   return ERROR;
310   }
311
312 if (ob->spa_domain)
313   if (!(domain = CS expand_string(ob->spa_domain)))
314     {
315     if (f.expand_string_forcedfail) return CANCELLED;
316     string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
317                   "authenticator: %s", ob->spa_domain, ablock->name,
318                   expand_string_message);
319     return ERROR;
320     }
321
322 /* Original code */
323
324 if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
325   return FAIL_SEND;
326
327 /* wait for the 3XX OK message */
328 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
329   return FAIL;
330
331 DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
332
333 spa_build_auth_request(&request, CS username, domain);
334 spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request));
335
336 DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
337
338 /* send the encrypted password */
339 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
340   return FAIL_SEND;
341
342 /* wait for the auth challenge */
343 if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
344   return FAIL;
345
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));
349
350 spa_build_auth_response(&challenge, &response, CS username, CS password);
351 spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response));
352 DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
353
354 /* send the challenge response */
355 if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
356        return FAIL_SEND;
357
358 /* If we receive a success response from the server, authentication
359 has succeeded. There may be more data to send, but is there any point
360 in provoking an error here? */
361
362 if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
363   return OK;
364
365 /* Not a success response. If errno != 0 there is some kind of transmission
366 error. Otherwise, check the response code in the buffer. If it starts with
367 '3', more data is expected. */
368
369 if (errno != 0 || buffer[0] != '3')
370   return FAIL;
371
372 return FAIL;
373 }
374
375 #endif   /*!MACRO_PREDEF*/
376 /* End of spa.c */