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