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