Various SASL fixes.
[exim.git] / src / src / auths / cyrus_sasl.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
0a49a7a4 5/* Copyright (c) University of Cambridge 1995 - 2009 */
0756eb3c
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
c5ddb310 8/* This code was originally contributed by Matthew Byng-Maddick */
0756eb3c
PH
9
10/* Copyright (c) A L Digital 2004 */
11
12/* A generic (mechanism independent) Cyrus SASL authenticator. */
13
14
15#include "../exim.h"
16
17
18/* We can't just compile this code and allow the library mechanism to omit the
19functions if they are not wanted, because we need to have the Cyrus SASL header
20available for compiling. Therefore, compile these functions only if
21AUTH_CYRUS_SASL is defined. However, some compilers don't like compiling empty
22modules, so keep them happy with a dummy when skipping the rest. Make it
23reference itself to stop picky compilers complaining that it is unused, and put
24in a dummy argument to stop even pickier compilers complaining about infinite
25loops. */
26
27#ifndef AUTH_CYRUS_SASL
28static void dummy(int x) { dummy(x-1); }
29#else
30
31
32#include <sasl/sasl.h>
33#include "cyrus_sasl.h"
34
35/* Options specific to the cyrus_sasl authentication mechanism. */
36
37optionlist auth_cyrus_sasl_options[] = {
38 { "server_hostname", opt_stringptr,
39 (void *)(offsetof(auth_cyrus_sasl_options_block, server_hostname)) },
40 { "server_mech", opt_stringptr,
41 (void *)(offsetof(auth_cyrus_sasl_options_block, server_mech)) },
42 { "server_realm", opt_stringptr,
43 (void *)(offsetof(auth_cyrus_sasl_options_block, server_realm)) },
44 { "server_service", opt_stringptr,
45 (void *)(offsetof(auth_cyrus_sasl_options_block, server_service)) }
46};
47
48/* Size of the options list. An extern variable has to be used so that its
49address can appear in the tables drtables.c. */
50
51int auth_cyrus_sasl_options_count =
52 sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
53
16ff981e 54/* Default private options block for the cyrus_sasl authentication method. */
0756eb3c
PH
55
56auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
57 US"smtp", /* server_service */
58 US"$primary_hostname", /* server_hostname */
59 NULL, /* server_realm */
60 NULL /* server_mech */
61};
62
63
64/*************************************************
65* Initialization entry point *
66*************************************************/
67
68/* Called for each instance, after its options have been read, to
69enable consistency checks to be done, or anything else that needs
70to be set up. */
71
c5ddb310
PH
72
73/* Auxiliary function, passed in data to sasl_server_init(). */
74
75static int
76mysasl_config(void *context,
77 const char *plugin_name,
78 const char *option,
79 const char **result,
80 unsigned int *len)
81{
82if (context && !strcmp(option, "mech_list"))
83 {
84 *result = context;
85 if (len != NULL) *len = strlen(*result);
86 return SASL_OK;
87 }
88return SASL_FAIL;
89}
90
91/* Here's the real function */
92
0756eb3c
PH
93void
94auth_cyrus_sasl_init(auth_instance *ablock)
95{
96auth_cyrus_sasl_options_block *ob =
97 (auth_cyrus_sasl_options_block *)(ablock->options_block);
0756eb3c
PH
98uschar *list, *listptr, *buffer;
99int rc, i;
100unsigned int len;
edc33b5f 101uschar *rs_point, *expanded_hostname;
0756eb3c 102
c5ddb310
PH
103sasl_conn_t *conn;
104sasl_callback_t cbs[]={
105 {SASL_CB_GETOPT, NULL, NULL },
106 {SASL_CB_LIST_END, NULL, NULL}};
107
0756eb3c
PH
108/* default the mechanism to our "public name" */
109if(ob->server_mech == NULL)
110 ob->server_mech=string_copy(ablock->public_name);
111
edc33b5f
PP
112expanded_hostname = expand_string(ob->server_hostname);
113if (expanded_hostname == NULL)
114 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
115 "couldn't expand server_hostname [%s]: %s",
116 ablock->name, ob->server_hostname, expand_string_message);
117
0756eb3c
PH
118/* we're going to initialise the library to check that there is an
119 * authenticator of type whatever mechanism we're using
120 */
c5ddb310 121
edc33b5f 122cbs[0].proc = (int(*)(void)) &mysasl_config;
c5ddb310
PH
123cbs[0].context = ob->server_mech;
124
0756eb3c 125rc=sasl_server_init(cbs, "exim");
c5ddb310 126
0756eb3c
PH
127if( rc != SASL_OK )
128 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
129 "couldn't initialise Cyrus SASL library.", ablock->name);
130
edc33b5f 131rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
0756eb3c
PH
132 CS ob->server_realm, NULL, NULL, NULL, 0, &conn);
133if( rc != SASL_OK )
134 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
135 "couldn't initialise Cyrus SASL server connection.", ablock->name);
136
137rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)(&list), &len, &i);
138if( rc != SASL_OK )
139 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
140 "couldn't get Cyrus SASL mechanism list.", ablock->name);
141
142i=':';
143listptr=list;
144
edc33b5f
PP
145HDEBUG(D_auth) {
146 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
147 ob->server_service, expanded_hostname, ob->server_realm);
148 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
149}
0756eb3c
PH
150
151/* the store_get / store_reset mechanism is hierarchical
152 * the hierarchy is stored for us behind our back. This point
153 * creates a hierarchy point for this function.
154 */
155rs_point=store_get(0);
156
157/* loop until either we get to the end of the list, or we match the
158 * public name of this authenticator
159 */
160while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
161 strcmpic(buffer,ob->server_mech) );
162
163if(!buffer)
164 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
165 "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
166
167store_reset(rs_point);
168
169HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
170
171/* make sure that if we get here then we're allowed to advertise. */
172ablock->server = TRUE;
173
174sasl_dispose(&conn);
175sasl_done();
176}
177
178/*************************************************
179* Server entry point *
180*************************************************/
181
182/* For interface, see auths/README */
183
184/* note, we don't care too much about memory allocation in this, because this is entirely
185 * within a shortlived child
186 */
187
188int
189auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
190{
191auth_cyrus_sasl_options_block *ob =
192 (auth_cyrus_sasl_options_block *)(ablock->options_block);
193uschar *output, *out2, *input, *clear, *hname;
194uschar *debug = NULL; /* Stops compiler complaining */
195sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
196sasl_conn_t *conn;
edc33b5f 197int rc, firsttime=1, clen, negotiated_ssf;
0756eb3c
PH
198unsigned int inlen, outlen;
199
200input=data;
201inlen=Ustrlen(data);
202
203HDEBUG(D_auth) debug=string_copy(data);
204
205hname=expand_string(ob->server_hostname);
206if(hname == NULL)
207 {
208 auth_defer_msg = expand_string_message;
209 return DEFER;
210 }
211
212if(inlen)
213 {
214 clen=auth_b64decode(input, &clear);
215 if(clen < 0)
216 {
217 return BAD64;
218 }
219 input=clear;
220 inlen=clen;
221 }
222
223rc=sasl_server_init(cbs, "exim");
224if (rc != SASL_OK)
225 {
226 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
227 return DEFER;
228 }
229
acb1b346
PH
230rc=sasl_server_new(CS ob->server_service, CS hname, CS ob->server_realm, NULL,
231 NULL, NULL, 0, &conn);
232
edc33b5f
PP
233HDEBUG(D_auth)
234 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
235 ob->server_service, hname, ob->server_realm);
236
0756eb3c
PH
237if( rc != SASL_OK )
238 {
239 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
240 sasl_done();
241 return DEFER;
242 }
243
edc33b5f
PP
244if (tls_cipher)
245 {
246 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, &tls_bits);
247 if (rc != SASL_OK)
248 {
249 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
250 tls_bits, sasl_errstring(rc, NULL, NULL));
251 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
252 sasl_done();
253 return DEFER;
254 }
255 else
256 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_bits);
257 }
258else
259 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
260
0756eb3c
PH
261rc=SASL_CONTINUE;
262
263while(rc==SASL_CONTINUE)
264 {
265 if(firsttime)
266 {
267 firsttime=0;
268 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
269 rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
270 (const char **)(&output), &outlen);
271 }
272 else
273 {
274 /* make sure that we have a null-terminated string */
275 out2=store_get(outlen+1);
276 memcpy(out2,output,outlen);
277 out2[outlen]='\0';
278 if((rc=auth_get_data(&input, out2, outlen))!=OK)
279 {
280 /* we couldn't get the data, so free up the library before
281 * returning whatever error we get */
282 sasl_dispose(&conn);
283 sasl_done();
284 return rc;
285 }
286 inlen=Ustrlen(input);
287
288 HDEBUG(D_auth) debug=string_copy(input);
289 if(inlen)
290 {
291 clen=auth_b64decode(input, &clear);
292 if(clen < 0)
293 {
294 sasl_dispose(&conn);
295 sasl_done();
296 return BAD64;
297 }
298 input=clear;
299 inlen=clen;
300 }
301
302 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
303 rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
304 }
305 if(rc==SASL_BADPROT)
306 {
307 sasl_dispose(&conn);
308 sasl_done();
309 return UNEXPECTED;
310 }
311 else if( rc==SASL_FAIL || rc==SASL_BUFOVER
312 || rc==SASL_BADMAC || rc==SASL_BADAUTH
313 || rc==SASL_NOAUTHZ || rc==SASL_ENCRYPT
314 || rc==SASL_EXPIRED || rc==SASL_DISABLED
315 || rc==SASL_NOUSER )
316 {
317 /* these are considered permanent failure codes */
318 HDEBUG(D_auth)
319 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
320 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
321 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
322 sasl_errstring(rc, NULL, NULL));
323 sasl_dispose(&conn);
324 sasl_done();
325 return FAIL;
326 }
327 else if(rc==SASL_NOMECH)
328 {
329 /* this is a temporary failure, because the mechanism is not
330 * available for this user. If it wasn't available at all, we
331 * shouldn't have got here in the first place...
332 */
333 HDEBUG(D_auth)
334 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
335 auth_defer_msg =
336 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
337 sasl_dispose(&conn);
338 sasl_done();
339 return DEFER;
340 }
341 else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
342 {
343 /* Anything else is a temporary failure, and we'll let SASL print out
344 * the error string for us
345 */
346 HDEBUG(D_auth)
347 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
348 auth_defer_msg =
349 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
350 sasl_dispose(&conn);
351 sasl_done();
352 return DEFER;
353 }
354 else if(rc==SASL_OK)
355 {
f78eb7c6
PH
356 /* Get the username and copy it into $auth1 and $1. The former is now the
357 preferred variable; the latter is the original variable. */
358 rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
edc33b5f
PP
359 if (rc != SASL_OK)
360 {
361 HDEBUG(D_auth)
362 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
363 sasl_errstring(rc, NULL, NULL));
364 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
365 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
366 sasl_errstring(rc, NULL, NULL));
367 sasl_dispose(&conn);
368 sasl_done();
369 return FAIL;
370 }
371
f78eb7c6
PH
372 auth_vars[0] = expand_nstring[1] = string_copy(out2);
373 expand_nlength[1] = Ustrlen(expand_nstring[1]);
374 expand_nmax = 1;
0756eb3c
PH
375
376 HDEBUG(D_auth)
edc33b5f
PP
377 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
378 ob->server_mech, auth_vars[0]);
379
380 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf));
381 if (rc != SASL_OK)
382 {
383 HDEBUG(D_auth)
384 debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
385 sasl_errstring(rc, NULL, NULL));
386 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
387 "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
388 sasl_errstring(rc, NULL, NULL));
389 sasl_dispose(&conn);
390 sasl_done();
391 return FAIL;
392 }
393 HDEBUG(D_auth)
394 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
395 if (negotiated_ssf > 0)
396 {
397 HDEBUG(D_auth)
398 debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
399 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
400 "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
401 sasl_dispose(&conn);
402 sasl_done();
403 return FAIL;
404 }
405
0756eb3c
PH
406 /* close down the connection, freeing up library's memory */
407 sasl_dispose(&conn);
408 sasl_done();
16ff981e
PH
409
410 /* Expand server_condition as an authorization check */
411 return auth_check_serv_cond(ablock);
0756eb3c
PH
412 }
413 }
414/* NOTREACHED */
415return 0; /* Stop compiler complaints */
416}
417
6545de78
PP
418/*************************************************
419* Diagnostic API *
420*************************************************/
421
422void
423auth_cyrus_sasl_version_report(FILE *f)
424{
425 const char *implementation, *version;
426 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
427 fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
428 " Runtime: %s [%s]\n",
429 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
430 version, implementation);
431}
432
0756eb3c
PH
433/*************************************************
434* Client entry point *
435*************************************************/
436
437/* For interface, see auths/README */
438
439int
440auth_cyrus_sasl_client(
441 auth_instance *ablock, /* authenticator block */
442 smtp_inblock *inblock, /* input connection */
443 smtp_outblock *outblock, /* output connection */
444 int timeout, /* command timeout */
445 uschar *buffer, /* for reading response */
446 int buffsize) /* size of buffer */
447{
448/* We don't support clients (yet) in this implementation of cyrus_sasl */
449return FAIL;
450}
451
452#endif /* AUTH_CYRUS_SASL */
453
454/* End of cyrus_sasl.c */