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