1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2023 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
10 /* This code was originally contributed by Matthew Byng-Maddick */
12 /* Copyright (c) A L Digital 2004 */
14 /* A generic (mechanism independent) Cyrus SASL authenticator. */
20 /* We can't just compile this code and allow the library mechanism to omit the
21 functions if they are not wanted, because we need to have the Cyrus SASL header
22 available for compiling. Therefore, compile these functions only if
23 AUTH_CYRUS_SASL is defined. However, some compilers don't like compiling empty
24 modules, so keep them happy with a dummy when skipping the rest. Make it
25 reference itself to stop picky compilers complaining that it is unused, and put
26 in a dummy argument to stop even pickier compilers complaining about infinite
29 #ifndef AUTH_CYRUS_SASL
30 static void dummy(int x);
31 static void dummy2(int x) { dummy(x-1); }
32 static void dummy(int x) { dummy2(x-1); }
36 #include <sasl/sasl.h>
37 #include "cyrus_sasl.h"
39 /* Options specific to the cyrus_sasl authentication mechanism. */
41 optionlist auth_cyrus_sasl_options[] = {
42 { "server_hostname", opt_stringptr,
43 OPT_OFF(auth_cyrus_sasl_options_block, server_hostname) },
44 { "server_mech", opt_stringptr,
45 OPT_OFF(auth_cyrus_sasl_options_block, server_mech) },
46 { "server_realm", opt_stringptr,
47 OPT_OFF(auth_cyrus_sasl_options_block, server_realm) },
48 { "server_service", opt_stringptr,
49 OPT_OFF(auth_cyrus_sasl_options_block, server_service) }
52 /* Size of the options list. An extern variable has to be used so that its
53 address can appear in the tables drtables.c. */
55 int auth_cyrus_sasl_options_count =
56 sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
58 /* Default private options block for the cyrus_sasl authentication method. */
60 auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
61 US"smtp", /* server_service */
62 US"$primary_hostname", /* server_hostname */
63 NULL, /* server_realm */
64 NULL /* server_mech */
71 void auth_cyrus_sasl_init(auth_instance *ablock) {}
72 int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;}
73 int auth_cyrus_sasl_client(auth_instance *ablock, void * sx,
74 int timeout, uschar *buffer, int buffsize) {return 0;}
75 gstring * auth_cyrus_sasl_version_report(gstring * g) {return NULL;}
77 #else /*!MACRO_PREDEF*/
82 /*************************************************
83 * Initialization entry point *
84 *************************************************/
86 /* Called for each instance, after its options have been read, to
87 enable consistency checks to be done, or anything else that needs
91 /* Auxiliary function, passed in data to sasl_server_init(). */
94 mysasl_config(void *context, const char *plugin_name, const char *option,
95 const char **result, unsigned int *len)
97 if (context && !strcmp(option, "mech_list"))
100 if (len) *len = strlen(*result);
106 /* Here's the real function */
109 auth_cyrus_sasl_init(auth_instance *ablock)
111 auth_cyrus_sasl_options_block *ob =
112 (auth_cyrus_sasl_options_block *)(ablock->options_block);
113 const uschar *list, *listptr, *buffer;
117 uschar *expanded_hostname;
118 char *realm_expanded;
121 sasl_callback_t cbs[] = {
122 {SASL_CB_GETOPT, NULL, NULL },
123 {SASL_CB_LIST_END, NULL, NULL}};
125 /* default the mechanism to our "public name" */
127 if (!ob->server_mech) ob->server_mech = string_copy(ablock->public_name);
129 if (!(expanded_hostname = expand_string(ob->server_hostname)))
130 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
131 "couldn't expand server_hostname [%s]: %s",
132 ablock->name, ob->server_hostname, expand_string_message);
134 realm_expanded = NULL;
135 if ( ob->server_realm
136 && !(realm_expanded = CS expand_string(ob->server_realm)))
137 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
138 "couldn't expand server_realm [%s]: %s",
139 ablock->name, ob->server_realm, expand_string_message);
141 /* we're going to initialise the library to check that there is an
142 authenticator of type whatever mechanism we're using */
144 cbs[0].proc = (int(*)(void)) &mysasl_config;
145 cbs[0].context = ob->server_mech;
147 if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
148 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
149 "couldn't initialise Cyrus SASL library.", ablock->name);
151 if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname,
152 realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK)
153 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
154 "couldn't initialise Cyrus SASL server connection.", ablock->name);
156 if ((rc = sasl_listmech(conn, NULL, "", ":", "", CCSS &list, &len, &i)) != SASL_OK)
157 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
158 "couldn't get Cyrus SASL mechanism list.", ablock->name);
165 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
166 ob->server_service, expanded_hostname, realm_expanded);
167 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
170 /* the store_get / store_reset mechanism is hierarchical
171 the hierarchy is stored for us behind our back. This point
172 creates a hierarchy point for this function. */
174 rs_point = store_mark();
176 /* loop until either we get to the end of the list, or we match the
177 public name of this authenticator */
179 while ( (buffer = string_nextinlist(&listptr, &i, NULL, 0))
180 && strcmpic(buffer,ob->server_mech) );
183 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
184 "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
186 store_reset(rs_point);
188 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
190 /* make sure that if we get here then we're allowed to advertise. */
191 ablock->server = TRUE;
197 /*************************************************
198 * Server entry point *
199 *************************************************/
201 /* For interface, see auths/README */
203 /* note, we don't care too much about memory allocation in this, because this is entirely
204 within a shortlived child */
207 auth_cyrus_sasl_server(auth_instance * ablock, uschar * data)
209 auth_cyrus_sasl_options_block * ob =
210 (auth_cyrus_sasl_options_block *)(ablock->options_block);
211 uschar * output, * out2, * input, * clear, * hname;
212 uschar * debug = NULL; /* Stops compiler complaining */
213 sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
215 char * realm_expanded = NULL;
216 int rc, firsttime = 1, clen, * negotiated_ssf_ptr = NULL, negotiated_ssf;
217 unsigned int inlen, outlen;
220 inlen = Ustrlen(data);
222 HDEBUG(D_auth) debug = string_copy(data);
224 hname = expand_string(ob->server_hostname);
225 if (hname && ob->server_realm)
226 realm_expanded = CS expand_string(ob->server_realm);
227 if (!hname || !realm_expanded && ob->server_realm)
229 auth_defer_msg = expand_string_message;
235 if ((clen = b64decode(input, &clear, input)) < 0)
241 if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
243 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
247 rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
248 NULL, NULL, 0, &conn);
251 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
252 ob->server_service, hname, realm_expanded);
256 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
263 if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
265 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
266 tls_in.bits, sasl_errstring(rc, NULL, NULL));
267 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
272 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
274 /*XXX Set channel-binding here with sasl_channel_binding_t / SASL_CHANNEL_BINDING
275 Unclear what the "name" element does though, ditto the "critical" flag. */
278 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
280 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
281 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
282 with their iptostring() function, which just wraps
283 getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
284 inet_ntop which we wrap in our host_ntoa() function.
286 So the docs are too strict and we shouldn't worry about :: contractions. */
288 /* Set properties for remote and local host-ip;port */
289 for (int i = 0; i < 2; ++i)
292 const uschar * label;
293 uschar * address_port;
298 propnum = SASL_IPREMOTEPORT;
300 address_port = string_sprintf("%s;%d",
301 sender_host_address, sender_host_port);
305 propnum = SASL_IPLOCALPORT;
307 address_port = string_sprintf("%s;%d", interface_address, interface_port);
310 if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
314 s_err = sasl_errdetail(conn);
315 debug_printf("Failed to set %s SASL property: [%d] %s\n",
316 label, rc, s_err ? s_err : "<unknown reason>");
320 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
321 label, address_port);
324 for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
329 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
330 rc = sasl_server_start(conn, CS ob->server_mech, inlen ? CS input : NULL, inlen,
331 CCSS &output, &outlen);
335 /* auth_get_data() takes a length-specfied block of binary
336 which can include zeroes; no terminating NUL is needed */
338 if ((rc = auth_get_data(&input, output, outlen)) != OK)
340 /* we couldn't get the data, so free up the library before
341 returning whatever error we get */
346 inlen = Ustrlen(input);
348 HDEBUG(D_auth) debug = string_copy_taint(input, GET_TAINTED);
351 if ((clen = b64decode(input, &clear, GET_TAINTED)) < 0)
361 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
362 rc = sasl_server_step(conn, CS input, inlen, CCSS &output, &outlen);
365 if (rc == SASL_BADPROT)
371 if (rc == SASL_CONTINUE)
374 /* Get the username and copy it into $auth1 and $1. The former is now the
375 preferred variable; the latter is the original variable. */
377 if ((sasl_getprop(conn, SASL_USERNAME, (const void **)&out2)) != SASL_OK)
380 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
381 sasl_errstring(rc, NULL, NULL));
382 log_write(0, LOG_REJECT, "%s authenticator (%s): "
383 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
384 sasl_errstring(rc, NULL, NULL));
389 auth_vars[0] = expand_nstring[1] = string_copy(out2);
390 expand_nlength[1] = Ustrlen(out2);
395 case SASL_FAIL: case SASL_BUFOVER: case SASL_BADMAC: case SASL_BADAUTH:
396 case SASL_NOAUTHZ: case SASL_ENCRYPT: case SASL_EXPIRED:
397 case SASL_DISABLED: case SASL_NOUSER:
398 /* these are considered permanent failure codes */
400 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
401 log_write(0, LOG_REJECT, "%s authenticator (%s): "
402 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
403 sasl_errstring(rc, NULL, NULL));
409 /* this is a temporary failure, because the mechanism is not
410 available for this user. If it wasn't available at all, we
411 shouldn't have got here in the first place... */
414 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
416 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
423 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
424 ob->server_mech, auth_vars[0]);
426 if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK)
429 debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
430 sasl_errstring(rc, NULL, NULL));
431 log_write(0, LOG_REJECT, "%s authenticator (%s): "
432 "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
433 sasl_errstring(rc, NULL, NULL));
438 negotiated_ssf = *negotiated_ssf_ptr;
440 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
441 if (negotiated_ssf > 0)
444 debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
445 log_write(0, LOG_REJECT, "%s authenticator (%s): "
446 "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
452 /* close down the connection, freeing up library's memory */
456 /* Expand server_condition as an authorization check */
457 return auth_check_serv_cond(ablock);
460 /* Anything else is a temporary failure, and we'll let SASL print out
461 * the error string for us
464 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
466 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
473 return 0; /* Stop compiler complaints */
476 /*************************************************
478 *************************************************/
481 auth_cyrus_sasl_version_report(gstring * g)
483 const char * implementation, * version;
484 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
485 g = string_fmt_append(g,
486 "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
487 " Runtime: %s [%s]\n",
488 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
489 version, implementation);
493 /*************************************************
494 * Client entry point *
495 *************************************************/
497 /* For interface, see auths/README */
500 auth_cyrus_sasl_client(
501 auth_instance *ablock, /* authenticator block */
502 void * sx, /* connexction */
503 int timeout, /* command timeout */
504 uschar *buffer, /* for reading response */
505 int buffsize) /* size of buffer */
507 /* We don't support clients (yet) in this implementation of cyrus_sasl */
511 #endif /*!MACRO_PREDEF*/
512 #endif /* AUTH_CYRUS_SASL */
514 /* End of cyrus_sasl.c */