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