1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2019 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
9 /* Copyright (c) Twitter Inc 2012
10 Author: Phil Pennock <pdp@exim.org> */
11 /* Copyright (c) Phil Pennock 2012 */
13 /* Interface to GNU SASL library for generic authentication. */
17 GNU SASL does not provide authentication data itself, so we have to expose
18 that decision to configuration. For some mechanisms, we need to act much
19 like plaintext. For others, we only need to be able to provide some
20 evaluated data on demand. There's no abstracted way (ie, without hardcoding
21 knowledge of authenticators here) to know which need what properties; we
22 can't query a session or the library for "we will need these for mechanism X".
24 So: we always require server_condition, even if sometimes it will just be
25 set as "yes". We do provide a number of other hooks, which might not make
26 sense in all contexts. For some, we can do checks at init time.
30 #define CHANNELBIND_HACK
33 /* dummy function to satisfy compilers when we link in an "empty" file. */
34 static void dummy(int x);
35 static void dummy2(int x) { dummy(x-1); }
36 static void dummy(int x) { dummy2(x-1); }
40 #include "gsasl_exim.h"
43 # include <stringprep.h>
47 #if GSASL_VERSION_MINOR >= 9
48 # define EXIM_GSASL_HAVE_SCRAM_SHA_256
52 /* Authenticator-specific options. */
53 /* I did have server_*_condition options for various mechanisms, but since
54 we only ever handle one mechanism at a time, I didn't see the point in keeping
55 that. In case someone sees a point, I've left the condition_check() API
57 optionlist auth_gsasl_options[] = {
58 { "client_authz", opt_stringptr,
59 (void *)(offsetof(auth_gsasl_options_block, client_authz)) },
60 { "client_channelbinding", opt_bool,
61 (void *)(offsetof(auth_gsasl_options_block, client_channelbinding)) },
62 { "client_password", opt_stringptr,
63 (void *)(offsetof(auth_gsasl_options_block, client_password)) },
64 { "client_username", opt_stringptr,
65 (void *)(offsetof(auth_gsasl_options_block, client_username)) },
67 { "server_channelbinding", opt_bool,
68 (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) },
69 { "server_hostname", opt_stringptr,
70 (void *)(offsetof(auth_gsasl_options_block, server_hostname)) },
71 { "server_mech", opt_stringptr,
72 (void *)(offsetof(auth_gsasl_options_block, server_mech)) },
73 { "server_password", opt_stringptr,
74 (void *)(offsetof(auth_gsasl_options_block, server_password)) },
75 { "server_realm", opt_stringptr,
76 (void *)(offsetof(auth_gsasl_options_block, server_realm)) },
77 { "server_scram_iter", opt_stringptr,
78 (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) },
79 { "server_scram_salt", opt_stringptr,
80 (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) },
81 { "server_service", opt_stringptr,
82 (void *)(offsetof(auth_gsasl_options_block, server_service)) }
84 /* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing
85 hooks to avoid cleartext passwords in the Exim server. */
87 int auth_gsasl_options_count =
88 sizeof(auth_gsasl_options)/sizeof(optionlist);
90 /* Defaults for the authenticator-specific options. */
91 auth_gsasl_options_block auth_gsasl_option_defaults = {
92 .server_service = US"smtp",
93 .server_hostname = US"$primary_hostname",
94 .server_scram_iter = US"4096",
95 /* all others zero/null */
102 void auth_gsasl_init(auth_instance *ablock) {}
103 int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;}
104 int auth_gsasl_client(auth_instance *ablock, void * sx,
105 int timeout, uschar *buffer, int buffsize) {return 0;}
106 void auth_gsasl_version_report(FILE *f) {}
109 auth_gsasl_macros(void)
111 # ifdef EXIM_GSASL_HAVE_SCRAM_SHA_256
112 builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_SHA_256");
116 #else /*!MACRO_PREDEF*/
120 /* "Globals" for managing the gsasl interface. */
122 static Gsasl *gsasl_ctx = NULL;
124 main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop);
126 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
128 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
130 static BOOL sasl_error_should_defer = FALSE;
131 static Gsasl_property callback_loop = 0;
132 static BOOL checked_server_condition = FALSE;
134 enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 };
136 struct callback_exim_state {
137 auth_instance *ablock;
142 /*************************************************
143 * Initialization entry point *
144 *************************************************/
146 /* Called for each instance, after its options have been read, to
147 enable consistency checks to be done, or anything else that needs
151 auth_gsasl_init(auth_instance *ablock)
153 static char * once = NULL;
155 auth_gsasl_options_block *ob =
156 (auth_gsasl_options_block *)(ablock->options_block);
158 /* As per existing Cyrus glue, use the authenticator's public name as
159 the default for the mechanism name; we don't handle multiple mechanisms
160 in one authenticator, but the same driver can be used multiple times. */
162 if (!ob->server_mech)
163 ob->server_mech = string_copy(ablock->public_name);
165 /* Can get multiple session contexts from one library context, so just
166 initialise the once. */
170 if ((rc = gsasl_init(&gsasl_ctx)) != GSASL_OK)
171 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
172 "couldn't initialise GNU SASL library: %s (%s)",
173 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
175 gsasl_callback_set(gsasl_ctx, main_callback);
178 /* We don't need this except to log it for debugging. */
180 HDEBUG(D_auth) if (!once)
182 if ((rc = gsasl_server_mechlist(gsasl_ctx, &once)) != GSASL_OK)
183 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
184 "failed to retrieve list of mechanisms: %s (%s)",
185 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
187 debug_printf("GNU SASL supports: %s\n", once);
190 if (!gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech))
191 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
192 "GNU SASL does not support mechanism \"%s\"",
193 ablock->name, ob->server_mech);
195 ablock->server = TRUE;
197 if ( !ablock->server_condition
198 && ( streqic(ob->server_mech, US"EXTERNAL")
199 || streqic(ob->server_mech, US"ANONYMOUS")
200 || streqic(ob->server_mech, US"PLAIN")
201 || streqic(ob->server_mech, US"LOGIN")
204 ablock->server = FALSE;
205 HDEBUG(D_auth) debug_printf("%s authenticator: "
206 "Need server_condition for %s mechanism\n",
207 ablock->name, ob->server_mech);
210 /* This does *not* scale to new SASL mechanisms. Need a better way to ask
211 which properties will be needed. */
213 if ( !ob->server_realm
214 && streqic(ob->server_mech, US"DIGEST-MD5"))
216 ablock->server = FALSE;
217 HDEBUG(D_auth) debug_printf("%s authenticator: "
218 "Need server_realm for %s mechanism\n",
219 ablock->name, ob->server_mech);
222 /* At present, for mechanisms we don't panic on absence of server_condition;
223 need to figure out the most generically correct approach to deciding when
224 it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism,
225 etc) it clearly is critical.
228 ablock->client = ob->client_username && ob->client_password;
232 /* GNU SASL uses one top-level callback, registered at library level.
233 We dispatch to client and server functions instead. */
236 main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
239 struct callback_exim_state *cb_state =
240 (struct callback_exim_state *)gsasl_session_hook_get(sctx);
244 HDEBUG(D_auth) debug_printf("gsasl callback (%d) not from our server/client processing\n", prop);
245 #ifdef CHANNELBIND_HACK
246 if (prop == GSASL_CB_TLS_UNIQUE)
249 if ((s = gsasl_callback_hook_get(ctx)))
251 HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n");
252 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CS s);
256 HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE! dummy for now\n");
257 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, "");
262 return GSASL_NO_CALLBACK;
266 debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n",
267 prop, callback_loop);
269 if (callback_loop > 0)
271 /* Most likely is that we were asked for property FOO, and to
272 expand the string we asked for property BAR to put into an auth
273 variable, but property BAR is not supplied for this mechanism. */
275 debug_printf("Loop, asked for property %d while handling property %d\n",
276 prop, callback_loop);
277 return GSASL_NO_CALLBACK;
279 callback_loop = prop;
281 if (cb_state->currently == CURRENTLY_CLIENT)
282 rc = client_callback(ctx, sctx, prop, cb_state->ablock);
283 else if (cb_state->currently == CURRENTLY_SERVER)
284 rc = server_callback(ctx, sctx, prop, cb_state->ablock);
286 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
287 "unhandled callback state, bug in Exim", cb_state->ablock->name);
295 /*************************************************
296 * Server entry point *
297 *************************************************/
299 /* For interface, see auths/README */
302 auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
305 char *to_send, *received;
306 Gsasl_session *sctx = NULL;
307 auth_gsasl_options_block *ob =
308 (auth_gsasl_options_block *)(ablock->options_block);
309 struct callback_exim_state cb_state;
310 int rc, auth_result, exim_error, exim_error_override;
313 debug_printf("GNU SASL: initialising session for %s, mechanism %s\n",
314 ablock->name, ob->server_mech);
317 if (tls_in.channelbinding && ob->server_channelbinding)
319 # ifdef EXPERIMENTAL_TLS_RESUME
320 if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED)
321 { /* per RFC 7677 section 4 */
322 HDEBUG(D_auth) debug_printf(
323 "channel binding not usable on resumed TLS without extended-master-secret");
327 # ifdef CHANNELBIND_HACK
328 /* This is a gross hack to get around the library a) requiring that
329 c-b was already set, at the _start() call, and b) caching a b64'd
330 version of the binding then which it never updates. */
332 gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding);
337 if ((rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK)
339 auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
340 gsasl_strerror_name(rc), gsasl_strerror(rc));
341 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
344 /* Hereafter: gsasl_finish(sctx) please */
346 cb_state.ablock = ablock;
347 cb_state.currently = CURRENTLY_SERVER;
348 gsasl_session_hook_set(sctx, &cb_state);
350 tmps = CS expand_string(ob->server_service);
351 gsasl_property_set(sctx, GSASL_SERVICE, tmps);
352 tmps = CS expand_string(ob->server_hostname);
353 gsasl_property_set(sctx, GSASL_HOSTNAME, tmps);
354 if (ob->server_realm)
356 tmps = CS expand_string(ob->server_realm);
358 gsasl_property_set(sctx, GSASL_REALM, tmps);
360 /* We don't support protection layers. */
361 gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
364 if (tls_in.channelbinding)
366 /* Some auth mechanisms can ensure that both sides are talking withing the
367 same security context; for TLS, this means that even if a bad certificate
368 has been accepted, they remain MitM-proof because both sides must be within
369 the same negotiated session; if someone is terminating one session and
370 proxying data on within a second, authentication will fail.
372 We might not have this available, depending upon TLS implementation,
373 ciphersuite, phase of moon ...
375 If we do, it results in extra SASL mechanisms being available; here,
376 Exim's one-mechanism-per-authenticator potentially causes problems.
377 It depends upon how GNU SASL will implement the PLUS variants of GS2
378 and whether it automatically mandates a switch to the bound PLUS
379 if the data is available. Since default-on, despite being more secure,
380 would then result in mechanism name changes on a library update, we
381 have little choice but to default it off and let the admin choose to
384 if (ob->server_channelbinding)
386 HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
388 # ifndef CHANNELBIND_HACK
389 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_in.channelbinding);
394 debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
399 debug_printf("Auth %s: no channel-binding data available\n",
403 checked_server_condition = FALSE;
405 received = CS initial_data;
407 exim_error = exim_error_override = OK;
410 switch (rc = gsasl_step64(sctx, received, &to_send))
414 goto STOP_INTERACTION;
417 case GSASL_NEEDS_MORE:
420 case GSASL_AUTHENTICATION_ERROR:
421 case GSASL_INTEGRITY_ERROR:
422 case GSASL_NO_AUTHID:
423 case GSASL_NO_ANONYMOUS_TOKEN:
424 case GSASL_NO_AUTHZID:
425 case GSASL_NO_PASSWORD:
426 case GSASL_NO_PASSCODE:
428 case GSASL_BASE64_ERROR:
429 HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n",
430 gsasl_strerror_name(rc), gsasl_strerror(rc));
431 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
432 "GNU SASL permanent failure: %s (%s)",
433 ablock->name, ob->server_mech,
434 gsasl_strerror_name(rc), gsasl_strerror(rc));
435 if (rc == GSASL_BASE64_ERROR)
436 exim_error_override = BAD64;
437 goto STOP_INTERACTION;
440 auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)",
441 gsasl_strerror_name(rc), gsasl_strerror(rc));
442 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
443 exim_error_override = DEFER;
444 goto STOP_INTERACTION;
447 if ((rc == GSASL_NEEDS_MORE) || (to_send && *to_send))
448 exim_error = auth_get_no64_data(USS &received, US to_send);
457 break; /* handles * cancelled check */
459 } while (rc == GSASL_NEEDS_MORE);
466 /* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */
468 if (exim_error != OK)
471 if (auth_result != GSASL_OK)
473 HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n",
474 gsasl_strerror_name(auth_result), gsasl_strerror(auth_result));
475 if (exim_error_override != OK)
476 return exim_error_override; /* might be DEFER */
477 if (sasl_error_should_defer) /* overriding auth failure SASL error */
482 /* Auth succeeded, check server_condition unless already done in callback */
483 return checked_server_condition ? OK : auth_check_serv_cond(ablock);
487 /* returns the GSASL status of expanding the Exim string given */
489 condition_check(auth_instance *ablock, uschar *label, uschar *condition_string)
491 int exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL);
494 case OK: return GSASL_OK;
495 case DEFER: sasl_error_should_defer = TRUE;
496 return GSASL_AUTHENTICATION_ERROR;
497 case FAIL: return GSASL_AUTHENTICATION_ERROR;
498 default: log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
499 "Unhandled return from checking %s: %d",
500 ablock->name, label, exim_rc);
504 return GSASL_AUTHENTICATION_ERROR;
508 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop,
509 auth_instance *ablock)
513 int cbrc = GSASL_NO_CALLBACK;
514 auth_gsasl_options_block *ob =
515 (auth_gsasl_options_block *)(ablock->options_block);
518 debug_printf("GNU SASL callback %d for %s/%s as server\n",
519 prop, ablock->name, ablock->public_name);
521 for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
526 case GSASL_VALIDATE_SIMPLE:
527 HDEBUG(D_auth) debug_printf(" VALIDATE_SIMPLE\n");
528 /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
529 propval = US gsasl_property_fast(sctx, GSASL_AUTHID);
530 auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
531 propval = US gsasl_property_fast(sctx, GSASL_AUTHZID);
532 auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US"";
533 propval = US gsasl_property_fast(sctx, GSASL_PASSWORD);
534 auth_vars[2] = expand_nstring[3] = propval ? string_copy(propval) : US"";
536 for (int i = 1; i <= 3; ++i)
537 expand_nlength[i] = Ustrlen(expand_nstring[i]);
539 cbrc = condition_check(ablock, US"server_condition", ablock->server_condition);
540 checked_server_condition = TRUE;
543 case GSASL_VALIDATE_EXTERNAL:
544 HDEBUG(D_auth) debug_printf(" VALIDATE_EXTERNAL\n");
545 if (!ablock->server_condition)
547 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL\n");
548 cbrc = GSASL_AUTHENTICATION_ERROR;
551 propval = US gsasl_property_fast(sctx, GSASL_AUTHZID);
553 /* We always set $auth1, even if only to empty string. */
554 auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
555 expand_nlength[1] = Ustrlen(expand_nstring[1]);
558 cbrc = condition_check(ablock,
559 US"server_condition (EXTERNAL)", ablock->server_condition);
560 checked_server_condition = TRUE;
563 case GSASL_VALIDATE_ANONYMOUS:
564 HDEBUG(D_auth) debug_printf(" VALIDATE_ANONYMOUS\n");
565 if (!ablock->server_condition)
567 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS\n");
568 cbrc = GSASL_AUTHENTICATION_ERROR;
571 propval = US gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN);
573 /* We always set $auth1, even if only to empty string. */
575 auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
576 expand_nlength[1] = Ustrlen(expand_nstring[1]);
579 cbrc = condition_check(ablock,
580 US"server_condition (ANONYMOUS)", ablock->server_condition);
581 checked_server_condition = TRUE;
584 case GSASL_VALIDATE_GSSAPI:
585 HDEBUG(D_auth) debug_printf(" VALIDATE_GSSAPI\n");
586 /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME
587 The display-name is authenticated as part of GSS, the authzid is claimed
588 by the SASL integration after authentication; protected against tampering
589 (if the SASL mechanism supports that, which Kerberos does) but is
590 unverified, same as normal for other mechanisms.
591 First coding, we had these values swapped, but for consistency and prior
592 to the first release of Exim with this authenticator, they've been
593 switched to match the ordering of GSASL_VALIDATE_SIMPLE. */
595 propval = US gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
596 auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
597 propval = US gsasl_property_fast(sctx, GSASL_AUTHZID);
598 auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US"";
600 for (int i = 1; i <= 2; ++i)
601 expand_nlength[i] = Ustrlen(expand_nstring[i]);
603 /* In this one case, it perhaps makes sense to default back open?
604 But for consistency, let's just mandate server_condition here too. */
606 cbrc = condition_check(ablock,
607 US"server_condition (GSSAPI family)", ablock->server_condition);
608 checked_server_condition = TRUE;
611 case GSASL_SCRAM_ITER:
612 HDEBUG(D_auth) debug_printf(" SCRAM_ITER\n");
613 if (ob->server_scram_iter)
615 tmps = CS expand_string(ob->server_scram_iter);
616 gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps);
621 case GSASL_SCRAM_SALT:
622 HDEBUG(D_auth) debug_printf(" SCRAM_SALT\n");
623 if (ob->server_scram_iter)
625 tmps = CS expand_string(ob->server_scram_salt);
626 gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps);
632 HDEBUG(D_auth) debug_printf(" PASSWORD\n");
633 /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM
634 CRAM-MD5: GSASL_AUTHID
635 PLAIN: GSASL_AUTHID and GSASL_AUTHZID
638 if (ob->server_scram_iter)
640 tmps = CS expand_string(ob->server_scram_iter);
641 gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps);
643 if (ob->server_scram_salt)
645 tmps = CS expand_string(ob->server_scram_salt);
646 gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps);
649 /* Asking for GSASL_AUTHZID calls back into us if we use
650 gsasl_property_get(), thus the use of gsasl_property_fast().
651 Do we really want to hardcode limits per mechanism? What happens when
652 a new mechanism is added to the library. It *shouldn't* result in us
653 needing to add more glue, since avoiding that is a large part of the
656 propval = US gsasl_property_fast(sctx, GSASL_AUTHID);
657 auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
658 propval = US gsasl_property_fast(sctx, GSASL_AUTHZID);
659 auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US"";
660 propval = US gsasl_property_fast(sctx, GSASL_REALM);
661 auth_vars[2] = expand_nstring[3] = propval ? string_copy(propval) : US"";
663 for (int i = 1; i <= 3; ++i)
664 expand_nlength[i] = Ustrlen(expand_nstring[i]);
666 if (!(tmps = CS expand_string(ob->server_password)))
668 sasl_error_should_defer = f.expand_string_forcedfail ? FALSE : TRUE;
669 HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
670 "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
671 return GSASL_AUTHENTICATION_ERROR;
673 gsasl_property_set(sctx, GSASL_PASSWORD, tmps);
675 /* This is inadequate; don't think Exim's store stacks are geared
676 for memory wiping, so expanding strings will leave stuff laying around.
677 But no need to compound the problem, so get rid of the one we can. */
679 memset(tmps, '\0', strlen(tmps));
684 HDEBUG(D_auth) debug_printf(" Unrecognised callback: %d\n", prop);
685 cbrc = GSASL_NO_CALLBACK;
688 HDEBUG(D_auth) debug_printf("Returning %s (%s)\n",
689 gsasl_strerror_name(cbrc), gsasl_strerror(cbrc));
695 /******************************************************************************/
697 #define PROP_OPTIONAL BIT(0)
698 #define PROP_STRINGPREP BIT(1)
702 client_prop(Gsasl_session * sctx, Gsasl_property propnum, uschar * val,
703 const uschar * why, unsigned flags, uschar * buffer, int buffsize)
708 if (flags & PROP_OPTIONAL && !val) return TRUE;
709 if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s)
711 string_format(buffer, buffsize, "%s", expand_string_message);
714 if (!*s) return TRUE;
717 if (flags & PROP_STRINGPREP)
719 if (gsasl_saslprep(CCS s, 0, CSS &t, &rc) != GSASL_OK)
721 string_format(buffer, buffsize, "Bad result from saslprep(%s): %s\n",
722 why, stringprep_strerror(rc));
723 HDEBUG(D_auth) debug_printf("%s\n", buffer);
726 gsasl_property_set(sctx, propnum, CS t);
732 gsasl_property_set(sctx, propnum, CS s);
737 /*************************************************
738 * Client entry point *
739 *************************************************/
741 /* For interface, see auths/README */
745 auth_instance *ablock, /* authenticator block */
746 void * sx, /* connection */
747 int timeout, /* command timeout */
748 uschar *buffer, /* buffer for reading response */
749 int buffsize) /* size of buffer */
751 auth_gsasl_options_block *ob =
752 (auth_gsasl_options_block *)(ablock->options_block);
753 Gsasl_session * sctx = NULL;
754 struct callback_exim_state cb_state;
756 BOOL initial = TRUE, do_stringprep;
757 int rc, yield = FAIL, flags;
760 debug_printf("GNU SASL: initialising session for %s, mechanism %s\n",
761 ablock->name, ob->server_mech);
766 if (tls_out.channelbinding && ob->client_channelbinding)
768 # ifdef EXPERIMENTAL_TLS_RESUME
769 if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED)
770 { /* per RFC 7677 section 4 */
771 string_format(buffer, buffsize, "%s",
772 "channel binding not usable on resumed TLS without extended-master-secret");
776 # ifdef CHANNELBIND_HACK
777 /* This is a gross hack to get around the library a) requiring that
778 c-b was already set, at the _start() call, and b) caching a b64'd
779 version of the binding then which it never updates. */
781 gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding);
786 if ((rc = gsasl_client_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK)
788 string_format(buffer, buffsize, "GNU SASL: session start failure: %s (%s)",
789 gsasl_strerror_name(rc), gsasl_strerror(rc));
790 HDEBUG(D_auth) debug_printf("%s\n", buffer);
794 cb_state.ablock = ablock;
795 cb_state.currently = CURRENTLY_CLIENT;
796 gsasl_session_hook_set(sctx, &cb_state);
800 flags = Ustrncmp(ob->server_mech, "SCRAM-", 5) == 0 ? PROP_STRINGPREP : 0;
802 if ( !client_prop(sctx, GSASL_PASSWORD, ob->client_password, US"password",
803 flags, buffer, buffsize)
804 || !client_prop(sctx, GSASL_AUTHID, ob->client_username, US"username",
805 flags, buffer, buffsize)
806 || !client_prop(sctx, GSASL_AUTHZID, ob->client_authz, US"authz",
807 flags | PROP_OPTIONAL, buffer, buffsize)
812 if (tls_out.channelbinding)
813 if (ob->client_channelbinding)
815 HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
817 # ifndef CHANNELBIND_HACK
818 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
823 debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
827 /* Run the SASL conversation with the server */
834 rc = gsasl_step64(sctx, CS s, CSS &outstr);
837 ? smtp_write_command(sx, SCMD_FLUSH,
838 outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n",
839 ablock->public_name, outstr) <= 0
841 ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0
843 if (outstr && *outstr) free(outstr);
851 if (rc != GSASL_NEEDS_MORE)
855 string_format(buffer, buffsize, "gsasl: %s", gsasl_strerror(rc));
859 /* expecting a final 2xx from the server, accepting the AUTH */
861 if (smtp_read_response(sx, buffer, buffsize, '2', timeout))
863 break; /* from SASL sequence loop */
866 /* 2xx or 3xx response is acceptable. If 2xx, no further input */
868 if (!smtp_read_response(sx, buffer, buffsize, '3', timeout))
869 if (errno == 0 && buffer[0] == '2')
885 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
887 HDEBUG(D_auth) debug_printf("GNU SASL callback %d for %s/%s as client\n",
888 prop, ablock->name, ablock->public_name);
892 HDEBUG(D_auth) debug_printf(" inquired for AUTHZID; not providing one\n");
894 case GSASL_SCRAM_SALTED_PASSWORD:
896 debug_printf(" inquired for SCRAM_SALTED_PASSWORD; not providing one\n");
898 case GSASL_CB_TLS_UNIQUE:
900 debug_printf(" inquired for CB_TLS_UNIQUE, filling in\n");
901 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
904 return GSASL_NO_CALLBACK;
907 /*************************************************
909 *************************************************/
912 auth_gsasl_version_report(FILE *f)
915 runtime = gsasl_check_version(NULL);
916 fprintf(f, "Library version: GNU SASL: Compile: %s\n"
918 GSASL_VERSION, runtime);
924 void auth_gsasl_macros(void) {}
926 #endif /*!MACRO_PREDEF*/
927 #endif /* AUTH_GSASL */
929 /* End of gsasl_exim.c */