Cyrus SASL auth: SSF retrieval was incorrect.
[exim.git] / src / src / auths / cyrus_sasl.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
c4ceed07 5/* Copyright (c) University of Cambridge 1995 - 2012 */
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
28static 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
37optionlist 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
49address can appear in the tables drtables.c. */
50
51int auth_cyrus_sasl_options_count =
52 sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
53
16ff981e 54/* Default private options block for the cyrus_sasl authentication method. */
0756eb3c
PH
55
56auth_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
69enable consistency checks to be done, or anything else that needs
70to be set up. */
71
c5ddb310
PH
72
73/* Auxiliary function, passed in data to sasl_server_init(). */
74
75static int
76mysasl_config(void *context,
77 const char *plugin_name,
78 const char *option,
79 const char **result,
80 unsigned int *len)
81{
82if (context && !strcmp(option, "mech_list"))
83 {
84 *result = context;
85 if (len != NULL) *len = strlen(*result);
86 return SASL_OK;
87 }
88return SASL_FAIL;
89}
90
91/* Here's the real function */
92
0756eb3c
PH
93void
94auth_cyrus_sasl_init(auth_instance *ablock)
95{
96auth_cyrus_sasl_options_block *ob =
97 (auth_cyrus_sasl_options_block *)(ablock->options_block);
0756eb3c
PH
98uschar *list, *listptr, *buffer;
99int rc, i;
100unsigned int len;
edc33b5f 101uschar *rs_point, *expanded_hostname;
4c287009 102char *realm_expanded;
0756eb3c 103
c5ddb310
PH
104sasl_conn_t *conn;
105sasl_callback_t cbs[]={
106 {SASL_CB_GETOPT, NULL, NULL },
107 {SASL_CB_LIST_END, NULL, NULL}};
108
0756eb3c
PH
109/* default the mechanism to our "public name" */
110if(ob->server_mech == NULL)
111 ob->server_mech=string_copy(ablock->public_name);
112
edc33b5f
PP
113expanded_hostname = expand_string(ob->server_hostname);
114if (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
4c287009
PP
119realm_expanded=NULL;
120if (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
0756eb3c
PH
128/* we're going to initialise the library to check that there is an
129 * authenticator of type whatever mechanism we're using
130 */
c5ddb310 131
edc33b5f 132cbs[0].proc = (int(*)(void)) &mysasl_config;
c5ddb310
PH
133cbs[0].context = ob->server_mech;
134
0756eb3c 135rc=sasl_server_init(cbs, "exim");
c5ddb310 136
0756eb3c
PH
137if( 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
edc33b5f 141rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
4c287009 142 realm_expanded, NULL, NULL, NULL, 0, &conn);
0756eb3c
PH
143if( 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
147rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)(&list), &len, &i);
148if( 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
152i=':';
153listptr=list;
154
edc33b5f
PP
155HDEBUG(D_auth) {
156 debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
4c287009 157 ob->server_service, expanded_hostname, realm_expanded);
edc33b5f
PP
158 debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
159}
0756eb3c
PH
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 */
165rs_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 */
170while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
171 strcmpic(buffer,ob->server_mech) );
172
173if(!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
177store_reset(rs_point);
178
179HDEBUG(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. */
182ablock->server = TRUE;
183
184sasl_dispose(&conn);
185sasl_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
198int
199auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
200{
201auth_cyrus_sasl_options_block *ob =
202 (auth_cyrus_sasl_options_block *)(ablock->options_block);
203uschar *output, *out2, *input, *clear, *hname;
204uschar *debug = NULL; /* Stops compiler complaining */
205sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
206sasl_conn_t *conn;
4c287009 207char *realm_expanded;
16880d1a 208int rc, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
0756eb3c
PH
209unsigned int inlen, outlen;
210
211input=data;
212inlen=Ustrlen(data);
213
214HDEBUG(D_auth) debug=string_copy(data);
215
216hname=expand_string(ob->server_hostname);
4c287009
PP
217realm_expanded=NULL;
218if (hname && ob->server_realm)
219 realm_expanded= CS expand_string(ob->server_realm);
220if((hname == NULL) ||
221 ((realm_expanded == NULL) && (ob->server_realm != NULL)))
0756eb3c
PH
222 {
223 auth_defer_msg = expand_string_message;
224 return DEFER;
225 }
226
227if(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
238rc=sasl_server_init(cbs, "exim");
239if (rc != SASL_OK)
240 {
241 auth_defer_msg = US"couldn't initialise Cyrus SASL library";
242 return DEFER;
243 }
244
4c287009 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
0756eb3c
PH
252if( rc != SASL_OK )
253 {
254 auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
255 sasl_done();
256 return DEFER;
257 }
258
edc33b5f
PP
259if (tls_cipher)
260 {
16880d1a 261 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_bits);
edc33b5f
PP
262 if (rc != SASL_OK)
263 {
264 HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
265 tls_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_bits);
272 }
273else
274 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
275
0756eb3c
PH
276rc=SASL_CONTINUE;
277
278while(rc==SASL_CONTINUE)
279 {
280 if(firsttime)
281 {
282 firsttime=0;
283 HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
284 rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
285 (const char **)(&output), &outlen);
286 }
287 else
288 {
289 /* make sure that we have a null-terminated string */
290 out2=store_get(outlen+1);
291 memcpy(out2,output,outlen);
292 out2[outlen]='\0';
293 if((rc=auth_get_data(&input, out2, outlen))!=OK)
294 {
295 /* we couldn't get the data, so free up the library before
296 * returning whatever error we get */
297 sasl_dispose(&conn);
298 sasl_done();
299 return rc;
300 }
301 inlen=Ustrlen(input);
302
303 HDEBUG(D_auth) debug=string_copy(input);
304 if(inlen)
305 {
306 clen=auth_b64decode(input, &clear);
307 if(clen < 0)
308 {
309 sasl_dispose(&conn);
310 sasl_done();
311 return BAD64;
312 }
313 input=clear;
314 inlen=clen;
315 }
316
317 HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
318 rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
319 }
320 if(rc==SASL_BADPROT)
321 {
322 sasl_dispose(&conn);
323 sasl_done();
324 return UNEXPECTED;
325 }
326 else if( rc==SASL_FAIL || rc==SASL_BUFOVER
327 || rc==SASL_BADMAC || rc==SASL_BADAUTH
328 || rc==SASL_NOAUTHZ || rc==SASL_ENCRYPT
329 || rc==SASL_EXPIRED || rc==SASL_DISABLED
330 || rc==SASL_NOUSER )
331 {
332 /* these are considered permanent failure codes */
333 HDEBUG(D_auth)
334 debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
335 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
336 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
337 sasl_errstring(rc, NULL, NULL));
338 sasl_dispose(&conn);
339 sasl_done();
340 return FAIL;
341 }
342 else if(rc==SASL_NOMECH)
343 {
344 /* this is a temporary failure, because the mechanism is not
345 * available for this user. If it wasn't available at all, we
346 * shouldn't have got here in the first place...
347 */
348 HDEBUG(D_auth)
349 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
350 auth_defer_msg =
351 string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
352 sasl_dispose(&conn);
353 sasl_done();
354 return DEFER;
355 }
356 else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
357 {
358 /* Anything else is a temporary failure, and we'll let SASL print out
359 * the error string for us
360 */
361 HDEBUG(D_auth)
362 debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
363 auth_defer_msg =
364 string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
365 sasl_dispose(&conn);
366 sasl_done();
367 return DEFER;
368 }
369 else if(rc==SASL_OK)
370 {
f78eb7c6
PH
371 /* Get the username and copy it into $auth1 and $1. The former is now the
372 preferred variable; the latter is the original variable. */
373 rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
edc33b5f
PP
374 if (rc != SASL_OK)
375 {
376 HDEBUG(D_auth)
377 debug_printf("Cyrus SASL library will not tell us the username: %s\n",
378 sasl_errstring(rc, NULL, NULL));
379 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
380 "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
381 sasl_errstring(rc, NULL, NULL));
382 sasl_dispose(&conn);
383 sasl_done();
384 return FAIL;
385 }
386
f78eb7c6
PH
387 auth_vars[0] = expand_nstring[1] = string_copy(out2);
388 expand_nlength[1] = Ustrlen(expand_nstring[1]);
389 expand_nmax = 1;
0756eb3c
PH
390
391 HDEBUG(D_auth)
edc33b5f
PP
392 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
393 ob->server_mech, auth_vars[0]);
394
16880d1a 395 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
edc33b5f
PP
396 if (rc != SASL_OK)
397 {
398 HDEBUG(D_auth)
399 debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
400 sasl_errstring(rc, NULL, NULL));
401 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
402 "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
403 sasl_errstring(rc, NULL, NULL));
404 sasl_dispose(&conn);
405 sasl_done();
406 return FAIL;
407 }
16880d1a 408 negotiated_ssf = *negotiated_ssf_ptr;
edc33b5f
PP
409 HDEBUG(D_auth)
410 debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
411 if (negotiated_ssf > 0)
412 {
413 HDEBUG(D_auth)
414 debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
415 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
416 "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
417 sasl_dispose(&conn);
418 sasl_done();
419 return FAIL;
420 }
421
0756eb3c
PH
422 /* close down the connection, freeing up library's memory */
423 sasl_dispose(&conn);
424 sasl_done();
16ff981e
PH
425
426 /* Expand server_condition as an authorization check */
427 return auth_check_serv_cond(ablock);
0756eb3c
PH
428 }
429 }
430/* NOTREACHED */
431return 0; /* Stop compiler complaints */
432}
433
6545de78
PP
434/*************************************************
435* Diagnostic API *
436*************************************************/
437
438void
439auth_cyrus_sasl_version_report(FILE *f)
440{
441 const char *implementation, *version;
442 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
443 fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
444 " Runtime: %s [%s]\n",
445 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
446 version, implementation);
447}
448
0756eb3c
PH
449/*************************************************
450* Client entry point *
451*************************************************/
452
453/* For interface, see auths/README */
454
455int
456auth_cyrus_sasl_client(
457 auth_instance *ablock, /* authenticator block */
458 smtp_inblock *inblock, /* input connection */
459 smtp_outblock *outblock, /* output connection */
460 int timeout, /* command timeout */
461 uschar *buffer, /* for reading response */
462 int buffsize) /* size of buffer */
463{
464/* We don't support clients (yet) in this implementation of cyrus_sasl */
465return FAIL;
466}
467
468#endif /* AUTH_CYRUS_SASL */
469
470/* End of cyrus_sasl.c */