Copyright updates:
[exim.git] / src / src / auths / gsasl_exim.c
CommitLineData
44bbabb5
PP
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
1e1ddfac 5/* Copyright (c) The Exim Maintainers 2019-2020 */
f9ba5e22 6/* Copyright (c) University of Cambridge 1995 - 2018 */
44bbabb5
PP
7/* See the file NOTICE for conditions of use and distribution. */
8
df6303fa
PP
9/* Copyright (c) Twitter Inc 2012
10 Author: Phil Pennock <pdp@exim.org> */
11/* Copyright (c) Phil Pennock 2012 */
44bbabb5
PP
12
13/* Interface to GNU SASL library for generic authentication. */
14
15/* Trade-offs:
16
17GNU SASL does not provide authentication data itself, so we have to expose
18that decision to configuration. For some mechanisms, we need to act much
19like plaintext. For others, we only need to be able to provide some
20evaluated data on demand. There's no abstracted way (ie, without hardcoding
21knowledge of authenticators here) to know which need what properties; we
22can't query a session or the library for "we will need these for mechanism X".
23
24So: we always require server_condition, even if sometimes it will just be
25set as "yes". We do provide a number of other hooks, which might not make
26sense in all contexts. For some, we can do checks at init time.
27*/
28
29#include "../exim.h"
14a806d6 30#define CHANNELBIND_HACK
44bbabb5
PP
31
32#ifndef AUTH_GSASL
33/* dummy function to satisfy compilers when we link in an "empty" file. */
e1d15f5e
JH
34static void dummy(int x);
35static void dummy2(int x) { dummy(x-1); }
d9d29e05 36static void dummy(int x) { dummy2(x-1); }
44bbabb5
PP
37#else
38
39#include <gsasl.h>
40#include "gsasl_exim.h"
41
14a806d6 42
25bd12fd
JH
43#if GSASL_VERSION_MINOR >= 9
44# define EXIM_GSASL_HAVE_SCRAM_SHA_256
49d47806
JH
45
46# if GSASL_VERSION_PATCH >= 1
47# define EXIM_GSASL_SCRAM_S_KEY
48# endif
25bd12fd
JH
49#endif
50
51
44bbabb5
PP
52/* Authenticator-specific options. */
53/* I did have server_*_condition options for various mechanisms, but since
54we only ever handle one mechanism at a time, I didn't see the point in keeping
55that. In case someone sees a point, I've left the condition_check() API
56alone. */
13a4b4c1
JH
57#define LOFF(field) OPT_OFF(auth_gsasl_options_block, field)
58
44bbabb5 59optionlist auth_gsasl_options[] = {
13a4b4c1
JH
60 { "client_authz", opt_stringptr, LOFF(client_authz) },
61 { "client_channelbinding", opt_bool, LOFF(client_channelbinding) },
62 { "client_password", opt_stringptr, LOFF(client_password) },
63 { "client_spassword", opt_stringptr, LOFF(client_spassword) },
64 { "client_username", opt_stringptr, LOFF(client_username) },
65
66 { "server_channelbinding", opt_bool, LOFF(server_channelbinding) },
67 { "server_hostname", opt_stringptr, LOFF(server_hostname) },
49d47806 68#ifdef EXIM_GSASL_SCRAM_S_KEY
13a4b4c1 69 { "server_key", opt_stringptr, LOFF(server_key) },
49d47806 70#endif
13a4b4c1
JH
71 { "server_mech", opt_stringptr, LOFF(server_mech) },
72 { "server_password", opt_stringptr, LOFF(server_password) },
73 { "server_realm", opt_stringptr, LOFF(server_realm) },
74 { "server_scram_iter", opt_stringptr, LOFF(server_scram_iter) },
75 { "server_scram_salt", opt_stringptr, LOFF(server_scram_salt) },
49d47806 76#ifdef EXIM_GSASL_SCRAM_S_KEY
13a4b4c1 77 { "server_skey", opt_stringptr, LOFF(server_s_key) },
49d47806 78#endif
13a4b4c1 79 { "server_service", opt_stringptr, LOFF(server_service) }
44bbabb5 80};
44bbabb5
PP
81
82int auth_gsasl_options_count =
83 sizeof(auth_gsasl_options)/sizeof(optionlist);
84
85/* Defaults for the authenticator-specific options. */
86auth_gsasl_options_block auth_gsasl_option_defaults = {
14a806d6
JH
87 .server_service = US"smtp",
88 .server_hostname = US"$primary_hostname",
89 .server_scram_iter = US"4096",
90 /* all others zero/null */
44bbabb5
PP
91};
92
d185889f
JH
93
94#ifdef MACRO_PREDEF
49d47806 95# include "../macro_predef.h"
d185889f
JH
96
97/* Dummy values */
98void auth_gsasl_init(auth_instance *ablock) {}
99int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;}
1c519e07 100int auth_gsasl_client(auth_instance *ablock, void * sx,
251b9eb4 101 int timeout, uschar *buffer, int buffsize) {return 0;}
f9df71c0 102void auth_gsasl_version_report(FILE *f) {}
d185889f 103
25bd12fd
JH
104void
105auth_gsasl_macros(void)
106{
107# ifdef EXIM_GSASL_HAVE_SCRAM_SHA_256
108 builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_SHA_256");
109# endif
49d47806
JH
110# ifdef EXIM_GSASL_SCRAM_S_KEY
111 builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_S_KEY");
112# endif
25bd12fd
JH
113}
114
d185889f
JH
115#else /*!MACRO_PREDEF*/
116
117
118
44bbabb5
PP
119/* "Globals" for managing the gsasl interface. */
120
121static Gsasl *gsasl_ctx = NULL;
122static int
123 main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop);
124static int
125 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
126static int
127 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
128
129static BOOL sasl_error_should_defer = FALSE;
130static Gsasl_property callback_loop = 0;
131static BOOL checked_server_condition = FALSE;
132
133enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 };
134
135struct callback_exim_state {
136 auth_instance *ablock;
137 int currently;
138};
139
140
141/*************************************************
142* Initialization entry point *
143*************************************************/
144
145/* Called for each instance, after its options have been read, to
146enable consistency checks to be done, or anything else that needs
147to be set up. */
148
149void
150auth_gsasl_init(auth_instance *ablock)
151{
14a806d6
JH
152static char * once = NULL;
153int rc;
d7978c0f
JH
154auth_gsasl_options_block *ob =
155 (auth_gsasl_options_block *)(ablock->options_block);
156
157/* As per existing Cyrus glue, use the authenticator's public name as
158the default for the mechanism name; we don't handle multiple mechanisms
159in one authenticator, but the same driver can be used multiple times. */
160
6a2c32cb 161if (!ob->server_mech)
d7978c0f
JH
162 ob->server_mech = string_copy(ablock->public_name);
163
164/* Can get multiple session contexts from one library context, so just
165initialise the once. */
6a2c32cb
JH
166
167if (!gsasl_ctx)
168 {
169 if ((rc = gsasl_init(&gsasl_ctx)) != GSASL_OK)
d7978c0f
JH
170 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
171 "couldn't initialise GNU SASL library: %s (%s)",
172 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
6a2c32cb 173
d7978c0f 174 gsasl_callback_set(gsasl_ctx, main_callback);
6a2c32cb 175 }
44bbabb5 176
d7978c0f 177/* We don't need this except to log it for debugging. */
6a2c32cb 178
14a806d6
JH
179HDEBUG(D_auth) if (!once)
180 {
181 if ((rc = gsasl_server_mechlist(gsasl_ctx, &once)) != GSASL_OK)
182 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
183 "failed to retrieve list of mechanisms: %s (%s)",
184 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
6a2c32cb 185
14a806d6
JH
186 debug_printf("GNU SASL supports: %s\n", once);
187 }
44bbabb5 188
14a806d6 189if (!gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech))
d7978c0f
JH
190 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
191 "GNU SASL does not support mechanism \"%s\"",
192 ablock->name, ob->server_mech);
193
14a806d6
JH
194ablock->server = TRUE;
195
6a2c32cb
JH
196if ( !ablock->server_condition
197 && ( streqic(ob->server_mech, US"EXTERNAL")
198 || streqic(ob->server_mech, US"ANONYMOUS")
199 || streqic(ob->server_mech, US"PLAIN")
200 || streqic(ob->server_mech, US"LOGIN")
201 ) )
14a806d6
JH
202 {
203 ablock->server = FALSE;
204 HDEBUG(D_auth) debug_printf("%s authenticator: "
205 "Need server_condition for %s mechanism\n",
d7978c0f 206 ablock->name, ob->server_mech);
14a806d6 207 }
44bbabb5 208
d7978c0f
JH
209/* This does *not* scale to new SASL mechanisms. Need a better way to ask
210which properties will be needed. */
6a2c32cb
JH
211
212if ( !ob->server_realm
213 && streqic(ob->server_mech, US"DIGEST-MD5"))
14a806d6
JH
214 {
215 ablock->server = FALSE;
216 HDEBUG(D_auth) debug_printf("%s authenticator: "
217 "Need server_realm for %s mechanism\n",
d7978c0f 218 ablock->name, ob->server_mech);
14a806d6 219 }
ce52b325 220
d7978c0f
JH
221/* At present, for mechanisms we don't panic on absence of server_condition;
222need to figure out the most generically correct approach to deciding when
223it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism,
224etc) it clearly is critical.
d7978c0f 225*/
6a2c32cb 226
14a806d6 227ablock->client = ob->client_username && ob->client_password;
44bbabb5
PP
228}
229
230
231/* GNU SASL uses one top-level callback, registered at library level.
232We dispatch to client and server functions instead. */
233
234static int
235main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
236{
d7978c0f
JH
237int rc = 0;
238struct callback_exim_state *cb_state =
239 (struct callback_exim_state *)gsasl_session_hook_get(sctx);
240
6a2c32cb 241if (!cb_state)
d7978c0f 242 {
14a806d6
JH
243 HDEBUG(D_auth) debug_printf("gsasl callback (%d) not from our server/client processing\n", prop);
244#ifdef CHANNELBIND_HACK
245 if (prop == GSASL_CB_TLS_UNIQUE)
246 {
247 uschar * s;
248 if ((s = gsasl_callback_hook_get(ctx)))
249 {
250 HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n");
251 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CS s);
252 }
253 else
254 {
255 HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE! dummy for now\n");
256 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, "");
257 }
258 return GSASL_OK;
259 }
260#endif
d7978c0f 261 return GSASL_NO_CALLBACK;
44bbabb5
PP
262 }
263
14a806d6
JH
264HDEBUG(D_auth)
265 debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n",
266 prop, callback_loop);
267
d7978c0f
JH
268if (callback_loop > 0)
269 {
14a806d6
JH
270 /* Most likely is that we were asked for property FOO, and to
271 expand the string we asked for property BAR to put into an auth
272 variable, but property BAR is not supplied for this mechanism. */
d7978c0f
JH
273 HDEBUG(D_auth)
274 debug_printf("Loop, asked for property %d while handling property %d\n",
275 prop, callback_loop);
276 return GSASL_NO_CALLBACK;
44bbabb5 277 }
d7978c0f 278callback_loop = prop;
44bbabb5 279
d7978c0f
JH
280if (cb_state->currently == CURRENTLY_CLIENT)
281 rc = client_callback(ctx, sctx, prop, cb_state->ablock);
282else if (cb_state->currently == CURRENTLY_SERVER)
283 rc = server_callback(ctx, sctx, prop, cb_state->ablock);
284else
285 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
286 "unhandled callback state, bug in Exim", cb_state->ablock->name);
287 /* NOTREACHED */
44bbabb5 288
d7978c0f
JH
289callback_loop = 0;
290return rc;
44bbabb5
PP
291}
292
293
294/*************************************************
49d47806
JH
295* Debug service function *
296*************************************************/
297static const uschar *
298gsasl_prop_code_to_name(Gsasl_property prop)
299{
300switch (prop)
301 {
302 case GSASL_AUTHID: return US"AUTHID";
303 case GSASL_AUTHZID: return US"AUTHZID";
304 case GSASL_PASSWORD: return US"PASSWORD";
305 case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN";
306 case GSASL_SERVICE: return US"SERVICE";
307 case GSASL_HOSTNAME: return US"HOSTNAME";
308 case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME";
309 case GSASL_PASSCODE: return US"PASSCODE";
310 case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN";
311 case GSASL_PIN: return US"PIN";
312 case GSASL_REALM: return US"REALM";
313 case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD";
314 case GSASL_QOPS: return US"QOPS";
315 case GSASL_QOP: return US"QOP";
316 case GSASL_SCRAM_ITER: return US"SCRAM_ITER";
317 case GSASL_SCRAM_SALT: return US"SCRAM_SALT";
318 case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD";
319#ifdef EXIM_GSASL_SCRAM_S_KEY
320 case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY";
321 case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY";
322#endif
323 case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE";
324 case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER";
325 case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL";
326 case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL";
327 case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA";
328 case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER";
329 case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER";
330#ifdef EXIM_GSASL_SCRAM_S_KEY
331 case GSASL_SCRAM_CLIENTKEY: return US"SCRAM_CLIENTKEY";
332#endif
333 case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE";
334 case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL";
335 case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS";
336 case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI";
337 case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID";
338 case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20";
339 case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20";
340 }
341return CUS string_sprintf("(unknown prop: %d)", (int)prop);
342}
343
344/*************************************************
44bbabb5
PP
345* Server entry point *
346*************************************************/
347
348/* For interface, see auths/README */
349
350int
351auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
352{
d7978c0f
JH
353char *tmps;
354char *to_send, *received;
355Gsasl_session *sctx = NULL;
356auth_gsasl_options_block *ob =
357 (auth_gsasl_options_block *)(ablock->options_block);
358struct callback_exim_state cb_state;
359int rc, auth_result, exim_error, exim_error_override;
360
361HDEBUG(D_auth)
14a806d6 362 debug_printf("GNU SASL: initialising session for %s, mechanism %s\n",
d7978c0f
JH
363 ablock->name, ob->server_mech);
364
14a806d6 365#ifndef DISABLE_TLS
1c519e07
JH
366if (tls_in.channelbinding && ob->server_channelbinding)
367 {
368# ifdef EXPERIMENTAL_TLS_RESUME
369 if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED)
370 { /* per RFC 7677 section 4 */
371 HDEBUG(D_auth) debug_printf(
372 "channel binding not usable on resumed TLS without extended-master-secret");
373 return FAIL;
374 }
375# endif
14a806d6
JH
376# ifdef CHANNELBIND_HACK
377/* This is a gross hack to get around the library a) requiring that
378c-b was already set, at the _start() call, and b) caching a b64'd
379version of the binding then which it never updates. */
380
14a806d6
JH
381 gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding);
382# endif
1c519e07 383 }
14a806d6
JH
384#endif
385
6a2c32cb 386if ((rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK)
d7978c0f
JH
387 {
388 auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
389 gsasl_strerror_name(rc), gsasl_strerror(rc));
390 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
391 return DEFER;
44bbabb5 392 }
d7978c0f
JH
393/* Hereafter: gsasl_finish(sctx) please */
394
d7978c0f
JH
395cb_state.ablock = ablock;
396cb_state.currently = CURRENTLY_SERVER;
14a806d6 397gsasl_session_hook_set(sctx, &cb_state);
d7978c0f
JH
398
399tmps = CS expand_string(ob->server_service);
400gsasl_property_set(sctx, GSASL_SERVICE, tmps);
401tmps = CS expand_string(ob->server_hostname);
402gsasl_property_set(sctx, GSASL_HOSTNAME, tmps);
403if (ob->server_realm)
404 {
405 tmps = CS expand_string(ob->server_realm);
406 if (tmps && *tmps)
407 gsasl_property_set(sctx, GSASL_REALM, tmps);
44bbabb5 408 }
d7978c0f
JH
409/* We don't support protection layers. */
410gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
6a2c32cb 411
01603eec 412#ifndef DISABLE_TLS
b1a32a3c 413if (tls_in.channelbinding)
d7978c0f
JH
414 {
415 /* Some auth mechanisms can ensure that both sides are talking withing the
416 same security context; for TLS, this means that even if a bad certificate
417 has been accepted, they remain MitM-proof because both sides must be within
418 the same negotiated session; if someone is terminating one session and
419 proxying data on within a second, authentication will fail.
420
421 We might not have this available, depending upon TLS implementation,
422 ciphersuite, phase of moon ...
423
424 If we do, it results in extra SASL mechanisms being available; here,
425 Exim's one-mechanism-per-authenticator potentially causes problems.
426 It depends upon how GNU SASL will implement the PLUS variants of GS2
427 and whether it automatically mandates a switch to the bound PLUS
428 if the data is available. Since default-on, despite being more secure,
429 would then result in mechanism name changes on a library update, we
430 have little choice but to default it off and let the admin choose to
431 enable it. *sigh*
432 */
433 if (ob->server_channelbinding)
434 {
435 HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
436 ablock->name);
1c519e07 437# ifndef CHANNELBIND_HACK
14a806d6
JH
438 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_in.channelbinding);
439# endif
44bbabb5 440 }
d7978c0f 441 else
44bbabb5 442 HDEBUG(D_auth)
d7978c0f
JH
443 debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
444 ablock->name);
44bbabb5 445 }
d7978c0f
JH
446else
447 HDEBUG(D_auth)
448 debug_printf("Auth %s: no channel-binding data available\n",
449 ablock->name);
44bbabb5
PP
450#endif
451
d7978c0f
JH
452checked_server_condition = FALSE;
453
454received = CS initial_data;
455to_send = NULL;
456exim_error = exim_error_override = OK;
457
458do {
6a2c32cb 459 switch (rc = gsasl_step64(sctx, received, &to_send))
d7978c0f
JH
460 {
461 case GSASL_OK:
462 if (!to_send)
463 goto STOP_INTERACTION;
464 break;
465
466 case GSASL_NEEDS_MORE:
467 break;
468
469 case GSASL_AUTHENTICATION_ERROR:
470 case GSASL_INTEGRITY_ERROR:
471 case GSASL_NO_AUTHID:
472 case GSASL_NO_ANONYMOUS_TOKEN:
473 case GSASL_NO_AUTHZID:
474 case GSASL_NO_PASSWORD:
475 case GSASL_NO_PASSCODE:
476 case GSASL_NO_PIN:
477 case GSASL_BASE64_ERROR:
478 HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n",
479 gsasl_strerror_name(rc), gsasl_strerror(rc));
480 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
481 "GNU SASL permanent failure: %s (%s)",
482 ablock->name, ob->server_mech,
483 gsasl_strerror_name(rc), gsasl_strerror(rc));
484 if (rc == GSASL_BASE64_ERROR)
485 exim_error_override = BAD64;
486 goto STOP_INTERACTION;
487
488 default:
489 auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)",
490 gsasl_strerror_name(rc), gsasl_strerror(rc));
491 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
492 exim_error_override = DEFER;
493 goto STOP_INTERACTION;
44bbabb5
PP
494 }
495
49d47806 496 /*XXX having our caller send the final smtp "235" is unfortunate; wastes a roundtrip */
6a2c32cb 497 if ((rc == GSASL_NEEDS_MORE) || (to_send && *to_send))
14a806d6 498 exim_error = auth_get_no64_data(USS &received, US to_send);
ce52b325 499
d7978c0f
JH
500 if (to_send)
501 {
502 free(to_send);
503 to_send = NULL;
ce52b325
PP
504 }
505
d7978c0f
JH
506 if (exim_error)
507 break; /* handles * cancelled check */
44bbabb5
PP
508
509 } while (rc == GSASL_NEEDS_MORE);
510
511STOP_INTERACTION:
d7978c0f 512auth_result = rc;
44bbabb5 513
49d47806
JH
514HDEBUG(D_auth)
515 {
516 const uschar * s;
517 if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_ITER)))
518 debug_printf(" - itercnt: '%s'\n", s);
519 if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALT)))
520 debug_printf(" - salt: '%s'\n", s);
521#ifdef EXIM_GSASL_SCRAM_S_KEY
522 if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SERVERKEY)))
523 debug_printf(" - ServerKey: '%s'\n", s);
524 if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_STOREDKEY)))
525 debug_printf(" - StoredKey: '%s'\n", s);
526#endif
527 }
528
d7978c0f 529gsasl_finish(sctx);
44bbabb5 530
d7978c0f 531/* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */
44bbabb5 532
d7978c0f
JH
533if (exim_error != OK)
534 return exim_error;
44bbabb5 535
d7978c0f
JH
536if (auth_result != GSASL_OK)
537 {
538 HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n",
539 gsasl_strerror_name(auth_result), gsasl_strerror(auth_result));
540 if (exim_error_override != OK)
541 return exim_error_override; /* might be DEFER */
542 if (sasl_error_should_defer) /* overriding auth failure SASL error */
543 return DEFER;
544 return FAIL;
44bbabb5
PP
545 }
546
d7978c0f
JH
547/* Auth succeeded, check server_condition unless already done in callback */
548return checked_server_condition ? OK : auth_check_serv_cond(ablock);
44bbabb5
PP
549}
550
d7978c0f 551
44bbabb5
PP
552/* returns the GSASL status of expanding the Exim string given */
553static int
554condition_check(auth_instance *ablock, uschar *label, uschar *condition_string)
555{
6a2c32cb
JH
556int exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL);
557switch (exim_rc)
d7978c0f 558 {
6a2c32cb
JH
559 case OK: return GSASL_OK;
560 case DEFER: sasl_error_should_defer = TRUE;
561 return GSASL_AUTHENTICATION_ERROR;
562 case FAIL: return GSASL_AUTHENTICATION_ERROR;
563 default: log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
564 "Unhandled return from checking %s: %d",
565 ablock->name, label, exim_rc);
44bbabb5 566 }
d7978c0f 567
d7978c0f
JH
568/* NOTREACHED */
569return GSASL_AUTHENTICATION_ERROR;
44bbabb5
PP
570}
571
2b615f22
JH
572
573static void
574set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop)
575{
576uschar * propval = US gsasl_property_fast(sctx, prop);
577int i = expand_nmax, j = i + 1;
578propval = propval ? string_copy(propval) : US"";
579auth_vars[i] = expand_nstring[j] = propval;
580expand_nlength[j] = Ustrlen(propval);
581expand_nmax = j;
582}
583
584static void
585set_exim_authvars_from_a_az_r_props(Gsasl_session * sctx)
586{
587if (expand_nmax > 0 ) return;
588
589/* Asking for GSASL_AUTHZID calls back into us if we use
590gsasl_property_get(), thus the use of gsasl_property_fast().
591Do we really want to hardcode limits per mechanism? What happens when
592a new mechanism is added to the library. It *shouldn't* result in us
593needing to add more glue, since avoiding that is a large part of the
594point of SASL. */
595
596set_exim_authvar_from_prop(sctx, GSASL_AUTHID);
597set_exim_authvar_from_prop(sctx, GSASL_AUTHZID);
598set_exim_authvar_from_prop(sctx, GSASL_REALM);
599}
600
601
44bbabb5 602static int
49d47806
JH
603prop_from_option(Gsasl_session * sctx, Gsasl_property prop,
604 const uschar * option)
605{
606HDEBUG(D_auth) debug_printf(" %s\n", gsasl_prop_code_to_name(prop));
607if (option)
608 {
609 set_exim_authvars_from_a_az_r_props(sctx);
610 option = expand_cstring(option);
611 HDEBUG(D_auth) debug_printf(" '%s'\n", option);
612 if (*option)
613 gsasl_property_set(sctx, prop, CCS option);
614 return GSASL_OK;
615 }
616HDEBUG(D_auth) debug_printf(" option not set\n");
617return GSASL_NO_CALLBACK;
618}
619
620static int
6a2c32cb
JH
621server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop,
622 auth_instance *ablock)
44bbabb5 623{
d7978c0f 624char *tmps;
49d47806 625uschar *s, *propval;
d7978c0f
JH
626int cbrc = GSASL_NO_CALLBACK;
627auth_gsasl_options_block *ob =
628 (auth_gsasl_options_block *)(ablock->options_block);
629
49d47806
JH
630HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n",
631 gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name);
d7978c0f
JH
632
633for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
634expand_nmax = 0;
635
636switch (prop)
637 {
638 case GSASL_VALIDATE_SIMPLE:
639 /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
2b615f22
JH
640 set_exim_authvar_from_prop(sctx, GSASL_AUTHID);
641 set_exim_authvar_from_prop(sctx, GSASL_AUTHZID);
642 set_exim_authvar_from_prop(sctx, GSASL_PASSWORD);
d7978c0f
JH
643
644 cbrc = condition_check(ablock, US"server_condition", ablock->server_condition);
645 checked_server_condition = TRUE;
646 break;
647
648 case GSASL_VALIDATE_EXTERNAL:
6a2c32cb 649 if (!ablock->server_condition)
d7978c0f 650 {
14a806d6 651 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL\n");
d7978c0f 652 cbrc = GSASL_AUTHENTICATION_ERROR;
44bbabb5 653 break;
44bbabb5 654 }
2b615f22 655 set_exim_authvar_from_prop(sctx, GSASL_AUTHZID);
d7978c0f
JH
656
657 cbrc = condition_check(ablock,
658 US"server_condition (EXTERNAL)", ablock->server_condition);
659 checked_server_condition = TRUE;
660 break;
661
662 case GSASL_VALIDATE_ANONYMOUS:
6a2c32cb 663 if (!ablock->server_condition)
d7978c0f 664 {
14a806d6 665 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS\n");
d7978c0f 666 cbrc = GSASL_AUTHENTICATION_ERROR;
44bbabb5 667 break;
44bbabb5 668 }
2b615f22 669 set_exim_authvar_from_prop(sctx, GSASL_ANONYMOUS_TOKEN);
d7978c0f
JH
670
671 cbrc = condition_check(ablock,
672 US"server_condition (ANONYMOUS)", ablock->server_condition);
673 checked_server_condition = TRUE;
674 break;
675
676 case GSASL_VALIDATE_GSSAPI:
677 /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME
678 The display-name is authenticated as part of GSS, the authzid is claimed
679 by the SASL integration after authentication; protected against tampering
680 (if the SASL mechanism supports that, which Kerberos does) but is
681 unverified, same as normal for other mechanisms.
6a2c32cb 682 First coding, we had these values swapped, but for consistency and prior
d7978c0f
JH
683 to the first release of Exim with this authenticator, they've been
684 switched to match the ordering of GSASL_VALIDATE_SIMPLE. */
6a2c32cb 685
2b615f22
JH
686 set_exim_authvar_from_prop(sctx, GSASL_GSSAPI_DISPLAY_NAME);
687 set_exim_authvar_from_prop(sctx, GSASL_AUTHZID);
d7978c0f
JH
688
689 /* In this one case, it perhaps makes sense to default back open?
690 But for consistency, let's just mandate server_condition here too. */
6a2c32cb 691
d7978c0f
JH
692 cbrc = condition_check(ablock,
693 US"server_condition (GSSAPI family)", ablock->server_condition);
694 checked_server_condition = TRUE;
695 break;
696
98eb9592 697 case GSASL_SCRAM_ITER:
49d47806 698 cbrc = prop_from_option(sctx, prop, ob->server_scram_iter);
98eb9592
JH
699 break;
700
701 case GSASL_SCRAM_SALT:
49d47806 702 cbrc = prop_from_option(sctx, prop, ob->server_scram_salt);
98eb9592
JH
703 break;
704
49d47806
JH
705#ifdef EXIM_GSASL_SCRAM_S_KEY
706 case GSASL_SCRAM_STOREDKEY:
707 cbrc = prop_from_option(sctx, prop, ob->server_s_key);
708 break;
709
710 case GSASL_SCRAM_SERVERKEY:
711 cbrc = prop_from_option(sctx, prop, ob->server_key);
712 break;
713#endif
714
d7978c0f 715 case GSASL_PASSWORD:
49d47806 716 /* SCRAM-*: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM
2b615f22 717 DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM
d7978c0f
JH
718 CRAM-MD5: GSASL_AUTHID
719 PLAIN: GSASL_AUTHID and GSASL_AUTHZID
720 LOGIN: GSASL_AUTHID
721 */
2b615f22 722 set_exim_authvars_from_a_az_r_props(sctx);
d7978c0f 723
49d47806 724 if (!(s = ob->server_password))
2b615f22
JH
725 {
726 HDEBUG(D_auth) debug_printf("option not set\n");
0299eb6a 727 break;
2b615f22 728 }
49d47806 729 if (!(tmps = CS expand_string(s)))
d7978c0f 730 {
2b615f22 731 sasl_error_should_defer = !f.expand_string_forcedfail;
d7978c0f
JH
732 HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
733 "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
734 return GSASL_AUTHENTICATION_ERROR;
44bbabb5 735 }
2b615f22 736 HDEBUG(D_auth) debug_printf(" set\n");
d7978c0f 737 gsasl_property_set(sctx, GSASL_PASSWORD, tmps);
6a2c32cb 738
d7978c0f
JH
739 /* This is inadequate; don't think Exim's store stacks are geared
740 for memory wiping, so expanding strings will leave stuff laying around.
741 But no need to compound the problem, so get rid of the one we can. */
6a2c32cb 742
d7978c0f
JH
743 memset(tmps, '\0', strlen(tmps));
744 cbrc = GSASL_OK;
745 break;
746
747 default:
14a806d6 748 HDEBUG(D_auth) debug_printf(" Unrecognised callback: %d\n", prop);
d7978c0f 749 cbrc = GSASL_NO_CALLBACK;
44bbabb5
PP
750 }
751
d7978c0f
JH
752HDEBUG(D_auth) debug_printf("Returning %s (%s)\n",
753 gsasl_strerror_name(cbrc), gsasl_strerror(cbrc));
44bbabb5 754
d7978c0f 755return cbrc;
44bbabb5
PP
756}
757
758
14a806d6
JH
759/******************************************************************************/
760
761#define PROP_OPTIONAL BIT(0)
14a806d6
JH
762
763static BOOL
49d47806
JH
764set_client_prop(Gsasl_session * sctx, Gsasl_property prop, uschar * val,
765 unsigned flags, uschar * buffer, int buffsize)
14a806d6 766{
0299eb6a 767uschar * s;
14a806d6
JH
768int rc;
769
49d47806 770if (!val) return !!(flags & PROP_OPTIONAL);
14a806d6
JH
771if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s)
772 {
773 string_format(buffer, buffsize, "%s", expand_string_message);
774 return FALSE;
775 }
49d47806
JH
776if (*s)
777 {
778 HDEBUG(D_auth) debug_printf("%s: set %s = '%s'\n", __FUNCTION__,
779 gsasl_prop_code_to_name(prop), s);
780 gsasl_property_set(sctx, prop, CS s);
781 }
782
14a806d6
JH
783return TRUE;
784}
785
44bbabb5
PP
786/*************************************************
787* Client entry point *
788*************************************************/
789
790/* For interface, see auths/README */
791
792int
793auth_gsasl_client(
d0858b27 794 auth_instance *ablock, /* authenticator block */
1c519e07 795 void * sx, /* connection */
d0858b27
JH
796 int timeout, /* command timeout */
797 uschar *buffer, /* buffer for reading response */
798 int buffsize) /* size of buffer */
44bbabb5 799{
14a806d6
JH
800auth_gsasl_options_block *ob =
801 (auth_gsasl_options_block *)(ablock->options_block);
802Gsasl_session * sctx = NULL;
803struct callback_exim_state cb_state;
804uschar * s;
0299eb6a
JH
805BOOL initial = TRUE;
806int rc, yield = FAIL;
14a806d6 807
d7978c0f 808HDEBUG(D_auth)
14a806d6
JH
809 debug_printf("GNU SASL: initialising session for %s, mechanism %s\n",
810 ablock->name, ob->server_mech);
811
812*buffer = 0;
813
814#ifndef DISABLE_TLS
1c519e07
JH
815if (tls_out.channelbinding && ob->client_channelbinding)
816 {
817# ifdef EXPERIMENTAL_TLS_RESUME
818 if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED)
819 { /* per RFC 7677 section 4 */
820 string_format(buffer, buffsize, "%s",
821 "channel binding not usable on resumed TLS without extended-master-secret");
822 return FAIL;
823 }
824# endif
825# ifdef CHANNELBIND_HACK
826 /* This is a gross hack to get around the library a) requiring that
827 c-b was already set, at the _start() call, and b) caching a b64'd
828 version of the binding then which it never updates. */
14a806d6 829
1c519e07
JH
830 gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding);
831# endif
832 }
14a806d6
JH
833#endif
834
835if ((rc = gsasl_client_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK)
836 {
837 string_format(buffer, buffsize, "GNU SASL: session start failure: %s (%s)",
838 gsasl_strerror_name(rc), gsasl_strerror(rc));
839 HDEBUG(D_auth) debug_printf("%s\n", buffer);
840 return ERROR;
841 }
842
843cb_state.ablock = ablock;
844cb_state.currently = CURRENTLY_CLIENT;
845gsasl_session_hook_set(sctx, &cb_state);
846
847/* Set properties */
848
49d47806 849if ( !set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, ob->client_spassword,
0299eb6a 850 0, buffer, buffsize)
49d47806
JH
851 &&
852 !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password,
0299eb6a 853 0, buffer, buffsize)
49d47806
JH
854 || !set_client_prop(sctx, GSASL_AUTHID, ob->client_username,
855 0, buffer, buffsize)
856 || !set_client_prop(sctx, GSASL_AUTHZID, ob->client_authz,
0299eb6a 857 PROP_OPTIONAL, buffer, buffsize)
14a806d6
JH
858 )
859 return ERROR;
860
861#ifndef DISABLE_TLS
862if (tls_out.channelbinding)
863 if (ob->client_channelbinding)
864 {
865 HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
866 ablock->name);
1c519e07 867# ifndef CHANNELBIND_HACK
14a806d6
JH
868 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
869# endif
870 }
871 else
872 HDEBUG(D_auth)
873 debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
874 ablock->name);
875#endif
876
877/* Run the SASL conversation with the server */
878
879for(s = NULL; ;)
880 {
881 uschar * outstr;
882 BOOL fail;
883
884 rc = gsasl_step64(sctx, CS s, CSS &outstr);
885
886 fail = initial
887 ? smtp_write_command(sx, SCMD_FLUSH,
888 outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n",
889 ablock->public_name, outstr) <= 0
890 : outstr
891 ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0
892 : FALSE;
893 if (outstr && *outstr) free(outstr);
894 if (fail)
895 {
896 yield = FAIL_SEND;
897 goto done;
898 }
899 initial = FALSE;
900
901 if (rc != GSASL_NEEDS_MORE)
902 {
903 if (rc != GSASL_OK)
904 {
905 string_format(buffer, buffsize, "gsasl: %s", gsasl_strerror(rc));
906 break;
907 }
908
909 /* expecting a final 2xx from the server, accepting the AUTH */
910
911 if (smtp_read_response(sx, buffer, buffsize, '2', timeout))
912 yield = OK;
913 break; /* from SASL sequence loop */
914 }
915
916 /* 2xx or 3xx response is acceptable. If 2xx, no further input */
917
918 if (!smtp_read_response(sx, buffer, buffsize, '3', timeout))
919 if (errno == 0 && buffer[0] == '2')
920 buffer[4] = '\0';
921 else
922 {
923 yield = FAIL;
924 goto done;
925 }
926 s = buffer + 4;
927 }
928
929done:
49d47806
JH
930HDEBUG(D_auth)
931 {
932 const uschar * s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALTED_PASSWORD);
933 if (s) debug_printf(" - SaltedPassword: '%s'\n", s);
934 }
935
14a806d6
JH
936gsasl_finish(sctx);
937return yield;
44bbabb5
PP
938}
939
940static int
941client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
942{
49d47806
JH
943HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as client\n",
944 gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name);
14a806d6
JH
945switch (prop)
946 {
14a806d6
JH
947 case GSASL_CB_TLS_UNIQUE:
948 HDEBUG(D_auth)
49d47806 949 debug_printf(" filling in\n");
14a806d6
JH
950 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
951 break;
49d47806
JH
952 default:
953 HDEBUG(D_auth)
954 debug_printf(" not providing one\n");
955 break;
14a806d6
JH
956 }
957return GSASL_NO_CALLBACK;
44bbabb5
PP
958}
959
960/*************************************************
961* Diagnostic API *
962*************************************************/
963
964void
965auth_gsasl_version_report(FILE *f)
966{
d7978c0f
JH
967const char *runtime;
968runtime = gsasl_check_version(NULL);
969fprintf(f, "Library version: GNU SASL: Compile: %s\n"
970 " Runtime: %s\n",
971 GSASL_VERSION, runtime);
44bbabb5
PP
972}
973
25bd12fd
JH
974
975
976/* Dummy */
977void auth_gsasl_macros(void) {}
978
d185889f 979#endif /*!MACRO_PREDEF*/
44bbabb5
PP
980#endif /* AUTH_GSASL */
981
982/* End of gsasl_exim.c */