1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2017 */
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, smtp_inblock *inblock,
72 smtp_outblock *outblock, 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);
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 rc=sasl_server_init(cbs, "exim");
154 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
155 "couldn't initialise Cyrus SASL library.", ablock->name);
157 rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
158 realm_expanded, NULL, NULL, NULL, 0, &conn);
160 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
161 "couldn't initialise Cyrus SASL server connection.", ablock->name);
163 rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
165 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
166 "couldn't get Cyrus SASL mechanism list.", ablock->name);
172 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
173 ob->server_service, expanded_hostname, realm_expanded);
174 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
177 /* the store_get / store_reset mechanism is hierarchical
178 * the hierarchy is stored for us behind our back. This point
179 * creates a hierarchy point for this function.
181 rs_point=store_get(0);
183 /* loop until either we get to the end of the list, or we match the
184 * public name of this authenticator
186 while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
187 strcmpic(buffer,ob->server_mech) );
190 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
191 "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
193 store_reset(rs_point);
195 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
197 /* make sure that if we get here then we're allowed to advertise. */
198 ablock->server = TRUE;
204 /*************************************************
205 * Server entry point *
206 *************************************************/
208 /* For interface, see auths/README */
210 /* note, we don't care too much about memory allocation in this, because this is entirely
211 * within a shortlived child
215 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
217 auth_cyrus_sasl_options_block *ob =
218 (auth_cyrus_sasl_options_block *)(ablock->options_block);
219 uschar *output, *out2, *input, *clear, *hname;
220 uschar *debug = NULL; /* Stops compiler complaining */
221 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
223 char *realm_expanded;
224 int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
225 unsigned int inlen, outlen;
230 HDEBUG(D_auth) debug=string_copy(data);
232 hname=expand_string(ob->server_hostname);
234 if (hname && ob->server_realm)
235 realm_expanded= CS expand_string(ob->server_realm);
236 if((hname == NULL) ||
237 ((realm_expanded == NULL) && (ob->server_realm != NULL)))
239 auth_defer_msg = expand_string_message;
245 clen = b64decode(input, &clear);
254 rc=sasl_server_init(cbs, "exim");
257 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
261 rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
262 NULL, NULL, 0, &conn);
265 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
266 ob->server_service, hname, realm_expanded);
270 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
277 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
280 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
281 tls_in.bits, sasl_errstring(rc, NULL, NULL));
282 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
287 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
290 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
292 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
293 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
294 with their iptostring() function, which just wraps
295 getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
296 inet_ntop which we wrap in our host_ntoa() function.
298 So the docs are too strict and we shouldn't worry about :: contractions. */
300 /* Set properties for remote and local host-ip;port */
301 for (i=0; i < 2; ++i)
303 struct sockaddr_storage ss;
304 int (*query)(int, struct sockaddr *, socklen_t *);
307 uschar *address, *address_port;
313 query = &getpeername;
314 propnum = SASL_IPREMOTEPORT;
319 query = &getsockname;
320 propnum = SASL_IPLOCALPORT;
325 rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
329 debug_printf("Failed to get %s address information: %s\n",
330 label, strerror(errno));
334 address = host_ntoa(-1, &ss, NULL, &port);
335 address_port = string_sprintf("%s;%d", address, port);
337 rc = sasl_setprop(conn, propnum, address_port);
340 s_err = sasl_errdetail(conn);
342 debug_printf("Failed to set %s SASL property: [%d] %s\n",
343 label, rc, s_err ? s_err : "<unknown reason>");
346 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
347 label, address_port);
352 while(rc==SASL_CONTINUE)
357 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
358 rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
359 (const char **)(&output), &outlen);
363 /* make sure that we have a null-terminated string */
364 out2=store_get(outlen+1);
365 memcpy(out2,output,outlen);
367 if((rc=auth_get_data(&input, out2, outlen))!=OK)
369 /* we couldn't get the data, so free up the library before
370 * returning whatever error we get */
375 inlen=Ustrlen(input);
377 HDEBUG(D_auth) debug=string_copy(input);
380 clen = b64decode(input, &clear);
391 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
392 rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
400 else if( rc==SASL_FAIL || rc==SASL_BUFOVER
401 || rc==SASL_BADMAC || rc==SASL_BADAUTH
402 || rc==SASL_NOAUTHZ || rc==SASL_ENCRYPT
403 || rc==SASL_EXPIRED || rc==SASL_DISABLED
406 /* these are considered permanent failure codes */
408 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
409 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
410 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
411 sasl_errstring(rc, NULL, NULL));
416 else if(rc==SASL_NOMECH)
418 /* this is a temporary failure, because the mechanism is not
419 * available for this user. If it wasn't available at all, we
420 * shouldn't have got here in the first place...
423 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
425 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
430 else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
432 /* Anything else is a temporary failure, and we'll let SASL print out
433 * the error string for us
436 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
438 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
445 /* Get the username and copy it into $auth1 and $1. The former is now the
446 preferred variable; the latter is the original variable. */
447 rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
451 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
452 sasl_errstring(rc, NULL, NULL));
453 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
454 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
455 sasl_errstring(rc, NULL, NULL));
461 auth_vars[0] = expand_nstring[1] = string_copy(out2);
462 expand_nlength[1] = Ustrlen(expand_nstring[1]);
466 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
467 ob->server_mech, auth_vars[0]);
469 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
473 debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
474 sasl_errstring(rc, NULL, NULL));
475 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
476 "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
477 sasl_errstring(rc, NULL, NULL));
482 negotiated_ssf = *negotiated_ssf_ptr;
484 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
485 if (negotiated_ssf > 0)
488 debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
489 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
490 "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
496 /* close down the connection, freeing up library's memory */
500 /* Expand server_condition as an authorization check */
501 return auth_check_serv_cond(ablock);
505 return 0; /* Stop compiler complaints */
508 /*************************************************
510 *************************************************/
513 auth_cyrus_sasl_version_report(FILE *f)
515 const char *implementation, *version;
516 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
517 fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
518 " Runtime: %s [%s]\n",
519 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
520 version, implementation);
523 /*************************************************
524 * Client entry point *
525 *************************************************/
527 /* For interface, see auths/README */
530 auth_cyrus_sasl_client(
531 auth_instance *ablock, /* authenticator block */
532 smtp_inblock *inblock, /* input connection */
533 smtp_outblock *outblock, /* output connection */
534 int timeout, /* command timeout */
535 uschar *buffer, /* for reading response */
536 int buffsize) /* size of buffer */
538 /* We don't support clients (yet) in this implementation of cyrus_sasl */
542 #endif /*!MACRO_PREDEF*/
543 #endif /* AUTH_CYRUS_SASL */
545 /* End of cyrus_sasl.c */