cbf2446338d8f34fedebf1e4c5a6e1d6773ed891
[exim.git] / src / src / auths / cyrus_sasl.c
1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2015 */
6 /* See the file NOTICE for conditions of use and distribution. */
7
8 /* This code was originally contributed by Matthew Byng-Maddick */
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
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
25 loops. */
26
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); }
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
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)) }
48 };
49
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. */
52
53 int auth_cyrus_sasl_options_count =
54 sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
55
56 /* Default private options block for the cyrus_sasl authentication method. */
57
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 */
63 };
64
65
66 #ifdef MACRO_PREDEF
67
68 /* Dummy values */
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;}
73 void auth_cyrus_sasl_version_report(FILE *f) {}
74
75 #else /*!MACRO_PREDEF*/
76
77
78
79
80 /*************************************************
81 * Initialization entry point *
82 *************************************************/
83
84 /* Called for each instance, after its options have been read, to
85 enable consistency checks to be done, or anything else that needs
86 to be set up. */
87
88
89 /* Auxiliary function, passed in data to sasl_server_init(). */
90
91 static int
92 mysasl_config(void *context,
93 const char *plugin_name,
94 const char *option,
95 const char **result,
96 unsigned int *len)
97 {
98 if (context && !strcmp(option, "mech_list"))
99 {
100 *result = context;
101 if (len != NULL) *len = strlen(*result);
102 return SASL_OK;
103 }
104 return SASL_FAIL;
105 }
106
107 /* Here's the real function */
108
109 void
110 auth_cyrus_sasl_init(auth_instance *ablock)
111 {
112 auth_cyrus_sasl_options_block *ob =
113 (auth_cyrus_sasl_options_block *)(ablock->options_block);
114 const uschar *list, *listptr, *buffer;
115 int rc, i;
116 unsigned int len;
117 uschar *rs_point, *expanded_hostname;
118 char *realm_expanded;
119
120 sasl_conn_t *conn;
121 sasl_callback_t cbs[]={
122 {SASL_CB_GETOPT, NULL, NULL },
123 {SASL_CB_LIST_END, NULL, NULL}};
124
125 /* default the mechanism to our "public name" */
126 if(ob->server_mech == NULL)
127 ob->server_mech=string_copy(ablock->public_name);
128
129 expanded_hostname = expand_string(ob->server_hostname);
130 if (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
135 realm_expanded=NULL;
136 if (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
144 /* we're going to initialise the library to check that there is an
145 * authenticator of type whatever mechanism we're using
146 */
147
148 cbs[0].proc = (int(*)(void)) &mysasl_config;
149 cbs[0].context = ob->server_mech;
150
151 rc=sasl_server_init(cbs, "exim");
152
153 if( 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
157 rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
158 realm_expanded, NULL, NULL, NULL, 0, &conn);
159 if( 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
163 rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
164 if( 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
168 i=':';
169 listptr=list;
170
171 HDEBUG(D_auth) {
172 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
173 ob->server_service, expanded_hostname, realm_expanded);
174 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
175 }
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 */
181 rs_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 */
186 while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
187 strcmpic(buffer,ob->server_mech) );
188
189 if(!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
193 store_reset(rs_point);
194
195 HDEBUG(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. */
198 ablock->server = TRUE;
199
200 sasl_dispose(&conn);
201 sasl_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
214 int
215 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
216 {
217 auth_cyrus_sasl_options_block *ob =
218 (auth_cyrus_sasl_options_block *)(ablock->options_block);
219 uschar *output, *out2, *input, *clear, *hname;
220 uschar *debug = NULL; /* Stops compiler complaining */
221 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
222 sasl_conn_t *conn;
223 char *realm_expanded;
224 int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
225 unsigned int inlen, outlen;
226
227 input=data;
228 inlen=Ustrlen(data);
229
230 HDEBUG(D_auth) debug=string_copy(data);
231
232 hname=expand_string(ob->server_hostname);
233 realm_expanded=NULL;
234 if (hname && ob->server_realm)
235 realm_expanded= CS expand_string(ob->server_realm);
236 if((hname == NULL) ||
237 ((realm_expanded == NULL) && (ob->server_realm != NULL)))
238 {
239 auth_defer_msg = expand_string_message;
240 return DEFER;
241 }
242
243 if(inlen)
244 {
245 clen = b64decode(input, &clear);
246 if(clen < 0)
247 {
248 return BAD64;
249 }
250 input=clear;
251 inlen=clen;
252 }
253
254 rc=sasl_server_init(cbs, "exim");
255 if (rc != SASL_OK)
256 {
257 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
258 return DEFER;
259 }
260
261 rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
262 NULL, NULL, 0, &conn);
263
264 HDEBUG(D_auth)
265 debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
266 ob->server_service, hname, realm_expanded);
267
268 if( rc != SASL_OK )
269 {
270 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
271 sasl_done();
272 return DEFER;
273 }
274
275 if (tls_in.cipher)
276 {
277 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
278 if (rc != SASL_OK)
279 {
280 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
281 tls_in.bits, sasl_errstring(rc, NULL, NULL));
282 auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
283 sasl_done();
284 return DEFER;
285 }
286 else
287 HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
288 }
289 else
290 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
291
292 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
293 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
294 with their iptostring() function, which just wraps
295 getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
296 inet_ntop which we wrap in our host_ntoa() function.
297
298 So the docs are too strict and we shouldn't worry about :: contractions. */
299
300 /* Set properties for remote and local host-ip;port */
301 for (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
350 rc=SASL_CONTINUE;
351
352 while(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 {
380 clen = b64decode(input, &clear);
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 {
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));
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
461 auth_vars[0] = expand_nstring[1] = string_copy(out2);
462 expand_nlength[1] = Ustrlen(expand_nstring[1]);
463 expand_nmax = 1;
464
465 HDEBUG(D_auth)
466 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
467 ob->server_mech, auth_vars[0]);
468
469 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
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 }
482 negotiated_ssf = *negotiated_ssf_ptr;
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
496 /* close down the connection, freeing up library's memory */
497 sasl_dispose(&conn);
498 sasl_done();
499
500 /* Expand server_condition as an authorization check */
501 return auth_check_serv_cond(ablock);
502 }
503 }
504 /* NOTREACHED */
505 return 0; /* Stop compiler complaints */
506 }
507
508 /*************************************************
509 * Diagnostic API *
510 *************************************************/
511
512 void
513 auth_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
523 /*************************************************
524 * Client entry point *
525 *************************************************/
526
527 /* For interface, see auths/README */
528
529 int
530 auth_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 */
539 return FAIL;
540 }
541
542 #endif /*!MACRO_PREDEF*/
543 #endif /* AUTH_CYRUS_SASL */
544
545 /* End of cyrus_sasl.c */