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