More bug-fixes, GSASL DIGEST-MD5 now works.
[exim.git] / src / src / auths / gsasl_exim.c
CommitLineData
44bbabb5
PP
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
5/* Copyright (c) University of Cambridge 1995 - 2012 */
6/* See the file NOTICE for conditions of use and distribution. */
7
8/* Copyright (c) Twitter Inc 2012 */
9
10/* Interface to GNU SASL library for generic authentication. */
11
12/* Trade-offs:
13
14GNU SASL does not provide authentication data itself, so we have to expose
15that decision to configuration. For some mechanisms, we need to act much
16like plaintext. For others, we only need to be able to provide some
17evaluated data on demand. There's no abstracted way (ie, without hardcoding
18knowledge of authenticators here) to know which need what properties; we
19can't query a session or the library for "we will need these for mechanism X".
20
21So: we always require server_condition, even if sometimes it will just be
22set as "yes". We do provide a number of other hooks, which might not make
23sense in all contexts. For some, we can do checks at init time.
24*/
25
26#include "../exim.h"
27
28#ifndef AUTH_GSASL
29/* dummy function to satisfy compilers when we link in an "empty" file. */
30static void dummy(int x) { dummy(x-1); }
31#else
32
33#include <gsasl.h>
34#include "gsasl_exim.h"
35
36/* Authenticator-specific options. */
37/* I did have server_*_condition options for various mechanisms, but since
38we only ever handle one mechanism at a time, I didn't see the point in keeping
39that. In case someone sees a point, I've left the condition_check() API
40alone. */
41optionlist auth_gsasl_options[] = {
42 { "server_channelbinding", opt_bool,
43 (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) },
44 { "server_hostname", opt_stringptr,
45 (void *)(offsetof(auth_gsasl_options_block, server_hostname)) },
46 { "server_mech", opt_stringptr,
47 (void *)(offsetof(auth_gsasl_options_block, server_mech)) },
48 { "server_password", opt_stringptr,
49 (void *)(offsetof(auth_gsasl_options_block, server_password)) },
50 { "server_realm", opt_stringptr,
51 (void *)(offsetof(auth_gsasl_options_block, server_realm)) },
52 { "server_scram_iter", opt_stringptr,
53 (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) },
54 { "server_scram_salt", opt_stringptr,
55 (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) },
56 { "server_service", opt_stringptr,
57 (void *)(offsetof(auth_gsasl_options_block, server_service)) }
58};
59/* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing
60hooks to avoid cleartext passwords in the Exim server. */
61
62int auth_gsasl_options_count =
63 sizeof(auth_gsasl_options)/sizeof(optionlist);
64
65/* Defaults for the authenticator-specific options. */
66auth_gsasl_options_block auth_gsasl_option_defaults = {
67 US"smtp", /* server_service */
68 US"$primary_hostname", /* server_hostname */
69 NULL, /* server_realm */
70 NULL, /* server_mech */
71 NULL, /* server_password */
72 NULL, /* server_scram_iter */
73 NULL, /* server_scram_salt */
74 FALSE /* server_channelbinding */
75};
76
77/* "Globals" for managing the gsasl interface. */
78
79static Gsasl *gsasl_ctx = NULL;
80static int
81 main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop);
82static int
83 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
84static int
85 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
86
87static BOOL sasl_error_should_defer = FALSE;
88static Gsasl_property callback_loop = 0;
89static BOOL checked_server_condition = FALSE;
90
91enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 };
92
93struct callback_exim_state {
94 auth_instance *ablock;
95 int currently;
96};
97
98
99/*************************************************
100* Initialization entry point *
101*************************************************/
102
103/* Called for each instance, after its options have been read, to
104enable consistency checks to be done, or anything else that needs
105to be set up. */
106
107void
108auth_gsasl_init(auth_instance *ablock)
109{
110 char *p;
111 int rc, supported;
112 auth_gsasl_options_block *ob =
113 (auth_gsasl_options_block *)(ablock->options_block);
114
115 /* As per existing Cyrus glue, use the authenticator's public name as
116 the default for the mechanism name; we don't handle multiple mechanisms
117 in one authenticator, but the same driver can be used multiple times. */
118
119 if (ob->server_mech == NULL)
120 ob->server_mech = string_copy(ablock->public_name);
121
122 /* Can get multiple session contexts from one library context, so just
123 initialise the once. */
124 if (gsasl_ctx == NULL) {
125 rc = gsasl_init(&gsasl_ctx);
126 if (rc != GSASL_OK) {
127 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
128 "couldn't initialise GNU SASL library: %s (%s)",
129 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
130 }
131 gsasl_callback_set(gsasl_ctx, main_callback);
132 }
133
134 /* We don't need this except to log it for debugging. */
135 rc = gsasl_server_mechlist(gsasl_ctx, &p);
136 if (rc != GSASL_OK)
137 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
138 "failed to retrieve list of mechanisms: %s (%s)",
139 ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
140 HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p);
141
142 supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech);
143 if (!supported)
144 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
145 "GNU SASL does not support mechanism \"%s\"",
146 ablock->name, ob->server_mech);
147
148 if ((ablock->server_condition == NULL) &&
ce52b325
PP
149 (streqic(ob->server_mech, US"EXTERNAL") ||
150 streqic(ob->server_mech, US"ANONYMOUS") ||
151 streqic(ob->server_mech, US"PLAIN") ||
152 streqic(ob->server_mech, US"LOGIN")))
44bbabb5
PP
153 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
154 "Need server_condition for %s mechanism",
155 ablock->name, ob->server_mech);
156
ce52b325
PP
157 /* This does *not* scale to new SASL mechanisms. Need a better way to ask
158 which properties will be needed. */
159 if ((ob->server_realm == NULL) &&
160 streqic(ob->server_mech, US"DIGEST-MD5"))
161 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
162 "Need server_realm for %s mechanism",
163 ablock->name, ob->server_mech);
164
44bbabb5
PP
165 /* At present, for mechanisms we don't panic on absence of server_condition;
166 need to figure out the most generically correct approach to deciding when
167 it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism,
168 etc) it clearly is critical.
169
170 So don't activate without server_condition, this might be relaxed in the future.
171 */
172 if (ablock->server_condition != NULL) ablock->server = TRUE;
173 ablock->client = FALSE;
174}
175
176
177/* GNU SASL uses one top-level callback, registered at library level.
178We dispatch to client and server functions instead. */
179
180static int
181main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
182{
183 int rc = 0;
184 struct callback_exim_state *cb_state =
185 (struct callback_exim_state *)gsasl_session_hook_get(sctx);
186
ce52b325
PP
187 HDEBUG(D_auth)
188 debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n",
189 prop, callback_loop);
44bbabb5
PP
190
191 if (cb_state == NULL) {
192 HDEBUG(D_auth) debug_printf(" not from our server/client processing.\n");
193 return GSASL_NO_CALLBACK;
194 }
195
196 if (callback_loop > 0) {
197 /* Most likely is that we were asked for property foo, and to
198 expand the string we asked for property bar to put into an auth
199 variable, but property bar is not supplied for this mechanism. */
200 HDEBUG(D_auth)
201 debug_printf("Loop, asked for property %d while handling property %d\n",
202 prop, callback_loop);
203 return GSASL_NO_CALLBACK;
204 }
205 callback_loop = prop;
206
207 if (cb_state->currently == CURRENTLY_CLIENT)
208 rc = client_callback(ctx, sctx, prop, cb_state->ablock);
209 else if (cb_state->currently == CURRENTLY_SERVER)
210 rc = server_callback(ctx, sctx, prop, cb_state->ablock);
211 else {
212 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
213 "unhandled callback state, bug in Exim", cb_state->ablock->name);
214 /* NOTREACHED */
215 }
216
217 callback_loop = 0;
218 return rc;
219}
220
221
222/*************************************************
223* Server entry point *
224*************************************************/
225
226/* For interface, see auths/README */
227
228int
229auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
230{
231 char *tmps;
232 char *to_send, *received;
233 Gsasl_session *sctx = NULL;
234 auth_gsasl_options_block *ob =
235 (auth_gsasl_options_block *)(ablock->options_block);
236 struct callback_exim_state cb_state;
237 int rc, auth_result, exim_error, exim_error_override;
238
239 HDEBUG(D_auth)
240 debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n",
241 ablock->name, ob->server_mech);
242
243 rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx);
244 if (rc != GSASL_OK) {
245 auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
246 gsasl_strerror_name(rc), gsasl_strerror(rc));
247 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
248 return DEFER;
249 }
250 /* Hereafter: gsasl_finish(sctx) please */
251
252 gsasl_session_hook_set(sctx, (void *)ablock);
253 cb_state.ablock = ablock;
254 cb_state.currently = CURRENTLY_SERVER;
255 gsasl_session_hook_set(sctx, (void *)&cb_state);
256
257 tmps = CS expand_string(ob->server_service);
258 gsasl_property_set(sctx, GSASL_SERVICE, tmps);
259 tmps = CS expand_string(ob->server_hostname);
260 gsasl_property_set(sctx, GSASL_HOSTNAME, tmps);
261 if (ob->server_realm) {
262 tmps = CS expand_string(ob->server_realm);
263 if (tmps && *tmps) {
264 gsasl_property_set(sctx, GSASL_REALM, tmps);
265 }
266 }
267 /* We don't support protection layers. */
268 gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
269#ifdef SUPPORT_TLS
270 if (tls_channelbinding_b64) {
271 /* Some auth mechanisms can ensure that both sides are talking withing the
272 same security context; for TLS, this means that even if a bad certificate
273 has been accepted, they remain MitM-proof because both sides must be within
274 the same negotiated session; if someone is terminating one sesson and
275 proxying data on within a second, authentication will fail.
276
277 We might not have this available, depending upon TLS implementation,
278 ciphersuite, phase of moon ...
279
280 If we do, it results in extra SASL mechanisms being available; here,
281 Exim's one-mechanism-per-authenticator potentially causes problems.
282 It depends upon how GNU SASL will implement the PLUS variants of GS2
283 and whether it automatically mandates a switch to the bound PLUS
284 if the data is available. Since default-on, despite being more secure,
285 would then result in mechanism name changes on a library update, we
286 have little choice but to default it off and let the admin choose to
287 enable it. *sigh*
288 */
289 if (ob->server_channelbinding) {
290 HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
291 ablock->name);
292 gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE,
293 (const char *) tls_channelbinding_b64);
294 } else {
295 HDEBUG(D_auth)
296 debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
297 ablock->name);
298 }
299 } else {
300 HDEBUG(D_auth)
301 debug_printf("Auth %s: no channel-binding data available\n",
302 ablock->name);
303 }
304#endif
305
306 checked_server_condition = FALSE;
307
308 received = CS initial_data;
309 to_send = NULL;
310 exim_error = exim_error_override = OK;
311
312 do {
313 rc = gsasl_step64(sctx, received, &to_send);
314
315 switch (rc) {
316 case GSASL_OK:
ce52b325
PP
317 if (!to_send)
318 goto STOP_INTERACTION;
319 break;
44bbabb5
PP
320
321 case GSASL_NEEDS_MORE:
322 break;
323
324 case GSASL_AUTHENTICATION_ERROR:
325 case GSASL_INTEGRITY_ERROR:
326 case GSASL_NO_AUTHID:
327 case GSASL_NO_ANONYMOUS_TOKEN:
328 case GSASL_NO_AUTHZID:
329 case GSASL_NO_PASSWORD:
330 case GSASL_NO_PASSCODE:
331 case GSASL_NO_PIN:
332 case GSASL_BASE64_ERROR:
333 HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n",
334 gsasl_strerror_name(rc), gsasl_strerror(rc));
335 log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
336 "GNU SASL permanent failure: %s (%s)",
337 ablock->name, ob->server_mech,
338 gsasl_strerror_name(rc), gsasl_strerror(rc));
339 if (rc == GSASL_BASE64_ERROR)
340 exim_error_override = BAD64;
341 goto STOP_INTERACTION;
342
343 default:
344 auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)",
345 gsasl_strerror_name(rc), gsasl_strerror(rc));
346 HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
347 exim_error_override = DEFER;
348 goto STOP_INTERACTION;
349 }
350
ce52b325
PP
351 if ((rc == GSASL_NEEDS_MORE) ||
352 (to_send && *to_send))
353 exim_error =
354 auth_get_no64_data((uschar **)&received, (uschar *)to_send);
355
356 if (to_send) {
357 free(to_send);
358 to_send = NULL;
359 }
360
44bbabb5
PP
361 if (exim_error)
362 break; /* handles * cancelled check */
363
364 } while (rc == GSASL_NEEDS_MORE);
365
366STOP_INTERACTION:
367 auth_result = rc;
368
369 gsasl_finish(sctx);
370
371 /* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */
372
373 if (exim_error != OK)
374 return exim_error;
375
376 if (auth_result != GSASL_OK) {
377 HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n",
378 gsasl_strerror_name(auth_result), gsasl_strerror(auth_result));
379 if (exim_error_override != OK)
380 return exim_error_override; /* might be DEFER */
381 if (sasl_error_should_defer) /* overriding auth failure SASL error */
382 return DEFER;
383 return FAIL;
384 }
385
386 /* Auth succeeded, check server_condition unless already done in callback */
387 return checked_server_condition ? OK : auth_check_serv_cond(ablock);
388}
389
390/* returns the GSASL status of expanding the Exim string given */
391static int
392condition_check(auth_instance *ablock, uschar *label, uschar *condition_string)
393{
394 int exim_rc;
395
396 exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL);
397
398 if (exim_rc == OK) {
399 return GSASL_OK;
400 } else if (exim_rc == DEFER) {
401 sasl_error_should_defer = TRUE;
402 return GSASL_AUTHENTICATION_ERROR;
403 } else if (exim_rc == FAIL) {
404 return GSASL_AUTHENTICATION_ERROR;
405 }
406
407 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
408 "Unhandled return from checking %s: %d",
409 ablock->name, label, exim_rc);
410 /* NOTREACHED */
411 return GSASL_AUTHENTICATION_ERROR;
412}
413
414static int
415server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
416{
417 char *tmps;
418 uschar *propval;
419 int cbrc = GSASL_NO_CALLBACK;
420 int i;
421 auth_gsasl_options_block *ob =
422 (auth_gsasl_options_block *)(ablock->options_block);
423
424 HDEBUG(D_auth)
425 debug_printf("GNU SASL callback %d for %s/%s as server\n",
426 prop, ablock->name, ablock->public_name);
427
428 for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
429 expand_nmax = 0;
430
431 switch (prop) {
432 case GSASL_VALIDATE_SIMPLE:
433 /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
434 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHID);
435 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
436 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
437 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
438 propval = (uschar *) gsasl_property_get(sctx, GSASL_PASSWORD);
439 auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
440 expand_nmax = 3;
441 for (i = 1; i <= 3; ++i)
442 expand_nlength[i] = Ustrlen(expand_nstring[i]);
443
444 cbrc = condition_check(ablock, US"server_condition", ablock->server_condition);
445 checked_server_condition = TRUE;
446 break;
447
448 case GSASL_VALIDATE_EXTERNAL:
449 if (ablock->server_condition == NULL) {
450 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL.\n");
451 cbrc = GSASL_AUTHENTICATION_ERROR;
452 break;
453 }
454 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
455 /* We always set $auth1, even if only to empty string. */
456 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
457 expand_nlength[1] = Ustrlen(expand_nstring[1]);
458 expand_nmax = 1;
459
460 cbrc = condition_check(ablock,
461 US"server_condition (EXTERNAL)", ablock->server_condition);
462 checked_server_condition = TRUE;
463 break;
464
465 case GSASL_VALIDATE_ANONYMOUS:
466 if (ablock->server_condition == NULL) {
467 HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS.\n");
468 cbrc = GSASL_AUTHENTICATION_ERROR;
469 break;
470 }
471 propval = (uschar *) gsasl_property_get(sctx, GSASL_ANONYMOUS_TOKEN);
472 /* We always set $auth1, even if only to empty string. */
473 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
474 expand_nlength[1] = Ustrlen(expand_nstring[1]);
475 expand_nmax = 1;
476
477 cbrc = condition_check(ablock,
478 US"server_condition (ANONYMOUS)", ablock->server_condition);
479 checked_server_condition = TRUE;
480 break;
481
482 case GSASL_VALIDATE_GSSAPI:
483 /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME */
484 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
485 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
486 propval = (uschar *) gsasl_property_get(sctx, GSASL_GSSAPI_DISPLAY_NAME);
487 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
488 expand_nmax = 2;
489 for (i = 1; i <= 2; ++i)
490 expand_nlength[i] = Ustrlen(expand_nstring[i]);
491
492 /* In this one case, it perhaps makes sense to default back open?
493 But for consistency, let's just mandate server_condition here too. */
494 cbrc = condition_check(ablock,
495 US"server_condition (GSSAPI family)", ablock->server_condition);
496 checked_server_condition = TRUE;
497 break;
498
499 case GSASL_PASSWORD:
500 /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM
501 CRAM-MD5: GSASL_AUTHID
502 PLAIN: GSASL_AUTHID and GSASL_AUTHZID
503 LOGIN: GSASL_AUTHID
504 */
505 if (ob->server_scram_iter) {
506 tmps = CS expand_string(ob->server_scram_iter);
507 gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps);
508 }
509 if (ob->server_scram_salt) {
510 tmps = CS expand_string(ob->server_scram_salt);
511 gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps);
512 }
513 /* Asking for GSASL_AUTHZID will probably call back into us.
514 Do we really want to hardcode limits per mechanism? What happens when
515 a new mechanism is added to the library. It *shouldn't* result in us
516 needing to add more glue, since avoiding that is a large part of the
517 point of SASL. */
518 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHID);
519 auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
520 propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
521 auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
522 propval = (uschar *) gsasl_property_get(sctx, GSASL_REALM);
523 auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
524 expand_nmax = 3;
525 for (i = 1; i <= 3; ++i)
526 expand_nlength[i] = Ustrlen(expand_nstring[i]);
527
528 tmps = CS expand_string(ob->server_password);
529 if (tmps == NULL) {
530 sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE;
531 HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
532 "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
533 return GSASL_AUTHENTICATION_ERROR;
534 }
535 gsasl_property_set(sctx, GSASL_PASSWORD, tmps);
536 /* This is inadequate; don't think Exim's store stacks are geared
537 for memory wiping, so expanding strings will leave stuff laying around.
538 But no need to compound the problem, so get rid of the one we can. */
539 memset(tmps, '\0', strlen(tmps));
540 cbrc = GSASL_OK;
541 break;
542
543 default:
544 HDEBUG(D_auth) debug_printf("Unrecognised callback: %d\n", prop);
545 cbrc = GSASL_NO_CALLBACK;
546 }
547
548 HDEBUG(D_auth) debug_printf("Returning %s (%s)\n",
549 gsasl_strerror_name(cbrc), gsasl_strerror(cbrc));
550
551 return cbrc;
552}
553
554
555/*************************************************
556* Client entry point *
557*************************************************/
558
559/* For interface, see auths/README */
560
561int
562auth_gsasl_client(
563 auth_instance *ablock, /* authenticator block */
564 smtp_inblock *inblock, /* connection inblock */
565 smtp_outblock *outblock, /* connection outblock */
566 int timeout, /* command timeout */
567 uschar *buffer, /* buffer for reading response */
568 int buffsize) /* size of buffer */
569{
570 HDEBUG(D_auth)
571 debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
572 /* NOT IMPLEMENTED */
573 return FAIL;
574}
575
576static int
577client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
578{
579 int cbrc = GSASL_NO_CALLBACK;
580 HDEBUG(D_auth)
581 debug_printf("GNU SASL callback %d for %s/%s as client\n",
582 prop, ablock->name, ablock->public_name);
583
584 HDEBUG(D_auth)
585 debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
586
587 return cbrc;
588}
589
590/*************************************************
591* Diagnostic API *
592*************************************************/
593
594void
595auth_gsasl_version_report(FILE *f)
596{
597 const char *runtime;
598 runtime = gsasl_check_version(NULL);
599 fprintf(f, "Library version: GNU SASL: Compile: %s\n"
600 " Runtime: %s\n",
601 GSASL_VERSION, runtime);
602}
603
604#endif /* AUTH_GSASL */
605
606/* End of gsasl_exim.c */