1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
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. */
9 /* This code was originally contributed by Matthew Byng-Maddick */
11 /* Copyright (c) A L Digital 2004 */
13 /* A generic (mechanism independent) Cyrus SASL authenticator. */
19 /* We can't just compile this code and allow the library mechanism to omit the
20 functions if they are not wanted, because we need to have the Cyrus SASL header
21 available for compiling. Therefore, compile these functions only if
22 AUTH_CYRUS_SASL is defined. However, some compilers don't like compiling empty
23 modules, so keep them happy with a dummy when skipping the rest. Make it
24 reference itself to stop picky compilers complaining that it is unused, and put
25 in a dummy argument to stop even pickier compilers complaining about infinite
28 #ifndef AUTH_CYRUS_SASL
29 static void dummy(int x);
30 static void dummy2(int x) { dummy(x-1); }
31 static void dummy(int x) { dummy2(x-1); }
35 #include <sasl/sasl.h>
36 #include "cyrus_sasl.h"
38 /* Options specific to the cyrus_sasl authentication mechanism. */
40 optionlist 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) }
51 /* Size of the options list. An extern variable has to be used so that its
52 address can appear in the tables drtables.c. */
54 int auth_cyrus_sasl_options_count =
55 sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
57 /* Default private options block for the cyrus_sasl authentication method. */
59 auth_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 */
70 void auth_cyrus_sasl_init(auth_instance *ablock) {}
71 int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;}
72 int auth_cyrus_sasl_client(auth_instance *ablock, void * sx,
73 int timeout, uschar *buffer, int buffsize) {return 0;}
74 void auth_cyrus_sasl_version_report(FILE *f) {}
76 #else /*!MACRO_PREDEF*/
81 /*************************************************
82 * Initialization entry point *
83 *************************************************/
85 /* Called for each instance, after its options have been read, to
86 enable consistency checks to be done, or anything else that needs
90 /* Auxiliary function, passed in data to sasl_server_init(). */
93 mysasl_config(void *context, const char *plugin_name, const char *option,
94 const char **result, unsigned int *len)
96 if (context && !strcmp(option, "mech_list"))
99 if (len) *len = strlen(*result);
105 /* Here's the real function */
108 auth_cyrus_sasl_init(auth_instance *ablock)
110 auth_cyrus_sasl_options_block *ob =
111 (auth_cyrus_sasl_options_block *)(ablock->options_block);
112 const uschar *list, *listptr, *buffer;
116 uschar *expanded_hostname;
117 char *realm_expanded;
120 sasl_callback_t cbs[] = {
121 {SASL_CB_GETOPT, NULL, NULL },
122 {SASL_CB_LIST_END, NULL, NULL}};
124 /* default the mechanism to our "public name" */
126 if (!ob->server_mech) ob->server_mech = string_copy(ablock->public_name);
128 if (!(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);
133 realm_expanded = NULL;
134 if ( 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);
140 /* we're going to initialise the library to check that there is an
141 authenticator of type whatever mechanism we're using */
143 cbs[0].proc = (int(*)(void)) &mysasl_config;
144 cbs[0].context = ob->server_mech;
146 if ((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);
150 if ((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);
155 if ((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);
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);
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. */
173 rs_point = store_mark();
175 /* loop until either we get to the end of the list, or we match the
176 public name of this authenticator */
178 while ( (buffer = string_nextinlist(&listptr, &i, NULL, 0))
179 && strcmpic(buffer,ob->server_mech) );
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);
185 store_reset(rs_point);
187 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
189 /* make sure that if we get here then we're allowed to advertise. */
190 ablock->server = TRUE;
196 /*************************************************
197 * Server entry point *
198 *************************************************/
200 /* For interface, see auths/README */
202 /* note, we don't care too much about memory allocation in this, because this is entirely
203 within a shortlived child */
206 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
208 auth_cyrus_sasl_options_block *ob =
209 (auth_cyrus_sasl_options_block *)(ablock->options_block);
210 uschar *output, *out2, *input, *clear, *hname;
211 uschar *debug = NULL; /* Stops compiler complaining */
212 sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
214 char * realm_expanded = NULL;
215 int rc, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf;
216 unsigned int inlen, outlen;
219 inlen = Ustrlen(data);
221 HDEBUG(D_auth) debug = string_copy(data);
223 hname = expand_string(ob->server_hostname);
224 if (hname && ob->server_realm)
225 realm_expanded = CS expand_string(ob->server_realm);
226 if (!hname || !realm_expanded && ob->server_realm)
228 auth_defer_msg = expand_string_message;
234 if ((clen = b64decode(input, &clear)) < 0)
240 if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
242 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
246 rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
247 NULL, NULL, 0, &conn);
250 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
251 ob->server_service, hname, realm_expanded);
255 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
262 if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
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";
271 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
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. */
277 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
279 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
280 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
281 with their iptostring() function, which just wraps
282 getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
283 inet_ntop which we wrap in our host_ntoa() function.
285 So the docs are too strict and we shouldn't worry about :: contractions. */
287 /* Set properties for remote and local host-ip;port */
288 for (int i = 0; i < 2; ++i)
291 const uschar * label;
292 uschar * address_port;
297 propnum = SASL_IPREMOTEPORT;
299 address_port = string_sprintf("%s;%d",
300 sender_host_address, sender_host_port);
304 propnum = SASL_IPLOCALPORT;
306 address_port = string_sprintf("%s;%d", interface_address, interface_port);
309 if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
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>");
319 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
320 label, address_port);
323 for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
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);
334 /* auth_get_data() takes a length-specfied block of binary
335 which can include zeroes; no terminating NUL is needed */
337 if ((rc = auth_get_data(&input, output, outlen)) != OK)
339 /* we couldn't get the data, so free up the library before
340 returning whatever error we get */
345 inlen = Ustrlen(input);
347 HDEBUG(D_auth) debug = string_copy(input);
350 if ((clen = b64decode(input, &clear)) < 0)
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);
364 if (rc == SASL_BADPROT)
370 if (rc == SASL_CONTINUE)
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. */
376 if ((sasl_getprop(conn, SASL_USERNAME, (const void **)&out2)) != SASL_OK)
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));
388 auth_vars[0] = expand_nstring[1] = string_copy(out2);
389 expand_nlength[1] = Ustrlen(out2);
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 */
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));
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... */
413 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
415 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
422 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
423 ob->server_mech, auth_vars[0]);
425 if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK)
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));
437 negotiated_ssf = *negotiated_ssf_ptr;
439 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
440 if (negotiated_ssf > 0)
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);
451 /* close down the connection, freeing up library's memory */
455 /* Expand server_condition as an authorization check */
456 return auth_check_serv_cond(ablock);
459 /* Anything else is a temporary failure, and we'll let SASL print out
460 * the error string for us
463 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
465 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
472 return 0; /* Stop compiler complaints */
475 /*************************************************
477 *************************************************/
480 auth_cyrus_sasl_version_report(FILE *f)
482 const char *implementation, *version;
483 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
484 fprintf(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);
490 /*************************************************
491 * Client entry point *
492 *************************************************/
494 /* For interface, see auths/README */
497 auth_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 */
504 /* We don't support clients (yet) in this implementation of cyrus_sasl */
508 #endif /*!MACRO_PREDEF*/
509 #endif /* AUTH_CYRUS_SASL */
511 /* End of cyrus_sasl.c */