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