Copyright updates:
[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 */
1e1ddfac 6/* Copyright (c) The Exim Maintainers 2020 */
0756eb3c
PH
7/* See the file NOTICE for conditions of use and distribution. */
8
c5ddb310 9/* This code was originally contributed by Matthew Byng-Maddick */
0756eb3c
PH
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
e1d15f5e
JH
29static void dummy(int x);
30static void dummy2(int x) { dummy(x-1); }
31static void dummy(int x) { dummy2(x-1); }
0756eb3c
PH
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,
13a4b4c1 42 OPT_OFF(auth_cyrus_sasl_options_block, server_hostname) },
0756eb3c 43 { "server_mech", opt_stringptr,
13a4b4c1 44 OPT_OFF(auth_cyrus_sasl_options_block, server_mech) },
0756eb3c 45 { "server_realm", opt_stringptr,
13a4b4c1 46 OPT_OFF(auth_cyrus_sasl_options_block, server_realm) },
0756eb3c 47 { "server_service", opt_stringptr,
13a4b4c1 48 OPT_OFF(auth_cyrus_sasl_options_block, server_service) }
0756eb3c
PH
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
16ff981e 57/* Default private options block for the cyrus_sasl authentication method. */
0756eb3c
PH
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
d185889f
JH
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;}
251b9eb4
JH
72int auth_cyrus_sasl_client(auth_instance *ablock, void * sx,
73 int timeout, uschar *buffer, int buffsize) {return 0;}
f9df71c0 74void auth_cyrus_sasl_version_report(FILE *f) {}
d185889f
JH
75
76#else /*!MACRO_PREDEF*/
77
78
79
80
0756eb3c
PH
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
c5ddb310
PH
89
90/* Auxiliary function, passed in data to sasl_server_init(). */
91
92static int
6a2c32cb
JH
93mysasl_config(void *context, const char *plugin_name, const char *option,
94 const char **result, unsigned int *len)
c5ddb310
PH
95{
96if (context && !strcmp(option, "mech_list"))
97 {
98 *result = context;
6a2c32cb 99 if (len) *len = strlen(*result);
c5ddb310
PH
100 return SASL_OK;
101 }
102return SASL_FAIL;
103}
104
105/* Here's the real function */
106
0756eb3c
PH
107void
108auth_cyrus_sasl_init(auth_instance *ablock)
109{
110auth_cyrus_sasl_options_block *ob =
111 (auth_cyrus_sasl_options_block *)(ablock->options_block);
93a6fce2 112const uschar *list, *listptr, *buffer;
0756eb3c
PH
113int rc, i;
114unsigned int len;
f3ebb786
JH
115rmark rs_point;
116uschar *expanded_hostname;
4c287009 117char *realm_expanded;
0756eb3c 118
c5ddb310 119sasl_conn_t *conn;
c0fb53b7 120sasl_callback_t cbs[] = {
c5ddb310
PH
121 {SASL_CB_GETOPT, NULL, NULL },
122 {SASL_CB_LIST_END, NULL, NULL}};
123
0756eb3c 124/* default the mechanism to our "public name" */
0756eb3c 125
6a2c32cb
JH
126if (!ob->server_mech) ob->server_mech = string_copy(ablock->public_name);
127
128if (!(expanded_hostname = expand_string(ob->server_hostname)))
edc33b5f
PP
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
c0fb53b7 133realm_expanded = NULL;
6a2c32cb
JH
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);
4c287009 139
0756eb3c 140/* we're going to initialise the library to check that there is an
6a2c32cb 141authenticator of type whatever mechanism we're using */
c5ddb310 142
edc33b5f 143cbs[0].proc = (int(*)(void)) &mysasl_config;
c5ddb310
PH
144cbs[0].context = ob->server_mech;
145
6a2c32cb 146if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
0756eb3c
PH
147 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
148 "couldn't initialise Cyrus SASL library.", ablock->name);
149
c0fb53b7 150if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname,
6a2c32cb 151 realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK)
0756eb3c
PH
152 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
153 "couldn't initialise Cyrus SASL server connection.", ablock->name);
154
3d2e82c5 155if ((rc = sasl_listmech(conn, NULL, "", ":", "", CCSS &list, &len, &i)) != SASL_OK)
0756eb3c
PH
156 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
157 "couldn't get Cyrus SASL mechanism list.", ablock->name);
158
c0fb53b7
JH
159i = ':';
160listptr = list;
0756eb3c 161
c0fb53b7
JH
162HDEBUG(D_auth)
163 {
edc33b5f 164 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
4c287009 165 ob->server_service, expanded_hostname, realm_expanded);
edc33b5f 166 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
c0fb53b7 167 }
0756eb3c
PH
168
169/* the store_get / store_reset mechanism is hierarchical
6a2c32cb
JH
170 the hierarchy is stored for us behind our back. This point
171 creates a hierarchy point for this function. */
172
f3ebb786 173rs_point = store_mark();
0756eb3c
PH
174
175/* loop until either we get to the end of the list, or we match the
6a2c32cb
JH
176public name of this authenticator */
177
178while ( (buffer = string_nextinlist(&listptr, &i, NULL, 0))
179 && strcmpic(buffer,ob->server_mech) );
0756eb3c 180
c0fb53b7 181if (!buffer)
0756eb3c
PH
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
6a2c32cb 203within a shortlived child */
0756eb3c
PH
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 */
c0fb53b7 212sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
0756eb3c 213sasl_conn_t *conn;
c0fb53b7 214char * realm_expanded = NULL;
d7978c0f 215int rc, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf;
0756eb3c
PH
216unsigned int inlen, outlen;
217
c0fb53b7
JH
218input = data;
219inlen = Ustrlen(data);
0756eb3c 220
c0fb53b7 221HDEBUG(D_auth) debug = string_copy(data);
0756eb3c 222
c0fb53b7 223hname = expand_string(ob->server_hostname);
4c287009 224if (hname && ob->server_realm)
c0fb53b7
JH
225 realm_expanded = CS expand_string(ob->server_realm);
226if (!hname || !realm_expanded && ob->server_realm)
0756eb3c
PH
227 {
228 auth_defer_msg = expand_string_message;
229 return DEFER;
230 }
231
c0fb53b7 232if (inlen)
0756eb3c 233 {
c0fb53b7 234 if ((clen = b64decode(input, &clear)) < 0)
0756eb3c 235 return BAD64;
c0fb53b7
JH
236 input = clear;
237 inlen = clen;
0756eb3c
PH
238 }
239
c0fb53b7 240if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
0756eb3c
PH
241 {
242 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
243 return DEFER;
244 }
245
c0fb53b7 246rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
acb1b346
PH
247 NULL, NULL, 0, &conn);
248
edc33b5f
PP
249HDEBUG(D_auth)
250 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
4c287009 251 ob->server_service, hname, realm_expanded);
edc33b5f 252
c0fb53b7 253if (rc != SASL_OK )
0756eb3c
PH
254 {
255 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
256 sasl_done();
257 return DEFER;
258 }
259
817d9f57 260if (tls_in.cipher)
edc33b5f 261 {
c0fb53b7 262 if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
edc33b5f
PP
263 {
264 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
817d9f57 265 tls_in.bits, sasl_errstring(rc, NULL, NULL));
edc33b5f
PP
266 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
267 sasl_done();
268 return DEFER;
269 }
270 else
817d9f57 271 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
6a2c32cb
JH
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. */
edc33b5f
PP
275 }
276else
277 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
278
66645890
PP
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 */
d7978c0f 288for (int i = 0; i < 2; ++i)
66645890 289 {
6a2c32cb
JH
290 int propnum;
291 const uschar * label;
292 uschar * address_port;
66645890 293 const char *s_err;
66645890
PP
294
295 if (i)
296 {
66645890
PP
297 propnum = SASL_IPREMOTEPORT;
298 label = CUS"peer";
6a2c32cb
JH
299 address_port = string_sprintf("%s;%d",
300 sender_host_address, sender_host_port);
66645890
PP
301 }
302 else
303 {
66645890
PP
304 propnum = SASL_IPLOCALPORT;
305 label = CUS"local";
6a2c32cb 306 address_port = string_sprintf("%s;%d", interface_address, interface_port);
66645890
PP
307 }
308
c0fb53b7 309 if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
66645890 310 {
66645890 311 HDEBUG(D_auth)
6a2c32cb
JH
312 {
313 s_err = sasl_errdetail(conn);
66645890
PP
314 debug_printf("Failed to set %s SASL property: [%d] %s\n",
315 label, rc, s_err ? s_err : "<unknown reason>");
6a2c32cb 316 }
66645890
PP
317 break;
318 }
319 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
320 label, address_port);
321 }
322
c0fb53b7 323for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
0756eb3c 324 {
c0fb53b7 325 if (firsttime)
0756eb3c 326 {
c0fb53b7 327 firsttime = 0;
0756eb3c 328 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
3d2e82c5
JH
329 rc = sasl_server_start(conn, CS ob->server_mech, inlen ? CS input : NULL, inlen,
330 CCSS &output, &outlen);
0756eb3c
PH
331 }
332 else
333 {
5c329a43
JH
334 /* auth_get_data() takes a length-specfied block of binary
335 which can include zeroes; no terminating NUL is needed */
c0fb53b7 336
5c329a43 337 if ((rc = auth_get_data(&input, output, outlen)) != OK)
0756eb3c
PH
338 {
339 /* we couldn't get the data, so free up the library before
6a2c32cb 340 returning whatever error we get */
0756eb3c
PH
341 sasl_dispose(&conn);
342 sasl_done();
343 return rc;
344 }
c0fb53b7 345 inlen = Ustrlen(input);
0756eb3c 346
c0fb53b7
JH
347 HDEBUG(D_auth) debug = string_copy(input);
348 if (inlen)
0756eb3c 349 {
c0fb53b7 350 if ((clen = b64decode(input, &clear)) < 0)
0756eb3c 351 {
c0fb53b7
JH
352 sasl_dispose(&conn);
353 sasl_done();
0756eb3c
PH
354 return BAD64;
355 }
c0fb53b7
JH
356 input = clear;
357 inlen = clen;
0756eb3c
PH
358 }
359
360 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
3d2e82c5 361 rc = sasl_server_step(conn, CS input, inlen, CCSS &output, &outlen);
0756eb3c 362 }
c0fb53b7
JH
363
364 if (rc == SASL_BADPROT)
0756eb3c
PH
365 {
366 sasl_dispose(&conn);
367 sasl_done();
368 return UNEXPECTED;
369 }
c0fb53b7
JH
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
5c329a43 376 if ((sasl_getprop(conn, SASL_USERNAME, (const void **)&out2)) != SASL_OK)
0756eb3c 377 {
0756eb3c 378 HDEBUG(D_auth)
c0fb53b7
JH
379 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
380 sasl_errstring(rc, NULL, NULL));
0756eb3c 381 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
c0fb53b7 382 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
0756eb3c
PH
383 sasl_errstring(rc, NULL, NULL));
384 sasl_dispose(&conn);
385 sasl_done();
386 return FAIL;
387 }
c0fb53b7
JH
388 auth_vars[0] = expand_nstring[1] = string_copy(out2);
389 expand_nlength[1] = Ustrlen(out2);
390 expand_nmax = 1;
391
392 switch (rc)
0756eb3c 393 {
c0fb53b7
JH
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 */
edc33b5f 398 HDEBUG(D_auth)
c0fb53b7 399 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
edc33b5f 400 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
c0fb53b7
JH
401 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
402 sasl_errstring(rc, NULL, NULL));
edc33b5f
PP
403 sasl_dispose(&conn);
404 sasl_done();
405 return FAIL;
0756eb3c 406
c0fb53b7
JH
407 case SASL_NOMECH:
408 /* this is a temporary failure, because the mechanism is not
6a2c32cb
JH
409 available for this user. If it wasn't available at all, we
410 shouldn't have got here in the first place... */
411
edc33b5f 412 HDEBUG(D_auth)
c0fb53b7
JH
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);
edc33b5f
PP
416 sasl_dispose(&conn);
417 sasl_done();
c0fb53b7
JH
418 return DEFER;
419
420 case SASL_OK:
edc33b5f 421 HDEBUG(D_auth)
c0fb53b7
JH
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 */
edc33b5f
PP
452 sasl_dispose(&conn);
453 sasl_done();
edc33b5f 454
c0fb53b7
JH
455 /* Expand server_condition as an authorization check */
456 return auth_check_serv_cond(ablock);
16ff981e 457
c0fb53b7
JH
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;
0756eb3c
PH
469 }
470 }
471/* NOTREACHED */
472return 0; /* Stop compiler complaints */
473}
474
475/*************************************************
6545de78
PP
476* Diagnostic API *
477*************************************************/
478
479void
480auth_cyrus_sasl_version_report(FILE *f)
481{
c0fb53b7
JH
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);
6545de78
PP
488}
489
490/*************************************************
0756eb3c
PH
491* Client entry point *
492*************************************************/
493
494/* For interface, see auths/README */
495
496int
497auth_cyrus_sasl_client(
498 auth_instance *ablock, /* authenticator block */
251b9eb4 499 void * sx, /* connexction */
0756eb3c 500 int timeout, /* command timeout */
d7978c0f 501 uschar *buffer, /* for reading response */
0756eb3c
PH
502 int buffsize) /* size of buffer */
503{
504/* We don't support clients (yet) in this implementation of cyrus_sasl */
505return FAIL;
506}
507
d185889f 508#endif /*!MACRO_PREDEF*/
0756eb3c
PH
509#endif /* AUTH_CYRUS_SASL */
510
511/* End of cyrus_sasl.c */