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