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
9 Author: Phil Pennock <pdp@exim.org> */
10 /* Copyright (c) Phil Pennock 2012 */
12 /* Interface to GNU SASL library for generic authentication. */
16 GNU SASL does not provide authentication data itself, so we have to expose
17 that decision to configuration. For some mechanisms, we need to act much
18 like plaintext. For others, we only need to be able to provide some
19 evaluated data on demand. There's no abstracted way (ie, without hardcoding
20 knowledge of authenticators here) to know which need what properties; we
21 can't query a session or the library for "we will need these for mechanism X".
23 So: we always require server_condition, even if sometimes it will just be
24 set as "yes". We do provide a number of other hooks, which might not make
25 sense in all contexts. For some, we can do checks at init time.
31 /* dummy function to satisfy compilers when we link in an "empty" file. */
32 static void dummy(int x);
33 static void dummy2(int x) { dummy(x-1); }
34 static void dummy(int x) { dummy2(x-1); }
38 #include "gsasl_exim.h"
40 /* Authenticator-specific options. */
41 /* I did have server_*_condition options for various mechanisms, but since
42 we only ever handle one mechanism at a time, I didn't see the point in keeping
43 that. In case someone sees a point, I've left the condition_check() API
45 optionlist auth_gsasl_options[] = {
46 { "server_channelbinding", opt_bool,
47 (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) },
48 { "server_hostname", opt_stringptr,
49 (void *)(offsetof(auth_gsasl_options_block, server_hostname)) },
50 { "server_mech", opt_stringptr,
51 (void *)(offsetof(auth_gsasl_options_block, server_mech)) },
52 { "server_password", opt_stringptr,
53 (void *)(offsetof(auth_gsasl_options_block, server_password)) },
54 { "server_realm", opt_stringptr,
55 (void *)(offsetof(auth_gsasl_options_block, server_realm)) },
56 { "server_scram_iter", opt_stringptr,
57 (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) },
58 { "server_scram_salt", opt_stringptr,
59 (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) },
60 { "server_service", opt_stringptr,
61 (void *)(offsetof(auth_gsasl_options_block, server_service)) }
63 /* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing
64 hooks to avoid cleartext passwords in the Exim server. */
66 int auth_gsasl_options_count =
67 sizeof(auth_gsasl_options)/sizeof(optionlist);
69 /* Defaults for the authenticator-specific options. */
70 auth_gsasl_options_block auth_gsasl_option_defaults = {
71 US"smtp", /* server_service */
72 US"$primary_hostname", /* server_hostname */
73 NULL, /* server_realm */
74 NULL, /* server_mech */
75 NULL, /* server_password */
76 NULL, /* server_scram_iter */
77 NULL, /* server_scram_salt */
78 FALSE /* server_channelbinding */
81 /* "Globals" for managing the gsasl interface. */
83 static Gsasl *gsasl_ctx = NULL;
85 main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop);
87 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
89 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
91 static BOOL sasl_error_should_defer = FALSE;
92 static Gsasl_property callback_loop = 0;
93 static BOOL checked_server_condition = FALSE;
95 enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 };
97 struct callback_exim_state {
98 auth_instance *ablock;
103 /*************************************************
104 * Initialization entry point *
105 *************************************************/
107 /* Called for each instance, after its options have been read, to
108 enable consistency checks to be done, or anything else that needs
112 auth_gsasl_init(auth_instance *ablock)
116 auth_gsasl_options_block *ob =
117 (auth_gsasl_options_block *)(ablock->options_block);
119 /* As per existing Cyrus glue, use the authenticator's public name as
120 the default for the mechanism name; we don't handle multiple mechanisms
121 in one authenticator, but the same driver can be used multiple times. */
123 if (ob->server_mech == NULL)
124 ob->server_mech = string_copy(ablock->public_name);
126 /* Can get multiple session contexts from one library context, so just
127 initialise the once. */
128 if (gsasl_ctx == NULL) {
129 rc = gsasl_init(&gsasl_ctx);
130 if (rc != GSASL_OK) {
131 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
132 "couldn't initialise GNU SASL library: %s (%s)",
133 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
135 gsasl_callback_set(gsasl_ctx, main_callback);
138 /* We don't need this except to log it for debugging. */
139 rc = gsasl_server_mechlist(gsasl_ctx, &p);
141 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
142 "failed to retrieve list of mechanisms: %s (%s)",
143 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
144 HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p);
146 supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech);
148 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
149 "GNU SASL does not support mechanism \"%s\"",
150 ablock->name, ob->server_mech);
152 if ((ablock->server_condition == NULL) &&
153 (streqic(ob->server_mech, US"EXTERNAL") ||
154 streqic(ob->server_mech, US"ANONYMOUS") ||
155 streqic(ob->server_mech, US"PLAIN") ||
156 streqic(ob->server_mech, US"LOGIN")))
157 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
158 "Need server_condition for %s mechanism",
159 ablock->name, ob->server_mech);
161 /* This does *not* scale to new SASL mechanisms. Need a better way to ask
162 which properties will be needed. */
163 if ((ob->server_realm == NULL) &&
164 streqic(ob->server_mech, US"DIGEST-MD5"))
165 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
166 "Need server_realm for %s mechanism",
167 ablock->name, ob->server_mech);
169 /* At present, for mechanisms we don't panic on absence of server_condition;
170 need to figure out the most generically correct approach to deciding when
171 it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism,
172 etc) it clearly is critical.
174 So don't activate without server_condition, this might be relaxed in the future.
176 if (ablock->server_condition != NULL) ablock->server = TRUE;
177 ablock->client = FALSE;
181 /* GNU SASL uses one top-level callback, registered at library level.
182 We dispatch to client and server functions instead. */
185 main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
188 struct callback_exim_state *cb_state =
189 (struct callback_exim_state *)gsasl_session_hook_get(sctx);
192 debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n",
193 prop, callback_loop);
195 if (cb_state == NULL) {
196 HDEBUG(D_auth) debug_printf(" not from our server/client processing.\n");
197 return GSASL_NO_CALLBACK;
200 if (callback_loop > 0) {
201 /* Most likely is that we were asked for property foo, and to
202 expand the string we asked for property bar to put into an auth
203 variable, but property bar is not supplied for this mechanism. */
205 debug_printf("Loop, asked for property %d while handling property %d\n",
206 prop, callback_loop);
207 return GSASL_NO_CALLBACK;
209 callback_loop = prop;
211 if (cb_state->currently == CURRENTLY_CLIENT)
212 rc = client_callback(ctx, sctx, prop, cb_state->ablock);
213 else if (cb_state->currently == CURRENTLY_SERVER)
214 rc = server_callback(ctx, sctx, prop, cb_state->ablock);
216 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
217 "unhandled callback state, bug in Exim", cb_state->ablock->name);
226 /*************************************************
227 * Server entry point *
228 *************************************************/
230 /* For interface, see auths/README */
233 auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
236 char *to_send, *received;
237 Gsasl_session *sctx = NULL;
238 auth_gsasl_options_block *ob =
239 (auth_gsasl_options_block *)(ablock->options_block);
240 struct callback_exim_state cb_state;
241 int rc, auth_result, exim_error, exim_error_override;
244 debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n",
245 ablock->name, ob->server_mech);
247 rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx);
248 if (rc != GSASL_OK) {
249 auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
250 gsasl_strerror_name(rc), gsasl_strerror(rc));
251 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
254 /* Hereafter: gsasl_finish(sctx) please */
256 gsasl_session_hook_set(sctx, (void *)ablock);
257 cb_state.ablock = ablock;
258 cb_state.currently = CURRENTLY_SERVER;
259 gsasl_session_hook_set(sctx, (void *)&cb_state);
261 tmps = CS expand_string(ob->server_service);
262 gsasl_property_set(sctx, GSASL_SERVICE, tmps);
263 tmps = CS expand_string(ob->server_hostname);
264 gsasl_property_set(sctx, GSASL_HOSTNAME, tmps);
265 if (ob->server_realm) {
266 tmps = CS expand_string(ob->server_realm);
268 gsasl_property_set(sctx, GSASL_REALM, tmps);
271 /* We don't support protection layers. */
272 gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
274 if (tls_channelbinding_b64) {
275 /* Some auth mechanisms can ensure that both sides are talking withing the
276 same security context; for TLS, this means that even if a bad certificate
277 has been accepted, they remain MitM-proof because both sides must be within
278 the same negotiated session; if someone is terminating one session and
279 proxying data on within a second, authentication will fail.
281 We might not have this available, depending upon TLS implementation,
282 ciphersuite, phase of moon ...
284 If we do, it results in extra SASL mechanisms being available; here,
285 Exim's one-mechanism-per-authenticator potentially causes problems.
286 It depends upon how GNU SASL will implement the PLUS variants of GS2
287 and whether it automatically mandates a switch to the bound PLUS
288 if the data is available. Since default-on, despite being more secure,
289 would then result in mechanism name changes on a library update, we
290 have little choice but to default it off and let the admin choose to
293 if (ob->server_channelbinding) {
294 HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
296 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE,
297 (const char *) tls_channelbinding_b64);
300 debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
305 debug_printf("Auth %s: no channel-binding data available\n",
310 checked_server_condition = FALSE;
312 received = CS initial_data;
314 exim_error = exim_error_override = OK;
317 rc = gsasl_step64(sctx, received, &to_send);
322 goto STOP_INTERACTION;
325 case GSASL_NEEDS_MORE:
328 case GSASL_AUTHENTICATION_ERROR:
329 case GSASL_INTEGRITY_ERROR:
330 case GSASL_NO_AUTHID:
331 case GSASL_NO_ANONYMOUS_TOKEN:
332 case GSASL_NO_AUTHZID:
333 case GSASL_NO_PASSWORD:
334 case GSASL_NO_PASSCODE:
336 case GSASL_BASE64_ERROR:
337 HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n",
338 gsasl_strerror_name(rc), gsasl_strerror(rc));
339 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
340 "GNU SASL permanent failure: %s (%s)",
341 ablock->name, ob->server_mech,
342 gsasl_strerror_name(rc), gsasl_strerror(rc));
343 if (rc == GSASL_BASE64_ERROR)
344 exim_error_override = BAD64;
345 goto STOP_INTERACTION;
348 auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)",
349 gsasl_strerror_name(rc), gsasl_strerror(rc));
350 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
351 exim_error_override = DEFER;
352 goto STOP_INTERACTION;
355 if ((rc == GSASL_NEEDS_MORE) ||
356 (to_send && *to_send))
358 auth_get_no64_data((uschar **)&received, (uschar *)to_send);
366 break; /* handles * cancelled check */
368 } while (rc == GSASL_NEEDS_MORE);
375 /* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */
377 if (exim_error != OK)
380 if (auth_result != GSASL_OK) {
381 HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n",
382 gsasl_strerror_name(auth_result), gsasl_strerror(auth_result));
383 if (exim_error_override != OK)
384 return exim_error_override; /* might be DEFER */
385 if (sasl_error_should_defer) /* overriding auth failure SASL error */
390 /* Auth succeeded, check server_condition unless already done in callback */
391 return checked_server_condition ? OK : auth_check_serv_cond(ablock);
394 /* returns the GSASL status of expanding the Exim string given */
396 condition_check(auth_instance *ablock, uschar *label, uschar *condition_string)
400 exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL);
404 } else if (exim_rc == DEFER) {
405 sasl_error_should_defer = TRUE;
406 return GSASL_AUTHENTICATION_ERROR;
407 } else if (exim_rc == FAIL) {
408 return GSASL_AUTHENTICATION_ERROR;
411 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
412 "Unhandled return from checking %s: %d",
413 ablock->name, label, exim_rc);
415 return GSASL_AUTHENTICATION_ERROR;
419 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
423 int cbrc = GSASL_NO_CALLBACK;
425 auth_gsasl_options_block *ob =
426 (auth_gsasl_options_block *)(ablock->options_block);
429 debug_printf("GNU SASL callback %d for %s/%s as server\n",
430 prop, ablock->name, ablock->public_name);
432 for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
436 case GSASL_VALIDATE_SIMPLE:
437 /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
438 propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID);
439 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
440 propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
441 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
442 propval = (uschar *) gsasl_property_fast(sctx, GSASL_PASSWORD);
443 auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
445 for (i = 1; i <= 3; ++i)
446 expand_nlength[i] = Ustrlen(expand_nstring[i]);
448 cbrc = condition_check(ablock, US"server_condition", ablock->server_condition);
449 checked_server_condition = TRUE;
452 case GSASL_VALIDATE_EXTERNAL:
453 if (ablock->server_condition == NULL) {
454 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL.\n");
455 cbrc = GSASL_AUTHENTICATION_ERROR;
458 propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
459 /* We always set $auth1, even if only to empty string. */
460 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
461 expand_nlength[1] = Ustrlen(expand_nstring[1]);
464 cbrc = condition_check(ablock,
465 US"server_condition (EXTERNAL)", ablock->server_condition);
466 checked_server_condition = TRUE;
469 case GSASL_VALIDATE_ANONYMOUS:
470 if (ablock->server_condition == NULL) {
471 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS.\n");
472 cbrc = GSASL_AUTHENTICATION_ERROR;
475 propval = (uschar *) gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN);
476 /* We always set $auth1, even if only to empty string. */
477 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
478 expand_nlength[1] = Ustrlen(expand_nstring[1]);
481 cbrc = condition_check(ablock,
482 US"server_condition (ANONYMOUS)", ablock->server_condition);
483 checked_server_condition = TRUE;
486 case GSASL_VALIDATE_GSSAPI:
487 /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME
488 The display-name is authenticated as part of GSS, the authzid is claimed
489 by the SASL integration after authentication; protected against tampering
490 (if the SASL mechanism supports that, which Kerberos does) but is
491 unverified, same as normal for other mechanisms.
493 First coding, we had these values swapped, but for consistency and prior
494 to the first release of Exim with this authenticator, they've been
495 switched to match the ordering of GSASL_VALIDATE_SIMPLE. */
496 propval = (uschar *) gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
497 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
498 propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
499 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
501 for (i = 1; i <= 2; ++i)
502 expand_nlength[i] = Ustrlen(expand_nstring[i]);
504 /* In this one case, it perhaps makes sense to default back open?
505 But for consistency, let's just mandate server_condition here too. */
506 cbrc = condition_check(ablock,
507 US"server_condition (GSSAPI family)", ablock->server_condition);
508 checked_server_condition = TRUE;
512 /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM
513 CRAM-MD5: GSASL_AUTHID
514 PLAIN: GSASL_AUTHID and GSASL_AUTHZID
517 if (ob->server_scram_iter) {
518 tmps = CS expand_string(ob->server_scram_iter);
519 gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps);
521 if (ob->server_scram_salt) {
522 tmps = CS expand_string(ob->server_scram_salt);
523 gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps);
525 /* Asking for GSASL_AUTHZID calls back into us if we use
526 gsasl_property_get(), thus the use of gsasl_property_fast().
527 Do we really want to hardcode limits per mechanism? What happens when
528 a new mechanism is added to the library. It *shouldn't* result in us
529 needing to add more glue, since avoiding that is a large part of the
531 propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID);
532 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
533 propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
534 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
535 propval = (uschar *) gsasl_property_fast(sctx, GSASL_REALM);
536 auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
538 for (i = 1; i <= 3; ++i)
539 expand_nlength[i] = Ustrlen(expand_nstring[i]);
541 tmps = CS expand_string(ob->server_password);
543 sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE;
544 HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
545 "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
546 return GSASL_AUTHENTICATION_ERROR;
548 gsasl_property_set(sctx, GSASL_PASSWORD, tmps);
549 /* This is inadequate; don't think Exim's store stacks are geared
550 for memory wiping, so expanding strings will leave stuff laying around.
551 But no need to compound the problem, so get rid of the one we can. */
552 memset(tmps, '\0', strlen(tmps));
557 HDEBUG(D_auth) debug_printf("Unrecognised callback: %d\n", prop);
558 cbrc = GSASL_NO_CALLBACK;
561 HDEBUG(D_auth) debug_printf("Returning %s (%s)\n",
562 gsasl_strerror_name(cbrc), gsasl_strerror(cbrc));
568 /*************************************************
569 * Client entry point *
570 *************************************************/
572 /* For interface, see auths/README */
576 auth_instance *ablock, /* authenticator block */
577 smtp_inblock *inblock, /* connection inblock */
578 smtp_outblock *outblock, /* connection outblock */
579 int timeout, /* command timeout */
580 uschar *buffer, /* buffer for reading response */
581 int buffsize) /* size of buffer */
584 debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
585 /* NOT IMPLEMENTED */
590 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
592 int cbrc = GSASL_NO_CALLBACK;
594 debug_printf("GNU SASL callback %d for %s/%s as client\n",
595 prop, ablock->name, ablock->public_name);
598 debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
603 /*************************************************
605 *************************************************/
608 auth_gsasl_version_report(FILE *f)
611 runtime = gsasl_check_version(NULL);
612 fprintf(f, "Library version: GNU SASL: Compile: %s\n"
614 GSASL_VERSION, runtime);
617 #endif /* AUTH_GSASL */
619 /* End of gsasl_exim.c */