1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2015 */
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 */
66 /*************************************************
67 * Initialization entry point *
68 *************************************************/
70 /* Called for each instance, after its options have been read, to
71 enable consistency checks to be done, or anything else that needs
75 /* Auxiliary function, passed in data to sasl_server_init(). */
78 mysasl_config(void *context,
79 const char *plugin_name,
84 if (context && !strcmp(option, "mech_list"))
87 if (len != NULL) *len = strlen(*result);
93 /* Here's the real function */
96 auth_cyrus_sasl_init(auth_instance *ablock)
98 auth_cyrus_sasl_options_block *ob =
99 (auth_cyrus_sasl_options_block *)(ablock->options_block);
100 const uschar *list, *listptr, *buffer;
103 uschar *rs_point, *expanded_hostname;
104 char *realm_expanded;
107 sasl_callback_t cbs[]={
108 {SASL_CB_GETOPT, NULL, NULL },
109 {SASL_CB_LIST_END, NULL, NULL}};
111 /* default the mechanism to our "public name" */
112 if(ob->server_mech == NULL)
113 ob->server_mech=string_copy(ablock->public_name);
115 expanded_hostname = expand_string(ob->server_hostname);
116 if (expanded_hostname == NULL)
117 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
118 "couldn't expand server_hostname [%s]: %s",
119 ablock->name, ob->server_hostname, expand_string_message);
122 if (ob->server_realm != NULL) {
123 realm_expanded = CS expand_string(ob->server_realm);
124 if (realm_expanded == NULL)
125 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
126 "couldn't expand server_realm [%s]: %s",
127 ablock->name, ob->server_realm, expand_string_message);
130 /* we're going to initialise the library to check that there is an
131 * authenticator of type whatever mechanism we're using
134 cbs[0].proc = (int(*)(void)) &mysasl_config;
135 cbs[0].context = ob->server_mech;
137 rc=sasl_server_init(cbs, "exim");
140 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
141 "couldn't initialise Cyrus SASL library.", ablock->name);
143 rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
144 realm_expanded, NULL, NULL, NULL, 0, &conn);
146 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
147 "couldn't initialise Cyrus SASL server connection.", ablock->name);
149 rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
151 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
152 "couldn't get Cyrus SASL mechanism list.", ablock->name);
158 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
159 ob->server_service, expanded_hostname, realm_expanded);
160 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
163 /* the store_get / store_reset mechanism is hierarchical
164 * the hierarchy is stored for us behind our back. This point
165 * creates a hierarchy point for this function.
167 rs_point=store_get(0);
169 /* loop until either we get to the end of the list, or we match the
170 * public name of this authenticator
172 while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
173 strcmpic(buffer,ob->server_mech) );
176 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
177 "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
179 store_reset(rs_point);
181 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
183 /* make sure that if we get here then we're allowed to advertise. */
184 ablock->server = TRUE;
190 /*************************************************
191 * Server entry point *
192 *************************************************/
194 /* For interface, see auths/README */
196 /* note, we don't care too much about memory allocation in this, because this is entirely
197 * within a shortlived child
201 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
203 auth_cyrus_sasl_options_block *ob =
204 (auth_cyrus_sasl_options_block *)(ablock->options_block);
205 uschar *output, *out2, *input, *clear, *hname;
206 uschar *debug = NULL; /* Stops compiler complaining */
207 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
209 char *realm_expanded;
210 int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
211 unsigned int inlen, outlen;
216 HDEBUG(D_auth) debug=string_copy(data);
218 hname=expand_string(ob->server_hostname);
220 if (hname && ob->server_realm)
221 realm_expanded= CS expand_string(ob->server_realm);
222 if((hname == NULL) ||
223 ((realm_expanded == NULL) && (ob->server_realm != NULL)))
225 auth_defer_msg = expand_string_message;
231 clen = b64decode(input, &clear);
240 rc=sasl_server_init(cbs, "exim");
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 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
266 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
267 tls_in.bits, sasl_errstring(rc, NULL, NULL));
268 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
273 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
276 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
278 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
279 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
280 with their iptostring() function, which just wraps
281 getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
282 inet_ntop which we wrap in our host_ntoa() function.
284 So the docs are too strict and we shouldn't worry about :: contractions. */
286 /* Set properties for remote and local host-ip;port */
287 for (i=0; i < 2; ++i)
289 struct sockaddr_storage ss;
290 int (*query)(int, struct sockaddr *, socklen_t *);
293 uschar *address, *address_port;
299 query = &getpeername;
300 propnum = SASL_IPREMOTEPORT;
305 query = &getsockname;
306 propnum = SASL_IPLOCALPORT;
311 rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
315 debug_printf("Failed to get %s address information: %s\n",
316 label, strerror(errno));
320 address = host_ntoa(-1, &ss, NULL, &port);
321 address_port = string_sprintf("%s;%d", address, port);
323 rc = sasl_setprop(conn, propnum, address_port);
326 s_err = sasl_errdetail(conn);
328 debug_printf("Failed to set %s SASL property: [%d] %s\n",
329 label, rc, s_err ? s_err : "<unknown reason>");
332 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
333 label, address_port);
338 while(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=store_get(outlen+1);
351 memcpy(out2,output,outlen);
353 if((rc=auth_get_data(&input, out2, outlen))!=OK)
355 /* we couldn't get the data, so free up the library before
356 * returning whatever error we get */
361 inlen=Ustrlen(input);
363 HDEBUG(D_auth) debug=string_copy(input);
366 clen = b64decode(input, &clear);
377 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
378 rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
386 else if( rc==SASL_FAIL || rc==SASL_BUFOVER
387 || rc==SASL_BADMAC || rc==SASL_BADAUTH
388 || rc==SASL_NOAUTHZ || rc==SASL_ENCRYPT
389 || rc==SASL_EXPIRED || rc==SASL_DISABLED
392 /* these are considered permanent failure codes */
394 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
395 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
396 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
397 sasl_errstring(rc, NULL, NULL));
402 else if(rc==SASL_NOMECH)
404 /* this is a temporary failure, because the mechanism is not
405 * available for this user. If it wasn't available at all, we
406 * shouldn't have got here in the first place...
409 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
411 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
416 else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
418 /* Anything else is a temporary failure, and we'll let SASL print out
419 * the error string for us
422 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
424 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
431 /* Get the username and copy it into $auth1 and $1. The former is now the
432 preferred variable; the latter is the original variable. */
433 rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
437 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
438 sasl_errstring(rc, NULL, NULL));
439 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
440 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
441 sasl_errstring(rc, NULL, NULL));
447 auth_vars[0] = expand_nstring[1] = string_copy(out2);
448 expand_nlength[1] = Ustrlen(expand_nstring[1]);
452 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
453 ob->server_mech, auth_vars[0]);
455 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
459 debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
460 sasl_errstring(rc, NULL, NULL));
461 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
462 "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
463 sasl_errstring(rc, NULL, NULL));
468 negotiated_ssf = *negotiated_ssf_ptr;
470 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
471 if (negotiated_ssf > 0)
474 debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
475 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
476 "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
482 /* close down the connection, freeing up library's memory */
486 /* Expand server_condition as an authorization check */
487 return auth_check_serv_cond(ablock);
491 return 0; /* Stop compiler complaints */
494 /*************************************************
496 *************************************************/
499 auth_cyrus_sasl_version_report(FILE *f)
501 const char *implementation, *version;
502 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
503 fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
504 " Runtime: %s [%s]\n",
505 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
506 version, implementation);
509 /*************************************************
510 * Client entry point *
511 *************************************************/
513 /* For interface, see auths/README */
516 auth_cyrus_sasl_client(
517 auth_instance *ablock, /* authenticator block */
518 smtp_inblock *inblock, /* input connection */
519 smtp_outblock *outblock, /* output connection */
520 int timeout, /* command timeout */
521 uschar *buffer, /* for reading response */
522 int buffsize) /* size of buffer */
524 /* We don't support clients (yet) in this implementation of cyrus_sasl */
528 #endif /* AUTH_CYRUS_SASL */
530 /* End of cyrus_sasl.c */