Doc: Add DKIM info in main sections. Bug 1607
[exim.git] / src / src / auths / cyrus_sasl.c
... / ...
CommitLineData
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
5/* Copyright (c) University of Cambridge 1995 - 2015 */
6/* See the file NOTICE for conditions of use and distribution. */
7
8/* This code was originally contributed by Matthew Byng-Maddick */
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);
29static void dummy2(int x) { dummy(x-1); }
30static void dummy(int x) { dummy2(x-1); }
31#else
32
33
34#include <sasl/sasl.h>
35#include "cyrus_sasl.h"
36
37/* Options specific to the cyrus_sasl authentication mechanism. */
38
39optionlist auth_cyrus_sasl_options[] = {
40 { "server_hostname", opt_stringptr,
41 (void *)(offsetof(auth_cyrus_sasl_options_block, server_hostname)) },
42 { "server_mech", opt_stringptr,
43 (void *)(offsetof(auth_cyrus_sasl_options_block, server_mech)) },
44 { "server_realm", opt_stringptr,
45 (void *)(offsetof(auth_cyrus_sasl_options_block, server_realm)) },
46 { "server_service", opt_stringptr,
47 (void *)(offsetof(auth_cyrus_sasl_options_block, server_service)) }
48};
49
50/* Size of the options list. An extern variable has to be used so that its
51address can appear in the tables drtables.c. */
52
53int auth_cyrus_sasl_options_count =
54 sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
55
56/* Default private options block for the cyrus_sasl authentication method. */
57
58auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
59 US"smtp", /* server_service */
60 US"$primary_hostname", /* server_hostname */
61 NULL, /* server_realm */
62 NULL /* server_mech */
63};
64
65
66/*************************************************
67* Initialization entry point *
68*************************************************/
69
70/* Called for each instance, after its options have been read, to
71enable consistency checks to be done, or anything else that needs
72to be set up. */
73
74
75/* Auxiliary function, passed in data to sasl_server_init(). */
76
77static int
78mysasl_config(void *context,
79 const char *plugin_name,
80 const char *option,
81 const char **result,
82 unsigned int *len)
83{
84if (context && !strcmp(option, "mech_list"))
85 {
86 *result = context;
87 if (len != NULL) *len = strlen(*result);
88 return SASL_OK;
89 }
90return SASL_FAIL;
91}
92
93/* Here's the real function */
94
95void
96auth_cyrus_sasl_init(auth_instance *ablock)
97{
98auth_cyrus_sasl_options_block *ob =
99 (auth_cyrus_sasl_options_block *)(ablock->options_block);
100const uschar *list, *listptr, *buffer;
101int rc, i;
102unsigned int len;
103uschar *rs_point, *expanded_hostname;
104char *realm_expanded;
105
106sasl_conn_t *conn;
107sasl_callback_t cbs[]={
108 {SASL_CB_GETOPT, NULL, NULL },
109 {SASL_CB_LIST_END, NULL, NULL}};
110
111/* default the mechanism to our "public name" */
112if(ob->server_mech == NULL)
113 ob->server_mech=string_copy(ablock->public_name);
114
115expanded_hostname = expand_string(ob->server_hostname);
116if (expanded_hostname == NULL)
117 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
118 "couldn't expand server_hostname [%s]: %s",
119 ablock->name, ob->server_hostname, expand_string_message);
120
121realm_expanded=NULL;
122if (ob->server_realm != NULL) {
123 realm_expanded = CS expand_string(ob->server_realm);
124 if (realm_expanded == NULL)
125 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
126 "couldn't expand server_realm [%s]: %s",
127 ablock->name, ob->server_realm, expand_string_message);
128}
129
130/* we're going to initialise the library to check that there is an
131 * authenticator of type whatever mechanism we're using
132 */
133
134cbs[0].proc = (int(*)(void)) &mysasl_config;
135cbs[0].context = ob->server_mech;
136
137rc=sasl_server_init(cbs, "exim");
138
139if( rc != SASL_OK )
140 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
141 "couldn't initialise Cyrus SASL library.", ablock->name);
142
143rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
144 realm_expanded, NULL, NULL, NULL, 0, &conn);
145if( rc != SASL_OK )
146 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
147 "couldn't initialise Cyrus SASL server connection.", ablock->name);
148
149rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
150if( rc != SASL_OK )
151 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
152 "couldn't get Cyrus SASL mechanism list.", ablock->name);
153
154i=':';
155listptr=list;
156
157HDEBUG(D_auth) {
158 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
159 ob->server_service, expanded_hostname, realm_expanded);
160 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
161}
162
163/* the store_get / store_reset mechanism is hierarchical
164 * the hierarchy is stored for us behind our back. This point
165 * creates a hierarchy point for this function.
166 */
167rs_point=store_get(0);
168
169/* loop until either we get to the end of the list, or we match the
170 * public name of this authenticator
171 */
172while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
173 strcmpic(buffer,ob->server_mech) );
174
175if(!buffer)
176 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
177 "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
178
179store_reset(rs_point);
180
181HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
182
183/* make sure that if we get here then we're allowed to advertise. */
184ablock->server = TRUE;
185
186sasl_dispose(&conn);
187sasl_done();
188}
189
190/*************************************************
191* Server entry point *
192*************************************************/
193
194/* For interface, see auths/README */
195
196/* note, we don't care too much about memory allocation in this, because this is entirely
197 * within a shortlived child
198 */
199
200int
201auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
202{
203auth_cyrus_sasl_options_block *ob =
204 (auth_cyrus_sasl_options_block *)(ablock->options_block);
205uschar *output, *out2, *input, *clear, *hname;
206uschar *debug = NULL; /* Stops compiler complaining */
207sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
208sasl_conn_t *conn;
209char *realm_expanded;
210int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
211unsigned int inlen, outlen;
212
213input=data;
214inlen=Ustrlen(data);
215
216HDEBUG(D_auth) debug=string_copy(data);
217
218hname=expand_string(ob->server_hostname);
219realm_expanded=NULL;
220if (hname && ob->server_realm)
221 realm_expanded= CS expand_string(ob->server_realm);
222if((hname == NULL) ||
223 ((realm_expanded == NULL) && (ob->server_realm != NULL)))
224 {
225 auth_defer_msg = expand_string_message;
226 return DEFER;
227 }
228
229if(inlen)
230 {
231 clen=auth_b64decode(input, &clear);
232 if(clen < 0)
233 {
234 return BAD64;
235 }
236 input=clear;
237 inlen=clen;
238 }
239
240rc=sasl_server_init(cbs, "exim");
241if (rc != SASL_OK)
242 {
243 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
244 return DEFER;
245 }
246
247rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
248 NULL, NULL, 0, &conn);
249
250HDEBUG(D_auth)
251 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
252 ob->server_service, hname, realm_expanded);
253
254if( rc != SASL_OK )
255 {
256 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
257 sasl_done();
258 return DEFER;
259 }
260
261if (tls_in.cipher)
262 {
263 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
264 if (rc != SASL_OK)
265 {
266 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
267 tls_in.bits, sasl_errstring(rc, NULL, NULL));
268 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
269 sasl_done();
270 return DEFER;
271 }
272 else
273 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
274 }
275else
276 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
277
278/* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
279annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
280with their iptostring() function, which just wraps
281getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
282inet_ntop which we wrap in our host_ntoa() function.
283
284So the docs are too strict and we shouldn't worry about :: contractions. */
285
286/* Set properties for remote and local host-ip;port */
287for (i=0; i < 2; ++i)
288 {
289 struct sockaddr_storage ss;
290 int (*query)(int, struct sockaddr *, socklen_t *);
291 int propnum, port;
292 const uschar *label;
293 uschar *address, *address_port;
294 const char *s_err;
295 socklen_t sslen;
296
297 if (i)
298 {
299 query = &getpeername;
300 propnum = SASL_IPREMOTEPORT;
301 label = CUS"peer";
302 }
303 else
304 {
305 query = &getsockname;
306 propnum = SASL_IPLOCALPORT;
307 label = CUS"local";
308 }
309
310 sslen = sizeof(ss);
311 rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
312 if (rc < 0)
313 {
314 HDEBUG(D_auth)
315 debug_printf("Failed to get %s address information: %s\n",
316 label, strerror(errno));
317 break;
318 }
319
320 address = host_ntoa(-1, &ss, NULL, &port);
321 address_port = string_sprintf("%s;%d", address, port);
322
323 rc = sasl_setprop(conn, propnum, address_port);
324 if (rc != SASL_OK)
325 {
326 s_err = sasl_errdetail(conn);
327 HDEBUG(D_auth)
328 debug_printf("Failed to set %s SASL property: [%d] %s\n",
329 label, rc, s_err ? s_err : "<unknown reason>");
330 break;
331 }
332 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
333 label, address_port);
334 }
335
336rc=SASL_CONTINUE;
337
338while(rc==SASL_CONTINUE)
339 {
340 if(firsttime)
341 {
342 firsttime=0;
343 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
344 rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
345 (const char **)(&output), &outlen);
346 }
347 else
348 {
349 /* make sure that we have a null-terminated string */
350 out2=store_get(outlen+1);
351 memcpy(out2,output,outlen);
352 out2[outlen]='\0';
353 if((rc=auth_get_data(&input, out2, outlen))!=OK)
354 {
355 /* we couldn't get the data, so free up the library before
356 * returning whatever error we get */
357 sasl_dispose(&conn);
358 sasl_done();
359 return rc;
360 }
361 inlen=Ustrlen(input);
362
363 HDEBUG(D_auth) debug=string_copy(input);
364 if(inlen)
365 {
366 clen=auth_b64decode(input, &clear);
367 if(clen < 0)
368 {
369 sasl_dispose(&conn);
370 sasl_done();
371 return BAD64;
372 }
373 input=clear;
374 inlen=clen;
375 }
376
377 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
378 rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
379 }
380 if(rc==SASL_BADPROT)
381 {
382 sasl_dispose(&conn);
383 sasl_done();
384 return UNEXPECTED;
385 }
386 else if( rc==SASL_FAIL || rc==SASL_BUFOVER
387 || rc==SASL_BADMAC || rc==SASL_BADAUTH
388 || rc==SASL_NOAUTHZ || rc==SASL_ENCRYPT
389 || rc==SASL_EXPIRED || rc==SASL_DISABLED
390 || rc==SASL_NOUSER )
391 {
392 /* these are considered permanent failure codes */
393 HDEBUG(D_auth)
394 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
395 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
396 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
397 sasl_errstring(rc, NULL, NULL));
398 sasl_dispose(&conn);
399 sasl_done();
400 return FAIL;
401 }
402 else if(rc==SASL_NOMECH)
403 {
404 /* this is a temporary failure, because the mechanism is not
405 * available for this user. If it wasn't available at all, we
406 * shouldn't have got here in the first place...
407 */
408 HDEBUG(D_auth)
409 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
410 auth_defer_msg =
411 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
412 sasl_dispose(&conn);
413 sasl_done();
414 return DEFER;
415 }
416 else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
417 {
418 /* Anything else is a temporary failure, and we'll let SASL print out
419 * the error string for us
420 */
421 HDEBUG(D_auth)
422 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
423 auth_defer_msg =
424 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
425 sasl_dispose(&conn);
426 sasl_done();
427 return DEFER;
428 }
429 else if(rc==SASL_OK)
430 {
431 /* Get the username and copy it into $auth1 and $1. The former is now the
432 preferred variable; the latter is the original variable. */
433 rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
434 if (rc != SASL_OK)
435 {
436 HDEBUG(D_auth)
437 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
438 sasl_errstring(rc, NULL, NULL));
439 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
440 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
441 sasl_errstring(rc, NULL, NULL));
442 sasl_dispose(&conn);
443 sasl_done();
444 return FAIL;
445 }
446
447 auth_vars[0] = expand_nstring[1] = string_copy(out2);
448 expand_nlength[1] = Ustrlen(expand_nstring[1]);
449 expand_nmax = 1;
450
451 HDEBUG(D_auth)
452 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
453 ob->server_mech, auth_vars[0]);
454
455 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
456 if (rc != SASL_OK)
457 {
458 HDEBUG(D_auth)
459 debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
460 sasl_errstring(rc, NULL, NULL));
461 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
462 "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
463 sasl_errstring(rc, NULL, NULL));
464 sasl_dispose(&conn);
465 sasl_done();
466 return FAIL;
467 }
468 negotiated_ssf = *negotiated_ssf_ptr;
469 HDEBUG(D_auth)
470 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
471 if (negotiated_ssf > 0)
472 {
473 HDEBUG(D_auth)
474 debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
475 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
476 "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
477 sasl_dispose(&conn);
478 sasl_done();
479 return FAIL;
480 }
481
482 /* close down the connection, freeing up library's memory */
483 sasl_dispose(&conn);
484 sasl_done();
485
486 /* Expand server_condition as an authorization check */
487 return auth_check_serv_cond(ablock);
488 }
489 }
490/* NOTREACHED */
491return 0; /* Stop compiler complaints */
492}
493
494/*************************************************
495* Diagnostic API *
496*************************************************/
497
498void
499auth_cyrus_sasl_version_report(FILE *f)
500{
501 const char *implementation, *version;
502 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
503 fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
504 " Runtime: %s [%s]\n",
505 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
506 version, implementation);
507}
508
509/*************************************************
510* Client entry point *
511*************************************************/
512
513/* For interface, see auths/README */
514
515int
516auth_cyrus_sasl_client(
517 auth_instance *ablock, /* authenticator block */
518 smtp_inblock *inblock, /* input connection */
519 smtp_outblock *outblock, /* output connection */
520 int timeout, /* command timeout */
521 uschar *buffer, /* for reading response */
522 int buffsize) /* size of buffer */
523{
524/* We don't support clients (yet) in this implementation of cyrus_sasl */
525return FAIL;
526}
527
528#endif /* AUTH_CYRUS_SASL */
529
530/* End of cyrus_sasl.c */