More bug-fixes, GSASL DIGEST-MD5 now works.
[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
10 /* Interface to GNU SASL library for generic authentication. */
11
12 /* Trade-offs:
13
14 GNU SASL does not provide authentication data itself, so we have to expose
15 that decision to configuration.  For some mechanisms, we need to act much
16 like plaintext.  For others, we only need to be able to provide some
17 evaluated data on demand.  There's no abstracted way (ie, without hardcoding
18 knowledge of authenticators here) to know which need what properties; we
19 can't query a session or the library for "we will need these for mechanism X".
20
21 So: we always require server_condition, even if sometimes it will just be
22 set as "yes".  We do provide a number of other hooks, which might not make
23 sense 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. */
30 static 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
38 we only ever handle one mechanism at a time, I didn't see the point in keeping
39 that.  In case someone sees a point, I've left the condition_check() API
40 alone. */
41 optionlist 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
60 hooks to avoid cleartext passwords in the Exim server. */
61
62 int auth_gsasl_options_count =
63   sizeof(auth_gsasl_options)/sizeof(optionlist);
64
65 /* Defaults for the authenticator-specific options. */
66 auth_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
79 static Gsasl *gsasl_ctx = NULL;
80 static int
81   main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop);
82 static int
83   server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
84 static int
85   client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
86
87 static BOOL sasl_error_should_defer = FALSE;
88 static Gsasl_property callback_loop = 0;
89 static BOOL checked_server_condition = FALSE;
90
91 enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 };
92
93 struct 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
104 enable consistency checks to be done, or anything else that needs
105 to be set up. */
106
107 void
108 auth_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) &&
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")))
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
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
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.
178 We dispatch to client and server functions instead. */
179
180 static int
181 main_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
187   HDEBUG(D_auth)
188     debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n",
189         prop, callback_loop);
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
228 int
229 auth_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:
317         if (!to_send)
318           goto STOP_INTERACTION;
319         break;
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
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
361     if (exim_error)
362       break; /* handles * cancelled check */
363
364   } while (rc == GSASL_NEEDS_MORE);
365
366 STOP_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 */
391 static int
392 condition_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
414 static int
415 server_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
561 int
562 auth_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
576 static int
577 client_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
594 void
595 auth_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 */