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 */
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;}
74 #else /*!MACRO_PREDEF*/
79 /*************************************************
80 * Initialization entry point *
81 *************************************************/
83 /* Called for each instance, after its options have been read, to
84 enable consistency checks to be done, or anything else that needs
88 /* Auxiliary function, passed in data to sasl_server_init(). */
91 mysasl_config(void *context,
92 const char *plugin_name,
97 if (context && !strcmp(option, "mech_list"))
100 if (len != NULL) *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;
116 uschar *rs_point, *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" */
125 if(ob->server_mech == NULL)
126 ob->server_mech=string_copy(ablock->public_name);
128 expanded_hostname = expand_string(ob->server_hostname);
129 if (expanded_hostname == NULL)
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);
135 if (ob->server_realm != NULL) {
136 realm_expanded = CS expand_string(ob->server_realm);
137 if (realm_expanded == NULL)
138 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
139 "couldn't expand server_realm [%s]: %s",
140 ablock->name, ob->server_realm, expand_string_message);
143 /* we're going to initialise the library to check that there is an
144 * authenticator of type whatever mechanism we're using
147 cbs[0].proc = (int(*)(void)) &mysasl_config;
148 cbs[0].context = ob->server_mech;
150 rc=sasl_server_init(cbs, "exim");
153 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
154 "couldn't initialise Cyrus SASL library.", ablock->name);
156 rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
157 realm_expanded, NULL, NULL, NULL, 0, &conn);
159 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
160 "couldn't initialise Cyrus SASL server connection.", ablock->name);
162 rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
164 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
165 "couldn't get Cyrus SASL mechanism list.", ablock->name);
171 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
172 ob->server_service, expanded_hostname, realm_expanded);
173 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
176 /* the store_get / store_reset mechanism is hierarchical
177 * the hierarchy is stored for us behind our back. This point
178 * creates a hierarchy point for this function.
180 rs_point=store_get(0);
182 /* loop until either we get to the end of the list, or we match the
183 * public name of this authenticator
185 while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
186 strcmpic(buffer,ob->server_mech) );
189 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
190 "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
192 store_reset(rs_point);
194 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
196 /* make sure that if we get here then we're allowed to advertise. */
197 ablock->server = TRUE;
203 /*************************************************
204 * Server entry point *
205 *************************************************/
207 /* For interface, see auths/README */
209 /* note, we don't care too much about memory allocation in this, because this is entirely
210 * within a shortlived child
214 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
216 auth_cyrus_sasl_options_block *ob =
217 (auth_cyrus_sasl_options_block *)(ablock->options_block);
218 uschar *output, *out2, *input, *clear, *hname;
219 uschar *debug = NULL; /* Stops compiler complaining */
220 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
222 char *realm_expanded;
223 int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
224 unsigned int inlen, outlen;
229 HDEBUG(D_auth) debug=string_copy(data);
231 hname=expand_string(ob->server_hostname);
233 if (hname && ob->server_realm)
234 realm_expanded= CS expand_string(ob->server_realm);
235 if((hname == NULL) ||
236 ((realm_expanded == NULL) && (ob->server_realm != NULL)))
238 auth_defer_msg = expand_string_message;
244 clen = b64decode(input, &clear);
253 rc=sasl_server_init(cbs, "exim");
256 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
260 rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
261 NULL, NULL, 0, &conn);
264 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
265 ob->server_service, hname, realm_expanded);
269 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
276 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
279 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
280 tls_in.bits, sasl_errstring(rc, NULL, NULL));
281 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
286 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
289 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
291 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
292 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
293 with their iptostring() function, which just wraps
294 getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
295 inet_ntop which we wrap in our host_ntoa() function.
297 So the docs are too strict and we shouldn't worry about :: contractions. */
299 /* Set properties for remote and local host-ip;port */
300 for (i=0; i < 2; ++i)
302 struct sockaddr_storage ss;
303 int (*query)(int, struct sockaddr *, socklen_t *);
306 uschar *address, *address_port;
312 query = &getpeername;
313 propnum = SASL_IPREMOTEPORT;
318 query = &getsockname;
319 propnum = SASL_IPLOCALPORT;
324 rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
328 debug_printf("Failed to get %s address information: %s\n",
329 label, strerror(errno));
333 address = host_ntoa(-1, &ss, NULL, &port);
334 address_port = string_sprintf("%s;%d", address, port);
336 rc = sasl_setprop(conn, propnum, address_port);
339 s_err = sasl_errdetail(conn);
341 debug_printf("Failed to set %s SASL property: [%d] %s\n",
342 label, rc, s_err ? s_err : "<unknown reason>");
345 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
346 label, address_port);
351 while(rc==SASL_CONTINUE)
356 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
357 rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
358 (const char **)(&output), &outlen);
362 /* make sure that we have a null-terminated string */
363 out2=store_get(outlen+1);
364 memcpy(out2,output,outlen);
366 if((rc=auth_get_data(&input, out2, outlen))!=OK)
368 /* we couldn't get the data, so free up the library before
369 * returning whatever error we get */
374 inlen=Ustrlen(input);
376 HDEBUG(D_auth) debug=string_copy(input);
379 clen = b64decode(input, &clear);
390 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
391 rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
399 else if( rc==SASL_FAIL || rc==SASL_BUFOVER
400 || rc==SASL_BADMAC || rc==SASL_BADAUTH
401 || rc==SASL_NOAUTHZ || rc==SASL_ENCRYPT
402 || rc==SASL_EXPIRED || rc==SASL_DISABLED
405 /* these are considered permanent failure codes */
407 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
408 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
409 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
410 sasl_errstring(rc, NULL, NULL));
415 else if(rc==SASL_NOMECH)
417 /* this is a temporary failure, because the mechanism is not
418 * available for this user. If it wasn't available at all, we
419 * shouldn't have got here in the first place...
422 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
424 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
429 else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
431 /* Anything else is a temporary failure, and we'll let SASL print out
432 * the error string for us
435 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
437 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
444 /* Get the username and copy it into $auth1 and $1. The former is now the
445 preferred variable; the latter is the original variable. */
446 rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
450 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
451 sasl_errstring(rc, NULL, NULL));
452 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
453 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
454 sasl_errstring(rc, NULL, NULL));
460 auth_vars[0] = expand_nstring[1] = string_copy(out2);
461 expand_nlength[1] = Ustrlen(expand_nstring[1]);
465 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
466 ob->server_mech, auth_vars[0]);
468 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
472 debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
473 sasl_errstring(rc, NULL, NULL));
474 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
475 "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
476 sasl_errstring(rc, NULL, NULL));
481 negotiated_ssf = *negotiated_ssf_ptr;
483 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
484 if (negotiated_ssf > 0)
487 debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
488 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
489 "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
495 /* close down the connection, freeing up library's memory */
499 /* Expand server_condition as an authorization check */
500 return auth_check_serv_cond(ablock);
504 return 0; /* Stop compiler complaints */
507 /*************************************************
509 *************************************************/
512 auth_cyrus_sasl_version_report(FILE *f)
514 const char *implementation, *version;
515 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
516 fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
517 " Runtime: %s [%s]\n",
518 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
519 version, implementation);
522 /*************************************************
523 * Client entry point *
524 *************************************************/
526 /* For interface, see auths/README */
529 auth_cyrus_sasl_client(
530 auth_instance *ablock, /* authenticator block */
531 smtp_inblock *inblock, /* input connection */
532 smtp_outblock *outblock, /* output connection */
533 int timeout, /* command timeout */
534 uschar *buffer, /* for reading response */
535 int buffsize) /* size of buffer */
537 /* We don't support clients (yet) in this implementation of cyrus_sasl */
541 #endif /*!MACRO_PREDEF*/
542 #endif /* AUTH_CYRUS_SASL */
544 /* End of cyrus_sasl.c */