Auths: fix cyrus-sasl driver for gssapi use. Bug 2524
[exim.git] / src / src / auths / cyrus_sasl.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
0756eb3c
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
c5ddb310 8/* This code was originally contributed by Matthew Byng-Maddick */
0756eb3c
PH
9
10/* Copyright (c) A L Digital 2004 */
11
12/* A generic (mechanism independent) Cyrus SASL authenticator. */
13
14
15#include "../exim.h"
16
17
18/* We can't just compile this code and allow the library mechanism to omit the
19functions if they are not wanted, because we need to have the Cyrus SASL header
20available for compiling. Therefore, compile these functions only if
21AUTH_CYRUS_SASL is defined. However, some compilers don't like compiling empty
22modules, so keep them happy with a dummy when skipping the rest. Make it
23reference itself to stop picky compilers complaining that it is unused, and put
24in a dummy argument to stop even pickier compilers complaining about infinite
25loops. */
26
27#ifndef AUTH_CYRUS_SASL
e1d15f5e
JH
28static void dummy(int x);
29static void dummy2(int x) { dummy(x-1); }
30static void dummy(int x) { dummy2(x-1); }
0756eb3c
PH
31#else
32
33
34#include <sasl/sasl.h>
35#include "cyrus_sasl.h"
36
37/* Options specific to the cyrus_sasl authentication mechanism. */
38
39optionlist auth_cyrus_sasl_options[] = {
40 { "server_hostname", opt_stringptr,
13a4b4c1 41 OPT_OFF(auth_cyrus_sasl_options_block, server_hostname) },
0756eb3c 42 { "server_mech", opt_stringptr,
13a4b4c1 43 OPT_OFF(auth_cyrus_sasl_options_block, server_mech) },
0756eb3c 44 { "server_realm", opt_stringptr,
13a4b4c1 45 OPT_OFF(auth_cyrus_sasl_options_block, server_realm) },
0756eb3c 46 { "server_service", opt_stringptr,
13a4b4c1 47 OPT_OFF(auth_cyrus_sasl_options_block, server_service) }
0756eb3c
PH
48};
49
50/* Size of the options list. An extern variable has to be used so that its
51address can appear in the tables drtables.c. */
52
53int auth_cyrus_sasl_options_count =
54 sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
55
16ff981e 56/* Default private options block for the cyrus_sasl authentication method. */
0756eb3c
PH
57
58auth_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 */
63};
64
65
d185889f
JH
66#ifdef MACRO_PREDEF
67
68/* Dummy values */
69void auth_cyrus_sasl_init(auth_instance *ablock) {}
70int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;}
251b9eb4
JH
71int auth_cyrus_sasl_client(auth_instance *ablock, void * sx,
72 int timeout, uschar *buffer, int buffsize) {return 0;}
f9df71c0 73void auth_cyrus_sasl_version_report(FILE *f) {}
d185889f
JH
74
75#else /*!MACRO_PREDEF*/
76
77
78
79
0756eb3c
PH
80/*************************************************
81* Initialization entry point *
82*************************************************/
83
84/* Called for each instance, after its options have been read, to
85enable consistency checks to be done, or anything else that needs
86to be set up. */
87
c5ddb310
PH
88
89/* Auxiliary function, passed in data to sasl_server_init(). */
90
91static int
6a2c32cb
JH
92mysasl_config(void *context, const char *plugin_name, const char *option,
93 const char **result, unsigned int *len)
c5ddb310
PH
94{
95if (context && !strcmp(option, "mech_list"))
96 {
97 *result = context;
6a2c32cb 98 if (len) *len = strlen(*result);
c5ddb310
PH
99 return SASL_OK;
100 }
101return SASL_FAIL;
102}
103
104/* Here's the real function */
105
0756eb3c
PH
106void
107auth_cyrus_sasl_init(auth_instance *ablock)
108{
109auth_cyrus_sasl_options_block *ob =
110 (auth_cyrus_sasl_options_block *)(ablock->options_block);
93a6fce2 111const uschar *list, *listptr, *buffer;
0756eb3c
PH
112int rc, i;
113unsigned int len;
f3ebb786
JH
114rmark rs_point;
115uschar *expanded_hostname;
4c287009 116char *realm_expanded;
0756eb3c 117
c5ddb310 118sasl_conn_t *conn;
c0fb53b7 119sasl_callback_t cbs[] = {
c5ddb310
PH
120 {SASL_CB_GETOPT, NULL, NULL },
121 {SASL_CB_LIST_END, NULL, NULL}};
122
0756eb3c 123/* default the mechanism to our "public name" */
0756eb3c 124
6a2c32cb
JH
125if (!ob->server_mech) ob->server_mech = string_copy(ablock->public_name);
126
127if (!(expanded_hostname = expand_string(ob->server_hostname)))
edc33b5f
PP
128 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
129 "couldn't expand server_hostname [%s]: %s",
130 ablock->name, ob->server_hostname, expand_string_message);
131
c0fb53b7 132realm_expanded = NULL;
6a2c32cb
JH
133if ( ob->server_realm
134 && !(realm_expanded = CS expand_string(ob->server_realm)))
135 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
136 "couldn't expand server_realm [%s]: %s",
137 ablock->name, ob->server_realm, expand_string_message);
4c287009 138
0756eb3c 139/* we're going to initialise the library to check that there is an
6a2c32cb 140authenticator of type whatever mechanism we're using */
c5ddb310 141
edc33b5f 142cbs[0].proc = (int(*)(void)) &mysasl_config;
c5ddb310
PH
143cbs[0].context = ob->server_mech;
144
6a2c32cb 145if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
0756eb3c
PH
146 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
147 "couldn't initialise Cyrus SASL library.", ablock->name);
148
c0fb53b7 149if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname,
6a2c32cb 150 realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK)
0756eb3c
PH
151 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
152 "couldn't initialise Cyrus SASL server connection.", ablock->name);
153
3d2e82c5 154if ((rc = sasl_listmech(conn, NULL, "", ":", "", CCSS &list, &len, &i)) != SASL_OK)
0756eb3c
PH
155 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
156 "couldn't get Cyrus SASL mechanism list.", ablock->name);
157
c0fb53b7
JH
158i = ':';
159listptr = list;
0756eb3c 160
c0fb53b7
JH
161HDEBUG(D_auth)
162 {
edc33b5f 163 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
4c287009 164 ob->server_service, expanded_hostname, realm_expanded);
edc33b5f 165 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
c0fb53b7 166 }
0756eb3c
PH
167
168/* the store_get / store_reset mechanism is hierarchical
6a2c32cb
JH
169 the hierarchy is stored for us behind our back. This point
170 creates a hierarchy point for this function. */
171
f3ebb786 172rs_point = store_mark();
0756eb3c
PH
173
174/* loop until either we get to the end of the list, or we match the
6a2c32cb
JH
175public name of this authenticator */
176
177while ( (buffer = string_nextinlist(&listptr, &i, NULL, 0))
178 && strcmpic(buffer,ob->server_mech) );
0756eb3c 179
c0fb53b7 180if (!buffer)
0756eb3c
PH
181 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
182 "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
183
184store_reset(rs_point);
185
186HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
187
188/* make sure that if we get here then we're allowed to advertise. */
189ablock->server = TRUE;
190
191sasl_dispose(&conn);
192sasl_done();
193}
194
195/*************************************************
196* Server entry point *
197*************************************************/
198
199/* For interface, see auths/README */
200
201/* note, we don't care too much about memory allocation in this, because this is entirely
6a2c32cb 202within a shortlived child */
0756eb3c
PH
203
204int
205auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
206{
207auth_cyrus_sasl_options_block *ob =
208 (auth_cyrus_sasl_options_block *)(ablock->options_block);
209uschar *output, *out2, *input, *clear, *hname;
210uschar *debug = NULL; /* Stops compiler complaining */
c0fb53b7 211sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
0756eb3c 212sasl_conn_t *conn;
c0fb53b7 213char * realm_expanded = NULL;
d7978c0f 214int rc, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf;
0756eb3c
PH
215unsigned int inlen, outlen;
216
c0fb53b7
JH
217input = data;
218inlen = Ustrlen(data);
0756eb3c 219
c0fb53b7 220HDEBUG(D_auth) debug = string_copy(data);
0756eb3c 221
c0fb53b7 222hname = expand_string(ob->server_hostname);
4c287009 223if (hname && ob->server_realm)
c0fb53b7
JH
224 realm_expanded = CS expand_string(ob->server_realm);
225if (!hname || !realm_expanded && ob->server_realm)
0756eb3c
PH
226 {
227 auth_defer_msg = expand_string_message;
228 return DEFER;
229 }
230
c0fb53b7 231if (inlen)
0756eb3c 232 {
c0fb53b7 233 if ((clen = b64decode(input, &clear)) < 0)
0756eb3c 234 return BAD64;
c0fb53b7
JH
235 input = clear;
236 inlen = clen;
0756eb3c
PH
237 }
238
c0fb53b7 239if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
0756eb3c
PH
240 {
241 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
242 return DEFER;
243 }
244
c0fb53b7 245rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
acb1b346
PH
246 NULL, NULL, 0, &conn);
247
edc33b5f
PP
248HDEBUG(D_auth)
249 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
4c287009 250 ob->server_service, hname, realm_expanded);
edc33b5f 251
c0fb53b7 252if (rc != SASL_OK )
0756eb3c
PH
253 {
254 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
255 sasl_done();
256 return DEFER;
257 }
258
817d9f57 259if (tls_in.cipher)
edc33b5f 260 {
c0fb53b7 261 if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
edc33b5f
PP
262 {
263 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
817d9f57 264 tls_in.bits, sasl_errstring(rc, NULL, NULL));
edc33b5f
PP
265 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
266 sasl_done();
267 return DEFER;
268 }
269 else
817d9f57 270 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
6a2c32cb
JH
271
272 /*XXX Set channel-binding here with sasl_channel_binding_t / SASL_CHANNEL_BINDING
273 Unclear what the "name" element does though, ditto the "critical" flag. */
edc33b5f
PP
274 }
275else
276 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
277
66645890
PP
278/* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
279annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
280with their iptostring() function, which just wraps
281getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
282inet_ntop which we wrap in our host_ntoa() function.
283
284So the docs are too strict and we shouldn't worry about :: contractions. */
285
286/* Set properties for remote and local host-ip;port */
d7978c0f 287for (int i = 0; i < 2; ++i)
66645890 288 {
6a2c32cb
JH
289 int propnum;
290 const uschar * label;
291 uschar * address_port;
66645890 292 const char *s_err;
66645890
PP
293
294 if (i)
295 {
66645890
PP
296 propnum = SASL_IPREMOTEPORT;
297 label = CUS"peer";
6a2c32cb
JH
298 address_port = string_sprintf("%s;%d",
299 sender_host_address, sender_host_port);
66645890
PP
300 }
301 else
302 {
66645890
PP
303 propnum = SASL_IPLOCALPORT;
304 label = CUS"local";
6a2c32cb 305 address_port = string_sprintf("%s;%d", interface_address, interface_port);
66645890
PP
306 }
307
c0fb53b7 308 if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
66645890 309 {
66645890 310 HDEBUG(D_auth)
6a2c32cb
JH
311 {
312 s_err = sasl_errdetail(conn);
66645890
PP
313 debug_printf("Failed to set %s SASL property: [%d] %s\n",
314 label, rc, s_err ? s_err : "<unknown reason>");
6a2c32cb 315 }
66645890
PP
316 break;
317 }
318 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
319 label, address_port);
320 }
321
c0fb53b7 322for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
0756eb3c 323 {
c0fb53b7 324 if (firsttime)
0756eb3c 325 {
c0fb53b7 326 firsttime = 0;
0756eb3c 327 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
3d2e82c5
JH
328 rc = sasl_server_start(conn, CS ob->server_mech, inlen ? CS input : NULL, inlen,
329 CCSS &output, &outlen);
0756eb3c
PH
330 }
331 else
332 {
5c329a43
JH
333 /* auth_get_data() takes a length-specfied block of binary
334 which can include zeroes; no terminating NUL is needed */
c0fb53b7 335
5c329a43 336 if ((rc = auth_get_data(&input, output, outlen)) != OK)
0756eb3c
PH
337 {
338 /* we couldn't get the data, so free up the library before
6a2c32cb 339 returning whatever error we get */
0756eb3c
PH
340 sasl_dispose(&conn);
341 sasl_done();
342 return rc;
343 }
c0fb53b7 344 inlen = Ustrlen(input);
0756eb3c 345
c0fb53b7
JH
346 HDEBUG(D_auth) debug = string_copy(input);
347 if (inlen)
0756eb3c 348 {
c0fb53b7 349 if ((clen = b64decode(input, &clear)) < 0)
0756eb3c 350 {
c0fb53b7
JH
351 sasl_dispose(&conn);
352 sasl_done();
0756eb3c
PH
353 return BAD64;
354 }
c0fb53b7
JH
355 input = clear;
356 inlen = clen;
0756eb3c
PH
357 }
358
359 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
3d2e82c5 360 rc = sasl_server_step(conn, CS input, inlen, CCSS &output, &outlen);
0756eb3c 361 }
c0fb53b7
JH
362
363 if (rc == SASL_BADPROT)
0756eb3c
PH
364 {
365 sasl_dispose(&conn);
366 sasl_done();
367 return UNEXPECTED;
368 }
c0fb53b7
JH
369 if (rc == SASL_CONTINUE)
370 continue;
371
372 /* Get the username and copy it into $auth1 and $1. The former is now the
373 preferred variable; the latter is the original variable. */
374
5c329a43 375 if ((sasl_getprop(conn, SASL_USERNAME, (const void **)&out2)) != SASL_OK)
0756eb3c 376 {
0756eb3c 377 HDEBUG(D_auth)
c0fb53b7
JH
378 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
379 sasl_errstring(rc, NULL, NULL));
0756eb3c 380 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
c0fb53b7 381 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
0756eb3c
PH
382 sasl_errstring(rc, NULL, NULL));
383 sasl_dispose(&conn);
384 sasl_done();
385 return FAIL;
386 }
c0fb53b7
JH
387 auth_vars[0] = expand_nstring[1] = string_copy(out2);
388 expand_nlength[1] = Ustrlen(out2);
389 expand_nmax = 1;
390
391 switch (rc)
0756eb3c 392 {
c0fb53b7
JH
393 case SASL_FAIL: case SASL_BUFOVER: case SASL_BADMAC: case SASL_BADAUTH:
394 case SASL_NOAUTHZ: case SASL_ENCRYPT: case SASL_EXPIRED:
395 case SASL_DISABLED: case SASL_NOUSER:
396 /* these are considered permanent failure codes */
edc33b5f 397 HDEBUG(D_auth)
c0fb53b7 398 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
edc33b5f 399 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
c0fb53b7
JH
400 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
401 sasl_errstring(rc, NULL, NULL));
edc33b5f
PP
402 sasl_dispose(&conn);
403 sasl_done();
404 return FAIL;
0756eb3c 405
c0fb53b7
JH
406 case SASL_NOMECH:
407 /* this is a temporary failure, because the mechanism is not
6a2c32cb
JH
408 available for this user. If it wasn't available at all, we
409 shouldn't have got here in the first place... */
410
edc33b5f 411 HDEBUG(D_auth)
c0fb53b7
JH
412 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
413 auth_defer_msg =
414 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
edc33b5f
PP
415 sasl_dispose(&conn);
416 sasl_done();
c0fb53b7
JH
417 return DEFER;
418
419 case SASL_OK:
edc33b5f 420 HDEBUG(D_auth)
c0fb53b7
JH
421 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
422 ob->server_mech, auth_vars[0]);
423
424 if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK)
425 {
426 HDEBUG(D_auth)
427 debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
428 sasl_errstring(rc, NULL, NULL));
429 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
430 "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
431 sasl_errstring(rc, NULL, NULL));
432 sasl_dispose(&conn);
433 sasl_done();
434 return FAIL;
435 }
436 negotiated_ssf = *negotiated_ssf_ptr;
437 HDEBUG(D_auth)
438 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
439 if (negotiated_ssf > 0)
440 {
441 HDEBUG(D_auth)
442 debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
443 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
444 "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
445 sasl_dispose(&conn);
446 sasl_done();
447 return FAIL;
448 }
449
450 /* close down the connection, freeing up library's memory */
edc33b5f
PP
451 sasl_dispose(&conn);
452 sasl_done();
edc33b5f 453
c0fb53b7
JH
454 /* Expand server_condition as an authorization check */
455 return auth_check_serv_cond(ablock);
16ff981e 456
c0fb53b7
JH
457 default:
458 /* Anything else is a temporary failure, and we'll let SASL print out
459 * the error string for us
460 */
461 HDEBUG(D_auth)
462 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
463 auth_defer_msg =
464 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
465 sasl_dispose(&conn);
466 sasl_done();
467 return DEFER;
0756eb3c
PH
468 }
469 }
470/* NOTREACHED */
471return 0; /* Stop compiler complaints */
472}
473
474/*************************************************
6545de78
PP
475* Diagnostic API *
476*************************************************/
477
478void
479auth_cyrus_sasl_version_report(FILE *f)
480{
c0fb53b7
JH
481const char *implementation, *version;
482sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
483fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
484 " Runtime: %s [%s]\n",
485 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
486 version, implementation);
6545de78
PP
487}
488
489/*************************************************
0756eb3c
PH
490* Client entry point *
491*************************************************/
492
493/* For interface, see auths/README */
494
495int
496auth_cyrus_sasl_client(
497 auth_instance *ablock, /* authenticator block */
251b9eb4 498 void * sx, /* connexction */
0756eb3c 499 int timeout, /* command timeout */
d7978c0f 500 uschar *buffer, /* for reading response */
0756eb3c
PH
501 int buffsize) /* size of buffer */
502{
503/* We don't support clients (yet) in this implementation of cyrus_sasl */
504return FAIL;
505}
506
d185889f 507#endif /*!MACRO_PREDEF*/
0756eb3c
PH
508#endif /* AUTH_CYRUS_SASL */
509
510/* End of cyrus_sasl.c */