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