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