1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
8 /* This code was originally contributed by Matthew Byng-Maddick */
10 /* Copyright (c) A L Digital 2004 */
12 /* A generic (mechanism independent) Cyrus SASL authenticator. */
18 /* We can't just compile this code and allow the library mechanism to omit the
19 functions if they are not wanted, because we need to have the Cyrus SASL header
20 available for compiling. Therefore, compile these functions only if
21 AUTH_CYRUS_SASL is defined. However, some compilers don't like compiling empty
22 modules, so keep them happy with a dummy when skipping the rest. Make it
23 reference itself to stop picky compilers complaining that it is unused, and put
24 in a dummy argument to stop even pickier compilers complaining about infinite
27 #ifndef AUTH_CYRUS_SASL
28 static void dummy(int x);
29 static void dummy2(int x) { dummy(x-1); }
30 static void dummy(int x) { dummy2(x-1); }
34 #include <sasl/sasl.h>
35 #include "cyrus_sasl.h"
37 /* Options specific to the cyrus_sasl authentication mechanism. */
39 optionlist 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)) }
50 /* Size of the options list. An extern variable has to be used so that its
51 address can appear in the tables drtables.c. */
53 int auth_cyrus_sasl_options_count =
54 sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
56 /* Default private options block for the cyrus_sasl authentication method. */
58 auth_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 */
69 void auth_cyrus_sasl_init(auth_instance *ablock) {}
70 int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;}
71 int auth_cyrus_sasl_client(auth_instance *ablock, void * sx,
72 int timeout, uschar *buffer, int buffsize) {return 0;}
73 void auth_cyrus_sasl_version_report(FILE *f) {}
75 #else /*!MACRO_PREDEF*/
80 /*************************************************
81 * Initialization entry point *
82 *************************************************/
84 /* Called for each instance, after its options have been read, to
85 enable consistency checks to be done, or anything else that needs
89 /* Auxiliary function, passed in data to sasl_server_init(). */
92 mysasl_config(void *context,
93 const char *plugin_name,
98 if (context && !strcmp(option, "mech_list"))
101 if (len != NULL) *len = strlen(*result);
107 /* Here's the real function */
110 auth_cyrus_sasl_init(auth_instance *ablock)
112 auth_cyrus_sasl_options_block *ob =
113 (auth_cyrus_sasl_options_block *)(ablock->options_block);
114 const uschar *list, *listptr, *buffer;
117 uschar *rs_point, *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" */
126 if (ob->server_mech == NULL)
127 ob->server_mech = string_copy(ablock->public_name);
129 expanded_hostname = expand_string(ob->server_hostname);
130 if (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);
135 realm_expanded = NULL;
136 if (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);
144 /* we're going to initialise the library to check that there is an
145 * authenticator of type whatever mechanism we're using
148 cbs[0].proc = (int(*)(void)) &mysasl_config;
149 cbs[0].context = ob->server_mech;
151 if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK )
152 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
153 "couldn't initialise Cyrus SASL library.", ablock->name);
155 if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname,
156 realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK )
157 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
158 "couldn't initialise Cyrus SASL server connection.", ablock->name);
160 if ((rc = sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i)) != SASL_OK )
161 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
162 "couldn't get Cyrus SASL mechanism list.", ablock->name);
169 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
170 ob->server_service, expanded_hostname, realm_expanded);
171 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
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.
178 rs_point = store_get(0);
180 /* loop until either we get to the end of the list, or we match the
181 * public name of this authenticator
183 while ( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
184 strcmpic(buffer,ob->server_mech) );
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);
190 store_reset(rs_point);
192 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
194 /* make sure that if we get here then we're allowed to advertise. */
195 ablock->server = TRUE;
201 /*************************************************
202 * Server entry point *
203 *************************************************/
205 /* For interface, see auths/README */
207 /* note, we don't care too much about memory allocation in this, because this is entirely
208 * within a shortlived child
212 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
214 auth_cyrus_sasl_options_block *ob =
215 (auth_cyrus_sasl_options_block *)(ablock->options_block);
216 uschar *output, *out2, *input, *clear, *hname;
217 uschar *debug = NULL; /* Stops compiler complaining */
218 sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
220 char * realm_expanded = NULL;
221 int rc, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf;
222 unsigned int inlen, outlen;
225 inlen = Ustrlen(data);
227 HDEBUG(D_auth) debug = string_copy(data);
229 hname = expand_string(ob->server_hostname);
230 if (hname && ob->server_realm)
231 realm_expanded = CS expand_string(ob->server_realm);
232 if (!hname || !realm_expanded && ob->server_realm)
234 auth_defer_msg = expand_string_message;
240 if ((clen = b64decode(input, &clear)) < 0)
246 if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
248 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
252 rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
253 NULL, NULL, 0, &conn);
256 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
257 ob->server_service, hname, realm_expanded);
261 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
268 if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
270 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
271 tls_in.bits, sasl_errstring(rc, NULL, NULL));
272 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
277 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
280 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
282 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
283 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
284 with their iptostring() function, which just wraps
285 getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
286 inet_ntop which we wrap in our host_ntoa() function.
288 So the docs are too strict and we shouldn't worry about :: contractions. */
290 /* Set properties for remote and local host-ip;port */
291 for (int i = 0; i < 2; ++i)
293 struct sockaddr_storage ss;
294 int (*query)(int, struct sockaddr *, socklen_t *);
297 uschar *address, *address_port;
303 query = &getpeername;
304 propnum = SASL_IPREMOTEPORT;
309 query = &getsockname;
310 propnum = SASL_IPLOCALPORT;
315 if ((rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen)) < 0)
318 debug_printf("Failed to get %s address information: %s\n",
319 label, strerror(errno));
323 address = host_ntoa(-1, &ss, NULL, &port);
324 address_port = string_sprintf("%s;%d", address, port);
326 if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
328 s_err = sasl_errdetail(conn);
330 debug_printf("Failed to set %s SASL property: [%d] %s\n",
331 label, rc, s_err ? s_err : "<unknown reason>");
334 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
335 label, address_port);
338 for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
343 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
344 rc = sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
345 (const char **)(&output), &outlen);
349 /* make sure that we have a null-terminated string */
350 out2 = string_copyn(output, outlen);
352 if ((rc = auth_get_data(&input, out2, outlen)) != OK)
354 /* we couldn't get the data, so free up the library before
355 * returning whatever error we get */
360 inlen = Ustrlen(input);
362 HDEBUG(D_auth) debug = string_copy(input);
365 if ((clen = b64decode(input, &clear)) < 0)
375 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
376 rc = sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
379 if (rc == SASL_BADPROT)
385 if (rc == SASL_CONTINUE)
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. */
391 if ((sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2))) != SASL_OK)
394 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
395 sasl_errstring(rc, NULL, NULL));
396 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
397 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
398 sasl_errstring(rc, NULL, NULL));
403 auth_vars[0] = expand_nstring[1] = string_copy(out2);
404 expand_nlength[1] = Ustrlen(out2);
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 */
414 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
415 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
416 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
417 sasl_errstring(rc, NULL, NULL));
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...
428 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
430 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
437 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
438 ob->server_mech, auth_vars[0]);
440 if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK)
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));
452 negotiated_ssf = *negotiated_ssf_ptr;
454 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
455 if (negotiated_ssf > 0)
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);
466 /* close down the connection, freeing up library's memory */
470 /* Expand server_condition as an authorization check */
471 return auth_check_serv_cond(ablock);
474 /* Anything else is a temporary failure, and we'll let SASL print out
475 * the error string for us
478 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
480 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
487 return 0; /* Stop compiler complaints */
490 /*************************************************
492 *************************************************/
495 auth_cyrus_sasl_version_report(FILE *f)
497 const char *implementation, *version;
498 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
499 fprintf(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);
505 /*************************************************
506 * Client entry point *
507 *************************************************/
509 /* For interface, see auths/README */
512 auth_cyrus_sasl_client(
513 auth_instance *ablock, /* authenticator block */
514 void * sx, /* connexction */
515 int timeout, /* command timeout */
516 uschar *buffer, /* for reading response */
517 int buffsize) /* size of buffer */
519 /* We don't support clients (yet) in this implementation of cyrus_sasl */
523 #endif /*!MACRO_PREDEF*/
524 #endif /* AUTH_CYRUS_SASL */
526 /* End of cyrus_sasl.c */