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