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