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