Cyrus SASL auth: SSF retrieval was incorrect.
[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, 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_cipher)
260 {
261 rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_bits);
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 }
273 else
274 HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
275
276 rc=SASL_CONTINUE;
277
278 while(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 {
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));
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
387 auth_vars[0] = expand_nstring[1] = string_copy(out2);
388 expand_nlength[1] = Ustrlen(expand_nstring[1]);
389 expand_nmax = 1;
390
391 HDEBUG(D_auth)
392 debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
393 ob->server_mech, auth_vars[0]);
394
395 rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
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 }
408 negotiated_ssf = *negotiated_ssf_ptr;
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
422 /* close down the connection, freeing up library's memory */
423 sasl_dispose(&conn);
424 sasl_done();
425
426 /* Expand server_condition as an authorization check */
427 return auth_check_serv_cond(ablock);
428 }
429 }
430 /* NOTREACHED */
431 return 0; /* Stop compiler complaints */
432 }
433
434 /*************************************************
435 * Diagnostic API *
436 *************************************************/
437
438 void
439 auth_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
449 /*************************************************
450 * Client entry point *
451 *************************************************/
452
453 /* For interface, see auths/README */
454
455 int
456 auth_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 */
465 return FAIL;
466 }
467
468 #endif /* AUTH_CYRUS_SASL */
469
470 /* End of cyrus_sasl.c */