Clear more globals between messages
[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,
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)) }
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;}
71int auth_cyrus_sasl_client(auth_instance *ablock, smtp_inblock *inblock,
72 smtp_outblock *outblock, 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
92mysasl_config(void *context,
93 const char *plugin_name,
94 const char *option,
95 const char **result,
96 unsigned int *len)
97{
98if (context && !strcmp(option, "mech_list"))
99 {
100 *result = context;
101 if (len != NULL) *len = strlen(*result);
102 return SASL_OK;
103 }
104return SASL_FAIL;
105}
106
107/* Here's the real function */
108
0756eb3c
PH
109void
110auth_cyrus_sasl_init(auth_instance *ablock)
111{
112auth_cyrus_sasl_options_block *ob =
113 (auth_cyrus_sasl_options_block *)(ablock->options_block);
93a6fce2 114const uschar *list, *listptr, *buffer;
0756eb3c
PH
115int rc, i;
116unsigned int len;
edc33b5f 117uschar *rs_point, *expanded_hostname;
4c287009 118char *realm_expanded;
0756eb3c 119
c5ddb310
PH
120sasl_conn_t *conn;
121sasl_callback_t cbs[]={
122 {SASL_CB_GETOPT, NULL, NULL },
123 {SASL_CB_LIST_END, NULL, NULL}};
124
0756eb3c
PH
125/* default the mechanism to our "public name" */
126if(ob->server_mech == NULL)
127 ob->server_mech=string_copy(ablock->public_name);
128
edc33b5f
PP
129expanded_hostname = expand_string(ob->server_hostname);
130if (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);
134
4c287009
PP
135realm_expanded=NULL;
136if (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);
142}
143
0756eb3c
PH
144/* we're going to initialise the library to check that there is an
145 * authenticator of type whatever mechanism we're using
146 */
c5ddb310 147
edc33b5f 148cbs[0].proc = (int(*)(void)) &mysasl_config;
c5ddb310
PH
149cbs[0].context = ob->server_mech;
150
0756eb3c 151rc=sasl_server_init(cbs, "exim");
c5ddb310 152
0756eb3c
PH
153if( rc != SASL_OK )
154 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
155 "couldn't initialise Cyrus SASL library.", ablock->name);
156
edc33b5f 157rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
4c287009 158 realm_expanded, NULL, NULL, NULL, 0, &conn);
0756eb3c
PH
159if( rc != SASL_OK )
160 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
161 "couldn't initialise Cyrus SASL server connection.", ablock->name);
162
93a6fce2 163rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
0756eb3c
PH
164if( rc != SASL_OK )
165 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
166 "couldn't get Cyrus SASL mechanism list.", ablock->name);
167
168i=':';
169listptr=list;
170
edc33b5f
PP
171HDEBUG(D_auth) {
172 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
4c287009 173 ob->server_service, expanded_hostname, realm_expanded);
edc33b5f
PP
174 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
175}
0756eb3c
PH
176
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.
180 */
181rs_point=store_get(0);
182
183/* loop until either we get to the end of the list, or we match the
184 * public name of this authenticator
185 */
186while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
187 strcmpic(buffer,ob->server_mech) );
188
189if(!buffer)
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);
192
193store_reset(rs_point);
194
195HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
196
197/* make sure that if we get here then we're allowed to advertise. */
198ablock->server = TRUE;
199
200sasl_dispose(&conn);
201sasl_done();
202}
203
204/*************************************************
205* Server entry point *
206*************************************************/
207
208/* For interface, see auths/README */
209
210/* note, we don't care too much about memory allocation in this, because this is entirely
211 * within a shortlived child
212 */
213
214int
215auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
216{
217auth_cyrus_sasl_options_block *ob =
218 (auth_cyrus_sasl_options_block *)(ablock->options_block);
219uschar *output, *out2, *input, *clear, *hname;
220uschar *debug = NULL; /* Stops compiler complaining */
221sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
222sasl_conn_t *conn;
4c287009 223char *realm_expanded;
66645890 224int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
0756eb3c
PH
225unsigned int inlen, outlen;
226
227input=data;
228inlen=Ustrlen(data);
229
230HDEBUG(D_auth) debug=string_copy(data);
231
232hname=expand_string(ob->server_hostname);
4c287009
PP
233realm_expanded=NULL;
234if (hname && ob->server_realm)
235 realm_expanded= CS expand_string(ob->server_realm);
236if((hname == NULL) ||
237 ((realm_expanded == NULL) && (ob->server_realm != NULL)))
0756eb3c
PH
238 {
239 auth_defer_msg = expand_string_message;
240 return DEFER;
241 }
242
243if(inlen)
244 {
f4d091fb 245 clen = b64decode(input, &clear);
0756eb3c
PH
246 if(clen < 0)
247 {
248 return BAD64;
249 }
250 input=clear;
251 inlen=clen;
252 }
253
254rc=sasl_server_init(cbs, "exim");
255if (rc != SASL_OK)
256 {
257 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
258 return DEFER;
259 }
260
4c287009 261rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
acb1b346
PH
262 NULL, NULL, 0, &conn);
263
edc33b5f
PP
264HDEBUG(D_auth)
265 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
4c287009 266 ob->server_service, hname, realm_expanded);
edc33b5f 267
0756eb3c
PH
268if( rc != SASL_OK )
269 {
270 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
271 sasl_done();
272 return DEFER;
273 }
274
817d9f57 275if (tls_in.cipher)
edc33b5f 276 {
817d9f57 277 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
edc33b5f
PP
278 if (rc != SASL_OK)
279 {
280 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
817d9f57 281 tls_in.bits, sasl_errstring(rc, NULL, NULL));
edc33b5f
PP
282 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
283 sasl_done();
284 return DEFER;
285 }
286 else
817d9f57 287 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
edc33b5f
PP
288 }
289else
290 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
291
66645890
PP
292/* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
293annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
294with their iptostring() function, which just wraps
295getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
296inet_ntop which we wrap in our host_ntoa() function.
297
298So the docs are too strict and we shouldn't worry about :: contractions. */
299
300/* Set properties for remote and local host-ip;port */
301for (i=0; i < 2; ++i)
302 {
303 struct sockaddr_storage ss;
304 int (*query)(int, struct sockaddr *, socklen_t *);
305 int propnum, port;
306 const uschar *label;
307 uschar *address, *address_port;
308 const char *s_err;
309 socklen_t sslen;
310
311 if (i)
312 {
313 query = &getpeername;
314 propnum = SASL_IPREMOTEPORT;
315 label = CUS"peer";
316 }
317 else
318 {
319 query = &getsockname;
320 propnum = SASL_IPLOCALPORT;
321 label = CUS"local";
322 }
323
324 sslen = sizeof(ss);
325 rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
326 if (rc < 0)
327 {
328 HDEBUG(D_auth)
329 debug_printf("Failed to get %s address information: %s\n",
330 label, strerror(errno));
331 break;
332 }
333
334 address = host_ntoa(-1, &ss, NULL, &port);
335 address_port = string_sprintf("%s;%d", address, port);
336
337 rc = sasl_setprop(conn, propnum, address_port);
338 if (rc != SASL_OK)
339 {
340 s_err = sasl_errdetail(conn);
341 HDEBUG(D_auth)
342 debug_printf("Failed to set %s SASL property: [%d] %s\n",
343 label, rc, s_err ? s_err : "<unknown reason>");
344 break;
345 }
346 HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
347 label, address_port);
348 }
349
0756eb3c
PH
350rc=SASL_CONTINUE;
351
352while(rc==SASL_CONTINUE)
353 {
354 if(firsttime)
355 {
356 firsttime=0;
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);
360 }
361 else
362 {
363 /* make sure that we have a null-terminated string */
364 out2=store_get(outlen+1);
365 memcpy(out2,output,outlen);
366 out2[outlen]='\0';
367 if((rc=auth_get_data(&input, out2, outlen))!=OK)
368 {
369 /* we couldn't get the data, so free up the library before
370 * returning whatever error we get */
371 sasl_dispose(&conn);
372 sasl_done();
373 return rc;
374 }
375 inlen=Ustrlen(input);
376
377 HDEBUG(D_auth) debug=string_copy(input);
378 if(inlen)
379 {
f4d091fb 380 clen = b64decode(input, &clear);
0756eb3c
PH
381 if(clen < 0)
382 {
383 sasl_dispose(&conn);
384 sasl_done();
385 return BAD64;
386 }
387 input=clear;
388 inlen=clen;
389 }
390
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);
393 }
394 if(rc==SASL_BADPROT)
395 {
396 sasl_dispose(&conn);
397 sasl_done();
398 return UNEXPECTED;
399 }
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
404 || rc==SASL_NOUSER )
405 {
406 /* these are considered permanent failure codes */
407 HDEBUG(D_auth)
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));
412 sasl_dispose(&conn);
413 sasl_done();
414 return FAIL;
415 }
416 else if(rc==SASL_NOMECH)
417 {
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...
421 */
422 HDEBUG(D_auth)
423 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
424 auth_defer_msg =
425 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
426 sasl_dispose(&conn);
427 sasl_done();
428 return DEFER;
429 }
430 else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
431 {
432 /* Anything else is a temporary failure, and we'll let SASL print out
433 * the error string for us
434 */
435 HDEBUG(D_auth)
436 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
437 auth_defer_msg =
438 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
439 sasl_dispose(&conn);
440 sasl_done();
441 return DEFER;
442 }
443 else if(rc==SASL_OK)
444 {
f78eb7c6
PH
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));
edc33b5f
PP
448 if (rc != SASL_OK)
449 {
450 HDEBUG(D_auth)
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));
456 sasl_dispose(&conn);
457 sasl_done();
458 return FAIL;
459 }
460
f78eb7c6
PH
461 auth_vars[0] = expand_nstring[1] = string_copy(out2);
462 expand_nlength[1] = Ustrlen(expand_nstring[1]);
463 expand_nmax = 1;
0756eb3c
PH
464
465 HDEBUG(D_auth)
edc33b5f
PP
466 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
467 ob->server_mech, auth_vars[0]);
468
16880d1a 469 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
edc33b5f
PP
470 if (rc != SASL_OK)
471 {
472 HDEBUG(D_auth)
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));
478 sasl_dispose(&conn);
479 sasl_done();
480 return FAIL;
481 }
16880d1a 482 negotiated_ssf = *negotiated_ssf_ptr;
edc33b5f
PP
483 HDEBUG(D_auth)
484 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
485 if (negotiated_ssf > 0)
486 {
487 HDEBUG(D_auth)
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);
491 sasl_dispose(&conn);
492 sasl_done();
493 return FAIL;
494 }
495
0756eb3c
PH
496 /* close down the connection, freeing up library's memory */
497 sasl_dispose(&conn);
498 sasl_done();
16ff981e
PH
499
500 /* Expand server_condition as an authorization check */
501 return auth_check_serv_cond(ablock);
0756eb3c
PH
502 }
503 }
504/* NOTREACHED */
505return 0; /* Stop compiler complaints */
506}
507
6545de78
PP
508/*************************************************
509* Diagnostic API *
510*************************************************/
511
512void
513auth_cyrus_sasl_version_report(FILE *f)
514{
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);
521}
522
0756eb3c
PH
523/*************************************************
524* Client entry point *
525*************************************************/
526
527/* For interface, see auths/README */
528
529int
530auth_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 */
537{
538/* We don't support clients (yet) in this implementation of cyrus_sasl */
539return FAIL;
540}
541
d185889f 542#endif /*!MACRO_PREDEF*/
0756eb3c
PH
543#endif /* AUTH_CYRUS_SASL */
544
545/* End of cyrus_sasl.c */