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