9c0064ae4a25225b429da84a5534f2b2b986d274
[exim.git] / src / src / auths / spa.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2017 */
6 /* See the file NOTICE for conditions of use and distribution. */
7
8 /* This file, which provides support for Microsoft's Secure Password
9 Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA
10 server support. I (PH) have only modified it in very trivial ways.
11
12 References:
13   http://www.innovation.ch/java/ntlm.html
14   http://www.kuro5hin.org/story/2002/4/28/1436/66154
15   http://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/%5bMS-SMTP%5d.pdf
16
17  * It seems that some systems have existing but different definitions of some
18  * of the following types. I received a complaint about "int16" causing
19  * compilation problems. So I (PH) have renamed them all, to be on the safe
20  * side, by adding 'x' on the end. See auths/auth-spa.h.
21
22  * typedef signed short int16;
23  * typedef unsigned short uint16;
24  * typedef unsigned uint32;
25  * typedef unsigned char  uint8;
26
27 07-August-2003:  PH: Patched up the code to avoid assert bombouts for stupid
28                      input data. Find appropriate comment by grepping for "PH".
29 16-October-2006: PH: Added a call to auth_check_serv_cond() at the end
30 05-June-2010:    PP: handle SASL initial response
31 */
32
33
34 #include "../exim.h"
35 #include "spa.h"
36
37 /* #define DEBUG_SPA */
38
39 #ifdef DEBUG_SPA
40 #define DSPA(x,y,z)   debug_printf(x,y,z)
41 #else
42 #define DSPA(x,y,z)
43 #endif
44
45 /* Options specific to the spa authentication mechanism. */
46
47 optionlist auth_spa_options[] = {
48   { "client_domain",             opt_stringptr,
49       (void *)(offsetof(auth_spa_options_block, spa_domain)) },
50   { "client_password",           opt_stringptr,
51       (void *)(offsetof(auth_spa_options_block, spa_password)) },
52   { "client_username",           opt_stringptr,
53       (void *)(offsetof(auth_spa_options_block, spa_username)) },
54   { "server_password",           opt_stringptr,
55       (void *)(offsetof(auth_spa_options_block, spa_serverpassword)) }
56 };
57
58 /* Size of the options list. An extern variable has to be used so that its
59 address can appear in the tables drtables.c. */
60
61 int auth_spa_options_count =
62   sizeof(auth_spa_options)/sizeof(optionlist);
63
64 /* Default private options block for the condition authentication method. */
65
66 auth_spa_options_block auth_spa_option_defaults = {
67   NULL,              /* spa_password */
68   NULL,              /* spa_username */
69   NULL,              /* spa_domain */
70   NULL               /* spa_serverpassword (for server side use) */
71 };
72
73
74 #ifdef MACRO_PREDEF
75
76 /* Dummy values */
77 void auth_spa_init(auth_instance *ablock) {}
78 int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
79 int auth_spa_client(auth_instance *ablock, smtp_inblock *inblock,
80   smtp_outblock *outblock, int timeout, uschar *buffer, int buffsize) {return 0;}
81
82 #else   /*!MACRO_PREDEF*/
83
84
85
86
87 /*************************************************
88 *          Initialization entry point            *
89 *************************************************/
90
91 /* Called for each instance, after its options have been read, to
92 enable consistency checks to be done, or anything else that needs
93 to be set up. */
94
95 void
96 auth_spa_init(auth_instance *ablock)
97 {
98 auth_spa_options_block *ob =
99   (auth_spa_options_block *)(ablock->options_block);
100
101 /* The public name defaults to the authenticator name */
102
103 if (ablock->public_name == NULL) ablock->public_name = ablock->name;
104
105 /* Both username and password must be set for a client */
106
107 if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
108   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n  "
109       "one of client_username and client_password cannot be set without "
110       "the other", ablock->name);
111 ablock->client = ob->spa_username != NULL;
112
113 /* For a server we have just one option */
114
115 ablock->server = ob->spa_serverpassword != NULL;
116 }
117
118
119
120 /*************************************************
121 *             Server entry point                 *
122 *************************************************/
123
124 /* For interface, see auths/README */
125
126 #define CVAL(buf,pos) ((US (buf))[pos])
127 #define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
128 #define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
129 #define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
130
131 int
132 auth_spa_server(auth_instance *ablock, uschar *data)
133 {
134 auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
135 uint8x lmRespData[24];
136 uint8x ntRespData[24];
137 SPAAuthRequest request;
138 SPAAuthChallenge challenge;
139 SPAAuthResponse  response;
140 SPAAuthResponse  *responseptr = &response;
141 uschar msgbuf[2048];
142 uschar *clearpass;
143
144 /* send a 334, MS Exchange style, and grab the client's request,
145 unless we already have it via an initial response. */
146
147 if ((*data == '\0') &&
148     (auth_get_no64_data(&data, US"NTLM supported") != OK))
149   {
150   /* something borked */
151   return FAIL;
152   }
153
154 if (spa_base64_to_bits(CS (&request), sizeof(request), CCS (data)) < 0)
155   {
156   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
157   "request: %s\n", data);
158   return FAIL;
159   }
160
161 /* create a challenge and send it back */
162
163 spa_build_auth_challenge(&request,&challenge);
164 spa_bits_to_base64 (msgbuf, (unsigned char*)&challenge,
165     spa_request_length(&challenge));
166
167 if (auth_get_no64_data(&data, msgbuf) != OK)
168   {
169   /* something borked */
170   return FAIL;
171   }
172
173 /* dump client response */
174 if (spa_base64_to_bits(CS (&response), sizeof(response), CCS (data)) < 0)
175   {
176   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
177   "response: %s\n", data);
178   return FAIL;
179   }
180
181 /***************************************************************
182 PH 07-Aug-2003: The original code here was this:
183
184 Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
185   IVAL(&responseptr->uUser.offset,0),
186   SVAL(&responseptr->uUser.len,0)/2) );
187
188 However, if the response data is too long, unicodeToString bombs out on
189 an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
190 idea. It's too messy to try to rework that function to return an error because
191 it is called from a number of other places in the auth-spa.c module. Instead,
192 since it is a very small function, I reproduce its code here, with a size check
193 that causes failure if the size of msgbuf is exceeded. ****/
194
195   {
196   int i;
197   char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
198   int len = SVAL(&responseptr->uUser.len,0)/2;
199
200   if (len + 1 >= sizeof(msgbuf)) return FAIL;
201   for (i = 0; i < len; ++i)
202     {
203     msgbuf[i] = *p & 0x7f;
204     p += 2;
205     }
206   msgbuf[i] = 0;
207   }
208
209 /***************************************************************/
210
211 /* Put the username in $auth1 and $1. The former is now the preferred variable;
212 the latter is the original variable. These have to be out of stack memory, and
213 need to be available once known even if not authenticated, for error messages
214 (server_set_id, which only makes it to authenticated_id if we return OK) */
215
216 auth_vars[0] = expand_nstring[1] = string_copy(msgbuf);
217 expand_nlength[1] = Ustrlen(msgbuf);
218 expand_nmax = 1;
219
220 debug_print_string(ablock->server_debug_string);    /* customized debug */
221
222 /* look up password */
223
224 clearpass = expand_string(ob->spa_serverpassword);
225 if (clearpass == NULL)
226   {
227   if (expand_string_forcedfail)
228     {
229     DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
230       "expanding spa_serverpassword\n");
231     return FAIL;
232     }
233   else
234     {
235     DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
236       "spa_serverpassword: %s\n", expand_string_message);
237     return DEFER;
238     }
239   }
240
241 /* create local hash copy */
242
243 spa_smb_encrypt (clearpass, challenge.challengeData, lmRespData);
244 spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData);
245
246 /* compare NT hash (LM may not be available) */
247
248 if (memcmp(ntRespData,
249       ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0),
250       24) == 0)
251   /* success. we have a winner. */
252   {
253   return auth_check_serv_cond(ablock);
254   }
255
256   /* Expand server_condition as an authorization check (PH) */
257
258 return FAIL;
259 }
260
261
262 /*************************************************
263 *              Client entry point                *
264 *************************************************/
265
266 /* For interface, see auths/README */
267
268 int
269 auth_spa_client(
270   auth_instance *ablock,                 /* authenticator block */
271   smtp_inblock *inblock,                 /* connection inblock */
272   smtp_outblock *outblock,               /* connection outblock */
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 (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 (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   {
310   if (!(domain = CS expand_string(ob->spa_domain)))
311     {
312     if (expand_string_forcedfail) return CANCELLED;
313     string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
314                   "authenticator: %s", ob->spa_domain, ablock->name,
315                   expand_string_message);
316     return ERROR;
317     }
318   }
319
320 /* Original code */
321
322 if (smtp_write_command(outblock, SCMD_FLUSH, "AUTH %s\r\n",
323     ablock->public_name) < 0)
324   return FAIL_SEND;
325
326 /* wait for the 3XX OK message */
327 if (!smtp_read_response(inblock, US buffer, buffsize, '3', timeout))
328   return FAIL;
329
330 DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
331
332 spa_build_auth_request (&request, CS username, domain);
333 spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
334        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(outblock, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
340   return FAIL_SEND;
341
342 /* wait for the auth challenge */
343 if (!smtp_read_response(inblock, 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, (unsigned char*)&response,
352        spa_request_length(&response));
353 DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
354
355 /* send the challenge response */
356 if (smtp_write_command(outblock, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
357        return FAIL_SEND;
358
359 /* If we receive a success response from the server, authentication
360 has succeeded. There may be more data to send, but is there any point
361 in provoking an error here? */
362
363 if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout))
364   return OK;
365
366 /* Not a success response. If errno != 0 there is some kind of transmission
367 error. Otherwise, check the response code in the buffer. If it starts with
368 '3', more data is expected. */
369
370 if (errno != 0 || buffer[0] != '3')
371   return FAIL;
372
373 return FAIL;
374 }
375
376 #endif   /*!MACRO_PREDEF*/
377 /* End of spa.c */