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