1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2012 */
6 /* See the file NOTICE for conditions of use and distribution. */
8 /* Copyright (c) Twitter Inc 2012 */
10 /* Interface to GNU SASL library for generic authentication. */
14 GNU SASL does not provide authentication data itself, so we have to expose
15 that decision to configuration. For some mechanisms, we need to act much
16 like plaintext. For others, we only need to be able to provide some
17 evaluated data on demand. There's no abstracted way (ie, without hardcoding
18 knowledge of authenticators here) to know which need what properties; we
19 can't query a session or the library for "we will need these for mechanism X".
21 So: we always require server_condition, even if sometimes it will just be
22 set as "yes". We do provide a number of other hooks, which might not make
23 sense in all contexts. For some, we can do checks at init time.
29 /* dummy function to satisfy compilers when we link in an "empty" file. */
30 static void dummy(int x) { dummy(x-1); }
34 #include "gsasl_exim.h"
36 /* Authenticator-specific options. */
37 /* I did have server_*_condition options for various mechanisms, but since
38 we only ever handle one mechanism at a time, I didn't see the point in keeping
39 that. In case someone sees a point, I've left the condition_check() API
41 optionlist auth_gsasl_options[] = {
42 { "server_channelbinding", opt_bool,
43 (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) },
44 { "server_hostname", opt_stringptr,
45 (void *)(offsetof(auth_gsasl_options_block, server_hostname)) },
46 { "server_mech", opt_stringptr,
47 (void *)(offsetof(auth_gsasl_options_block, server_mech)) },
48 { "server_password", opt_stringptr,
49 (void *)(offsetof(auth_gsasl_options_block, server_password)) },
50 { "server_realm", opt_stringptr,
51 (void *)(offsetof(auth_gsasl_options_block, server_realm)) },
52 { "server_scram_iter", opt_stringptr,
53 (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) },
54 { "server_scram_salt", opt_stringptr,
55 (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) },
56 { "server_service", opt_stringptr,
57 (void *)(offsetof(auth_gsasl_options_block, server_service)) }
59 /* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing
60 hooks to avoid cleartext passwords in the Exim server. */
62 int auth_gsasl_options_count =
63 sizeof(auth_gsasl_options)/sizeof(optionlist);
65 /* Defaults for the authenticator-specific options. */
66 auth_gsasl_options_block auth_gsasl_option_defaults = {
67 US"smtp", /* server_service */
68 US"$primary_hostname", /* server_hostname */
69 NULL, /* server_realm */
70 NULL, /* server_mech */
71 NULL, /* server_password */
72 NULL, /* server_scram_iter */
73 NULL, /* server_scram_salt */
74 FALSE /* server_channelbinding */
77 /* "Globals" for managing the gsasl interface. */
79 static Gsasl *gsasl_ctx = NULL;
81 main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop);
83 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
85 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
87 static BOOL sasl_error_should_defer = FALSE;
88 static Gsasl_property callback_loop = 0;
89 static BOOL checked_server_condition = FALSE;
91 enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 };
93 struct callback_exim_state {
94 auth_instance *ablock;
99 /*************************************************
100 * Initialization entry point *
101 *************************************************/
103 /* Called for each instance, after its options have been read, to
104 enable consistency checks to be done, or anything else that needs
108 auth_gsasl_init(auth_instance *ablock)
112 auth_gsasl_options_block *ob =
113 (auth_gsasl_options_block *)(ablock->options_block);
115 /* As per existing Cyrus glue, use the authenticator's public name as
116 the default for the mechanism name; we don't handle multiple mechanisms
117 in one authenticator, but the same driver can be used multiple times. */
119 if (ob->server_mech == NULL)
120 ob->server_mech = string_copy(ablock->public_name);
122 /* Can get multiple session contexts from one library context, so just
123 initialise the once. */
124 if (gsasl_ctx == NULL) {
125 rc = gsasl_init(&gsasl_ctx);
126 if (rc != GSASL_OK) {
127 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
128 "couldn't initialise GNU SASL library: %s (%s)",
129 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
131 gsasl_callback_set(gsasl_ctx, main_callback);
134 /* We don't need this except to log it for debugging. */
135 rc = gsasl_server_mechlist(gsasl_ctx, &p);
137 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
138 "failed to retrieve list of mechanisms: %s (%s)",
139 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
140 HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p);
142 supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech);
144 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
145 "GNU SASL does not support mechanism \"%s\"",
146 ablock->name, ob->server_mech);
148 if ((ablock->server_condition == NULL) &&
149 (streqic(ob->server_mech, US"EXTERNAL") ||
150 streqic(ob->server_mech, US"ANONYMOUS") ||
151 streqic(ob->server_mech, US"PLAIN") ||
152 streqic(ob->server_mech, US"LOGIN")))
153 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
154 "Need server_condition for %s mechanism",
155 ablock->name, ob->server_mech);
157 /* This does *not* scale to new SASL mechanisms. Need a better way to ask
158 which properties will be needed. */
159 if ((ob->server_realm == NULL) &&
160 streqic(ob->server_mech, US"DIGEST-MD5"))
161 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
162 "Need server_realm for %s mechanism",
163 ablock->name, ob->server_mech);
165 /* At present, for mechanisms we don't panic on absence of server_condition;
166 need to figure out the most generically correct approach to deciding when
167 it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism,
168 etc) it clearly is critical.
170 So don't activate without server_condition, this might be relaxed in the future.
172 if (ablock->server_condition != NULL) ablock->server = TRUE;
173 ablock->client = FALSE;
177 /* GNU SASL uses one top-level callback, registered at library level.
178 We dispatch to client and server functions instead. */
181 main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
184 struct callback_exim_state *cb_state =
185 (struct callback_exim_state *)gsasl_session_hook_get(sctx);
188 debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n",
189 prop, callback_loop);
191 if (cb_state == NULL) {
192 HDEBUG(D_auth) debug_printf(" not from our server/client processing.\n");
193 return GSASL_NO_CALLBACK;
196 if (callback_loop > 0) {
197 /* Most likely is that we were asked for property foo, and to
198 expand the string we asked for property bar to put into an auth
199 variable, but property bar is not supplied for this mechanism. */
201 debug_printf("Loop, asked for property %d while handling property %d\n",
202 prop, callback_loop);
203 return GSASL_NO_CALLBACK;
205 callback_loop = prop;
207 if (cb_state->currently == CURRENTLY_CLIENT)
208 rc = client_callback(ctx, sctx, prop, cb_state->ablock);
209 else if (cb_state->currently == CURRENTLY_SERVER)
210 rc = server_callback(ctx, sctx, prop, cb_state->ablock);
212 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
213 "unhandled callback state, bug in Exim", cb_state->ablock->name);
222 /*************************************************
223 * Server entry point *
224 *************************************************/
226 /* For interface, see auths/README */
229 auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
232 char *to_send, *received;
233 Gsasl_session *sctx = NULL;
234 auth_gsasl_options_block *ob =
235 (auth_gsasl_options_block *)(ablock->options_block);
236 struct callback_exim_state cb_state;
237 int rc, auth_result, exim_error, exim_error_override;
240 debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n",
241 ablock->name, ob->server_mech);
243 rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx);
244 if (rc != GSASL_OK) {
245 auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
246 gsasl_strerror_name(rc), gsasl_strerror(rc));
247 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
250 /* Hereafter: gsasl_finish(sctx) please */
252 gsasl_session_hook_set(sctx, (void *)ablock);
253 cb_state.ablock = ablock;
254 cb_state.currently = CURRENTLY_SERVER;
255 gsasl_session_hook_set(sctx, (void *)&cb_state);
257 tmps = CS expand_string(ob->server_service);
258 gsasl_property_set(sctx, GSASL_SERVICE, tmps);
259 tmps = CS expand_string(ob->server_hostname);
260 gsasl_property_set(sctx, GSASL_HOSTNAME, tmps);
261 if (ob->server_realm) {
262 tmps = CS expand_string(ob->server_realm);
264 gsasl_property_set(sctx, GSASL_REALM, tmps);
267 /* We don't support protection layers. */
268 gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
270 if (tls_channelbinding_b64) {
271 /* Some auth mechanisms can ensure that both sides are talking withing the
272 same security context; for TLS, this means that even if a bad certificate
273 has been accepted, they remain MitM-proof because both sides must be within
274 the same negotiated session; if someone is terminating one sesson and
275 proxying data on within a second, authentication will fail.
277 We might not have this available, depending upon TLS implementation,
278 ciphersuite, phase of moon ...
280 If we do, it results in extra SASL mechanisms being available; here,
281 Exim's one-mechanism-per-authenticator potentially causes problems.
282 It depends upon how GNU SASL will implement the PLUS variants of GS2
283 and whether it automatically mandates a switch to the bound PLUS
284 if the data is available. Since default-on, despite being more secure,
285 would then result in mechanism name changes on a library update, we
286 have little choice but to default it off and let the admin choose to
289 if (ob->server_channelbinding) {
290 HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
292 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE,
293 (const char *) tls_channelbinding_b64);
296 debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
301 debug_printf("Auth %s: no channel-binding data available\n",
306 checked_server_condition = FALSE;
308 received = CS initial_data;
310 exim_error = exim_error_override = OK;
313 rc = gsasl_step64(sctx, received, &to_send);
318 goto STOP_INTERACTION;
321 case GSASL_NEEDS_MORE:
324 case GSASL_AUTHENTICATION_ERROR:
325 case GSASL_INTEGRITY_ERROR:
326 case GSASL_NO_AUTHID:
327 case GSASL_NO_ANONYMOUS_TOKEN:
328 case GSASL_NO_AUTHZID:
329 case GSASL_NO_PASSWORD:
330 case GSASL_NO_PASSCODE:
332 case GSASL_BASE64_ERROR:
333 HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n",
334 gsasl_strerror_name(rc), gsasl_strerror(rc));
335 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
336 "GNU SASL permanent failure: %s (%s)",
337 ablock->name, ob->server_mech,
338 gsasl_strerror_name(rc), gsasl_strerror(rc));
339 if (rc == GSASL_BASE64_ERROR)
340 exim_error_override = BAD64;
341 goto STOP_INTERACTION;
344 auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)",
345 gsasl_strerror_name(rc), gsasl_strerror(rc));
346 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
347 exim_error_override = DEFER;
348 goto STOP_INTERACTION;
351 if ((rc == GSASL_NEEDS_MORE) ||
352 (to_send && *to_send))
354 auth_get_no64_data((uschar **)&received, (uschar *)to_send);
362 break; /* handles * cancelled check */
364 } while (rc == GSASL_NEEDS_MORE);
371 /* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */
373 if (exim_error != OK)
376 if (auth_result != GSASL_OK) {
377 HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n",
378 gsasl_strerror_name(auth_result), gsasl_strerror(auth_result));
379 if (exim_error_override != OK)
380 return exim_error_override; /* might be DEFER */
381 if (sasl_error_should_defer) /* overriding auth failure SASL error */
386 /* Auth succeeded, check server_condition unless already done in callback */
387 return checked_server_condition ? OK : auth_check_serv_cond(ablock);
390 /* returns the GSASL status of expanding the Exim string given */
392 condition_check(auth_instance *ablock, uschar *label, uschar *condition_string)
396 exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL);
400 } else if (exim_rc == DEFER) {
401 sasl_error_should_defer = TRUE;
402 return GSASL_AUTHENTICATION_ERROR;
403 } else if (exim_rc == FAIL) {
404 return GSASL_AUTHENTICATION_ERROR;
407 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
408 "Unhandled return from checking %s: %d",
409 ablock->name, label, exim_rc);
411 return GSASL_AUTHENTICATION_ERROR;
415 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
419 int cbrc = GSASL_NO_CALLBACK;
421 auth_gsasl_options_block *ob =
422 (auth_gsasl_options_block *)(ablock->options_block);
425 debug_printf("GNU SASL callback %d for %s/%s as server\n",
426 prop, ablock->name, ablock->public_name);
428 for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
432 case GSASL_VALIDATE_SIMPLE:
433 /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
434 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHID);
435 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
436 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
437 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
438 propval = (uschar *) gsasl_property_get(sctx, GSASL_PASSWORD);
439 auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
441 for (i = 1; i <= 3; ++i)
442 expand_nlength[i] = Ustrlen(expand_nstring[i]);
444 cbrc = condition_check(ablock, US"server_condition", ablock->server_condition);
445 checked_server_condition = TRUE;
448 case GSASL_VALIDATE_EXTERNAL:
449 if (ablock->server_condition == NULL) {
450 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL.\n");
451 cbrc = GSASL_AUTHENTICATION_ERROR;
454 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
455 /* We always set $auth1, even if only to empty string. */
456 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
457 expand_nlength[1] = Ustrlen(expand_nstring[1]);
460 cbrc = condition_check(ablock,
461 US"server_condition (EXTERNAL)", ablock->server_condition);
462 checked_server_condition = TRUE;
465 case GSASL_VALIDATE_ANONYMOUS:
466 if (ablock->server_condition == NULL) {
467 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS.\n");
468 cbrc = GSASL_AUTHENTICATION_ERROR;
471 propval = (uschar *) gsasl_property_get(sctx, GSASL_ANONYMOUS_TOKEN);
472 /* We always set $auth1, even if only to empty string. */
473 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
474 expand_nlength[1] = Ustrlen(expand_nstring[1]);
477 cbrc = condition_check(ablock,
478 US"server_condition (ANONYMOUS)", ablock->server_condition);
479 checked_server_condition = TRUE;
482 case GSASL_VALIDATE_GSSAPI:
483 /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME */
484 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
485 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
486 propval = (uschar *) gsasl_property_get(sctx, GSASL_GSSAPI_DISPLAY_NAME);
487 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
489 for (i = 1; i <= 2; ++i)
490 expand_nlength[i] = Ustrlen(expand_nstring[i]);
492 /* In this one case, it perhaps makes sense to default back open?
493 But for consistency, let's just mandate server_condition here too. */
494 cbrc = condition_check(ablock,
495 US"server_condition (GSSAPI family)", ablock->server_condition);
496 checked_server_condition = TRUE;
500 /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM
501 CRAM-MD5: GSASL_AUTHID
502 PLAIN: GSASL_AUTHID and GSASL_AUTHZID
505 if (ob->server_scram_iter) {
506 tmps = CS expand_string(ob->server_scram_iter);
507 gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps);
509 if (ob->server_scram_salt) {
510 tmps = CS expand_string(ob->server_scram_salt);
511 gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps);
513 /* Asking for GSASL_AUTHZID will probably call back into us.
514 Do we really want to hardcode limits per mechanism? What happens when
515 a new mechanism is added to the library. It *shouldn't* result in us
516 needing to add more glue, since avoiding that is a large part of the
518 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHID);
519 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
520 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
521 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
522 propval = (uschar *) gsasl_property_get(sctx, GSASL_REALM);
523 auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
525 for (i = 1; i <= 3; ++i)
526 expand_nlength[i] = Ustrlen(expand_nstring[i]);
528 tmps = CS expand_string(ob->server_password);
530 sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE;
531 HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
532 "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
533 return GSASL_AUTHENTICATION_ERROR;
535 gsasl_property_set(sctx, GSASL_PASSWORD, tmps);
536 /* This is inadequate; don't think Exim's store stacks are geared
537 for memory wiping, so expanding strings will leave stuff laying around.
538 But no need to compound the problem, so get rid of the one we can. */
539 memset(tmps, '\0', strlen(tmps));
544 HDEBUG(D_auth) debug_printf("Unrecognised callback: %d\n", prop);
545 cbrc = GSASL_NO_CALLBACK;
548 HDEBUG(D_auth) debug_printf("Returning %s (%s)\n",
549 gsasl_strerror_name(cbrc), gsasl_strerror(cbrc));
555 /*************************************************
556 * Client entry point *
557 *************************************************/
559 /* For interface, see auths/README */
563 auth_instance *ablock, /* authenticator block */
564 smtp_inblock *inblock, /* connection inblock */
565 smtp_outblock *outblock, /* connection outblock */
566 int timeout, /* command timeout */
567 uschar *buffer, /* buffer for reading response */
568 int buffsize) /* size of buffer */
571 debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
572 /* NOT IMPLEMENTED */
577 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
579 int cbrc = GSASL_NO_CALLBACK;
581 debug_printf("GNU SASL callback %d for %s/%s as client\n",
582 prop, ablock->name, ablock->public_name);
585 debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
590 /*************************************************
592 *************************************************/
595 auth_gsasl_version_report(FILE *f)
598 runtime = gsasl_check_version(NULL);
599 fprintf(f, "Library version: GNU SASL: Compile: %s\n"
601 GSASL_VERSION, runtime);
604 #endif /* AUTH_GSASL */
606 /* End of gsasl_exim.c */