Implemented gsasl driver for authentication.
authorPhil Pennock <pdp@exim.org>
Mon, 6 Feb 2012 00:13:32 +0000 (19:13 -0500)
committerPhil Pennock <pdp@exim.org>
Mon, 6 Feb 2012 00:13:32 +0000 (19:13 -0500)
Missing: documentation; tests.

Tested: PLAIN auth.

Status: probably buggy

18 files changed:
src/scripts/MakeLinks
src/src/EDITME
src/src/auths/Makefile
src/src/auths/check_serv_cond.c
src/src/auths/cyrus_sasl.c
src/src/auths/cyrus_sasl.h
src/src/auths/get_no64_data.c
src/src/auths/gsasl_exim.c [new file with mode: 0644]
src/src/auths/gsasl_exim.h [new file with mode: 0644]
src/src/config.h.defaults
src/src/drtables.c
src/src/exim.c
src/src/exim.h
src/src/functions.h
src/src/globals.h
src/src/structs.h
src/src/tls-gnu.c
src/src/tls.c

index 082659c9923b8af8a046f05e167b867251d60630..f7d00030cab900afd8c6d9db164efd9bc658a46d 100755 (executable)
@@ -123,6 +123,8 @@ ln -s ../../src/auths/call_radius.c      call_radius.c
 ln -s ../../src/auths/check_serv_cond.c  check_serv_cond.c
 ln -s ../../src/auths/cyrus_sasl.c       cyrus_sasl.c
 ln -s ../../src/auths/cyrus_sasl.h       cyrus_sasl.h
 ln -s ../../src/auths/check_serv_cond.c  check_serv_cond.c
 ln -s ../../src/auths/cyrus_sasl.c       cyrus_sasl.c
 ln -s ../../src/auths/cyrus_sasl.h       cyrus_sasl.h
+ln -s ../../src/auths/gsasl_exim.c       gsasl_exim.c
+ln -s ../../src/auths/gsasl_exim.h       gsasl_exim.h
 ln -s ../../src/auths/get_data.c         get_data.c
 ln -s ../../src/auths/get_no64_data.c    get_no64_data.c
 ln -s ../../src/auths/md5.c              md5.c
 ln -s ../../src/auths/get_data.c         get_data.c
 ln -s ../../src/auths/get_no64_data.c    get_no64_data.c
 ln -s ../../src/auths/md5.c              md5.c
index a180cd5cd02342c05b3703dfb184b8c3daa917ee..7e426eaabe99ce4e6e053bb37bd970a2d2a64145 100644 (file)
@@ -553,6 +553,7 @@ FIXED_NEVER_USERS=root
 # AUTH_CRAM_MD5=yes
 # AUTH_CYRUS_SASL=yes
 # AUTH_DOVECOT=yes
 # AUTH_CRAM_MD5=yes
 # AUTH_CYRUS_SASL=yes
 # AUTH_DOVECOT=yes
+# AUTH_GSASL=yes
 # AUTH_PLAINTEXT=yes
 # AUTH_SPA=yes
 
 # AUTH_PLAINTEXT=yes
 # AUTH_SPA=yes
 
@@ -560,9 +561,10 @@ FIXED_NEVER_USERS=root
 #------------------------------------------------------------------------------
 # If you specified AUTH_CYRUS_SASL above, you should ensure that you have the
 # Cyrus SASL library installed before trying to build Exim, and you probably
 #------------------------------------------------------------------------------
 # If you specified AUTH_CYRUS_SASL above, you should ensure that you have the
 # Cyrus SASL library installed before trying to build Exim, and you probably
-# want to uncomment the following line:
+# want to uncomment the first line below.  Similarly for GNU SASL.
 
 # AUTH_LIBS=-lsasl2
 
 # AUTH_LIBS=-lsasl2
+# AUTH_LIBS=-lgsasl
 
 
 #------------------------------------------------------------------------------
 
 
 #------------------------------------------------------------------------------
index 3e0e1a2cde59c0820443d239650ac456b5fbca43..1354c8cd5e664c041c19c171437d5e6dff93e6e1 100644 (file)
@@ -7,7 +7,8 @@
 
 OBJ = auth-spa.o b64decode.o b64encode.o call_pam.o call_pwcheck.o \
       call_radius.o check_serv_cond.o cram_md5.o cyrus_sasl.o dovecot.o \
 
 OBJ = auth-spa.o b64decode.o b64encode.o call_pam.o call_pwcheck.o \
       call_radius.o check_serv_cond.o cram_md5.o cyrus_sasl.o dovecot.o \
-      get_data.o get_no64_data.o md5.o plaintext.o pwcheck.o sha1.o \
+      get_data.o get_no64_data.o gsasl_exim.o \
+      md5.o plaintext.o pwcheck.o sha1.o \
       spa.o xtextdecode.o xtextencode.o
 
 auths.a:         $(OBJ)
       spa.o xtextdecode.o xtextencode.o
 
 auths.a:         $(OBJ)
@@ -38,6 +39,7 @@ xtextencode.o:      $(HDRS) xtextencode.c
 cram_md5.o:         $(HDRS) cram_md5.c cram_md5.h
 cyrus_sasl.o:       $(HDRS) cyrus_sasl.c cyrus_sasl.h
 dovecot.o:          $(HDRS) dovecot.c dovecot.h
 cram_md5.o:         $(HDRS) cram_md5.c cram_md5.h
 cyrus_sasl.o:       $(HDRS) cyrus_sasl.c cyrus_sasl.h
 dovecot.o:          $(HDRS) dovecot.c dovecot.h
+gsasl_exim.o:       $(HDRS) gsasl_exim.c gsasl_exim.h
 plaintext.o:        $(HDRS) plaintext.c plaintext.h
 spa.o:              $(HDRS) spa.c spa.h
 
 plaintext.o:        $(HDRS) plaintext.c plaintext.h
 spa.o:              $(HDRS) spa.c spa.h
 
index 476d112ae4925887969cdc7329cfb29bcc379968..c10ff1be42d8fc5060cc09433004a9cb76a053d5 100644 (file)
@@ -16,8 +16,8 @@ by all authenticators. */
 *************************************************/
 
 /* This function is called from the server code of all authenticators. For
 *************************************************/
 
 /* This function is called from the server code of all authenticators. For
-plaintext, it is always called: the argument cannot be empty, because for
-plaintext, setting server_condition is what enables it as a server
+plaintext and gsasl, it is always called: the argument cannot be empty, because
+for those, setting server_condition is what enables it as a server
 authenticator. For all the other authenticators, this function is called after
 they have authenticated, to enable additional authorization to be done.
 
 authenticator. For all the other authenticators, this function is called after
 they have authenticated, to enable additional authorization to be done.
 
@@ -31,13 +31,41 @@ Returns:
 
 int
 auth_check_serv_cond(auth_instance *ablock)
 
 int
 auth_check_serv_cond(auth_instance *ablock)
+{
+  return auth_check_some_cond(ablock,
+      US"server_condition", ablock->server_condition, OK);
+}
+
+
+/*************************************************
+*         Check some server condition            *
+*************************************************/
+
+/* This underlies server_condition, but is also used for some more generic
+ checks.
+
+Arguments:
+  ablock     the authenticator's instance block
+  label      debugging label naming the string checked
+  condition  the condition string to be expanded and checked
+  unset      value to return on NULL condition
+
+Returns:
+  OK          success (or unset=OK)
+  DEFER       couldn't complete the check
+  FAIL        authentication failed
+*/
+
+int
+auth_check_some_cond(auth_instance *ablock,
+    uschar *label, uschar *condition, int unset)
 {
 uschar *cond;
 
 HDEBUG(D_auth)
   {
   int i;
 {
 uschar *cond;
 
 HDEBUG(D_auth)
   {
   int i;
-  debug_printf("%s authenticator:\n", ablock->name);
+  debug_printf("%s authenticator %s:\n", ablock->name, label);
   for (i = 0; i < AUTH_VARS; i++)
     {
     if (auth_vars[i] != NULL)
   for (i = 0; i < AUTH_VARS; i++)
     {
     if (auth_vars[i] != NULL)
@@ -51,8 +79,13 @@ HDEBUG(D_auth)
 /* For the plaintext authenticator, server_condition is never NULL. For the
 rest, an unset condition lets everything through. */
 
 /* For the plaintext authenticator, server_condition is never NULL. For the
 rest, an unset condition lets everything through. */
 
-if (ablock->server_condition == NULL) return OK;
-cond = expand_string(ablock->server_condition);
+/* For server_condition, an unset condition lets everything through.
+For plaintext/gsasl authenticators, it will have been pre-checked to prevent
+this.  We return the unset scenario value given to us, which for
+server_condition will be OK and otherwise will typically be FAIL. */
+
+if (condition == NULL) return unset;
+cond = expand_string(condition);
 
 HDEBUG(D_auth)
   {
 
 HDEBUG(D_auth)
   {
index fea1def38392db142e64a48b94aab91c07a9c3db..df7abc9281c516e913feaf05100bf5ff185bae1d 100644 (file)
@@ -113,7 +113,7 @@ if(ob->server_mech == NULL)
  * authenticator of type whatever mechanism we're using
  */
 
  * authenticator of type whatever mechanism we're using
  */
 
-cbs[0].proc = &mysasl_config;
+cbs[0].proc = (int(*)(void))&mysasl_config;
 cbs[0].context = ob->server_mech;
 
 rc=sasl_server_init(cbs, "exim");
 cbs[0].context = ob->server_mech;
 
 rc=sasl_server_init(cbs, "exim");
index 7e62e63aa0d835557fbc2b04dffbd5f353c92079..031e783ed90072983202a29f6be71f49728ad107 100644 (file)
@@ -31,5 +31,6 @@ extern void auth_cyrus_sasl_init(auth_instance *);
 extern int auth_cyrus_sasl_server(auth_instance *, uschar *);
 extern int auth_cyrus_sasl_client(auth_instance *, smtp_inblock *,
                                 smtp_outblock *, int, uschar *, int);
 extern int auth_cyrus_sasl_server(auth_instance *, uschar *);
 extern int auth_cyrus_sasl_client(auth_instance *, smtp_inblock *,
                                 smtp_outblock *, int, uschar *, int);
+extern void auth_cyrus_sasl_version_report(FILE *f);
 
 /* End of cyrus_sasl.h */
 
 /* End of cyrus_sasl.h */
index 4055c04113e353d6832c50dfba6553cd0cfbf535..ea5fd6f6dc3db168814a6198fb50c412d9e58141 100644 (file)
@@ -14,8 +14,8 @@
 
 /* This function is used by authentication drivers to output a challenge
 to the SMTP client and read the response line. This version does not use base
 
 /* This function is used by authentication drivers to output a challenge
 to the SMTP client and read the response line. This version does not use base
-64 encoding for the text on the 334 line. It is used by the SPA and dovecot
-authenticators.
+64 encoding for the text on the 334 line. It is used by the SPA, dovecot
+and gsasl authenticators.
 
 Arguments:
    aptr       set to point to the response (which is in big_buffer)
 
 Arguments:
    aptr       set to point to the response (which is in big_buffer)
diff --git a/src/src/auths/gsasl_exim.c b/src/src/auths/gsasl_exim.c
new file mode 100644 (file)
index 0000000..e88bd25
--- /dev/null
@@ -0,0 +1,587 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Copyright (c) Twitter Inc 2012 */
+
+/* Interface to GNU SASL library for generic authentication. */
+
+/* Trade-offs:
+
+GNU SASL does not provide authentication data itself, so we have to expose
+that decision to configuration.  For some mechanisms, we need to act much
+like plaintext.  For others, we only need to be able to provide some
+evaluated data on demand.  There's no abstracted way (ie, without hardcoding
+knowledge of authenticators here) to know which need what properties; we
+can't query a session or the library for "we will need these for mechanism X".
+
+So: we always require server_condition, even if sometimes it will just be
+set as "yes".  We do provide a number of other hooks, which might not make
+sense in all contexts.  For some, we can do checks at init time.
+*/
+
+#include "../exim.h"
+
+#ifndef AUTH_GSASL
+/* dummy function to satisfy compilers when we link in an "empty" file. */
+static void dummy(int x) { dummy(x-1); }
+#else
+
+#include <gsasl.h>
+#include "gsasl_exim.h"
+
+/* Authenticator-specific options. */
+/* I did have server_*_condition options for various mechanisms, but since
+we only ever handle one mechanism at a time, I didn't see the point in keeping
+that.  In case someone sees a point, I've left the condition_check() API
+alone. */
+optionlist auth_gsasl_options[] = {
+  { "server_channelbinding", opt_bool,
+      (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) },
+  { "server_hostname",      opt_stringptr,
+      (void *)(offsetof(auth_gsasl_options_block, server_hostname)) },
+  { "server_mech",          opt_stringptr,
+      (void *)(offsetof(auth_gsasl_options_block, server_mech)) },
+  { "server_password",      opt_stringptr,
+      (void *)(offsetof(auth_gsasl_options_block, server_password)) },
+  { "server_realm",         opt_stringptr,
+      (void *)(offsetof(auth_gsasl_options_block, server_realm)) },
+  { "server_scram_iter",    opt_stringptr,
+      (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) },
+  { "server_scram_salt",    opt_stringptr,
+      (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) },
+  { "server_service",       opt_stringptr,
+      (void *)(offsetof(auth_gsasl_options_block, server_service)) }
+};
+/* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing
+hooks to avoid cleartext passwords in the Exim server. */
+
+int auth_gsasl_options_count =
+  sizeof(auth_gsasl_options)/sizeof(optionlist);
+
+/* Defaults for the authenticator-specific options. */
+auth_gsasl_options_block auth_gsasl_option_defaults = {
+  US"smtp",                 /* server_service */
+  US"$primary_hostname",    /* server_hostname */
+  NULL,                     /* server_realm */
+  NULL,                     /* server_mech */
+  NULL,                     /* server_password */
+  NULL,                     /* server_scram_iter */
+  NULL,                     /* server_scram_salt */
+  FALSE                     /* server_channelbinding */
+};
+
+/* "Globals" for managing the gsasl interface. */
+
+static Gsasl *gsasl_ctx = NULL;
+static int
+  main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop);
+static int
+  server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
+static int
+  client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock);
+
+static BOOL sasl_error_should_defer = FALSE;
+static Gsasl_property callback_loop = 0;
+static BOOL checked_server_condition = FALSE;
+
+enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 };
+
+struct callback_exim_state {
+  auth_instance *ablock;
+  int currently;
+};
+
+
+/*************************************************
+*          Initialization entry point            *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to
+enable consistency checks to be done, or anything else that needs
+to be set up. */
+
+void
+auth_gsasl_init(auth_instance *ablock)
+{
+  char *p;
+  int rc, supported;
+  auth_gsasl_options_block *ob =
+    (auth_gsasl_options_block *)(ablock->options_block);
+
+  /* As per existing Cyrus glue, use the authenticator's public name as
+  the default for the mechanism name; we don't handle multiple mechanisms
+  in one authenticator, but the same driver can be used multiple times. */
+
+  if (ob->server_mech == NULL)
+    ob->server_mech = string_copy(ablock->public_name);
+
+  /* Can get multiple session contexts from one library context, so just
+  initialise the once. */
+  if (gsasl_ctx == NULL) {
+    rc = gsasl_init(&gsasl_ctx);
+    if (rc != GSASL_OK) {
+      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+                "couldn't initialise GNU SASL library: %s (%s)",
+                ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
+    }
+    gsasl_callback_set(gsasl_ctx, main_callback);
+  }
+
+  /* We don't need this except to log it for debugging. */
+  rc = gsasl_server_mechlist(gsasl_ctx, &p);
+  if (rc != GSASL_OK)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+              "failed to retrieve list of mechanisms: %s (%s)",
+              ablock->name,  gsasl_strerror_name(rc), gsasl_strerror(rc));
+  HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p);
+
+  supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech);
+  if (!supported)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+              "GNU SASL does not support mechanism \"%s\"",
+              ablock->name, ob->server_mech);
+
+  if ((ablock->server_condition == NULL) &&
+      (strcmpic(ob->server_mech, US"EXTERNAL") ||
+       strcmpic(ob->server_mech, US"ANONYMOUS") ||
+       strcmpic(ob->server_mech, US"PLAIN") ||
+       strcmpic(ob->server_mech, US"LOGIN")))
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+              "Need server_condition for %s mechanism",
+              ablock->name, ob->server_mech);
+
+  /* At present, for mechanisms we don't panic on absence of server_condition;
+  need to figure out the most generically correct approach to deciding when
+  it's critical and when it isn't.  Eg, for simple validation (PLAIN mechanism,
+  etc) it clearly is critical.
+
+  So don't activate without server_condition, this might be relaxed in the future.
+  */
+  if (ablock->server_condition != NULL) ablock->server = TRUE;
+  ablock->client = FALSE;
+}
+
+
+/* GNU SASL uses one top-level callback, registered at library level.
+We dispatch to client and server functions instead. */
+
+static int
+main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
+{
+  int rc = 0;
+  struct callback_exim_state *cb_state =
+    (struct callback_exim_state *)gsasl_session_hook_get(sctx);
+
+  HDEBUG(D_auth) debug_printf("Callback entered, prop=%d (loop prop=%d)\n",
+      prop, callback_loop);
+
+  if (cb_state == NULL) {
+    HDEBUG(D_auth) debug_printf("  not from our server/client processing.\n");
+    return GSASL_NO_CALLBACK;
+  }
+
+  if (callback_loop > 0) {
+    /* Most likely is that we were asked for property foo, and to
+    expand the string we asked for property bar to put into an auth
+    variable, but property bar is not supplied for this mechanism. */
+    HDEBUG(D_auth)
+      debug_printf("Loop, asked for property %d while handling property %d\n",
+          prop, callback_loop);
+    return GSASL_NO_CALLBACK;
+  }
+  callback_loop = prop;
+
+  if (cb_state->currently == CURRENTLY_CLIENT)
+    rc = client_callback(ctx, sctx, prop, cb_state->ablock);
+  else if (cb_state->currently == CURRENTLY_SERVER)
+    rc = server_callback(ctx, sctx, prop, cb_state->ablock);
+  else {
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+        "unhandled callback state, bug in Exim", cb_state->ablock->name);
+    /* NOTREACHED */
+  }
+
+  callback_loop = 0;
+  return rc;
+}
+
+
+/*************************************************
+*             Server entry point                 *
+*************************************************/
+
+/* For interface, see auths/README */
+
+int
+auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
+{
+  char *tmps;
+  char *to_send, *received;
+  Gsasl_session *sctx = NULL;
+  auth_gsasl_options_block *ob =
+    (auth_gsasl_options_block *)(ablock->options_block);
+  struct callback_exim_state cb_state;
+  int rc, auth_result, exim_error, exim_error_override;
+
+  HDEBUG(D_auth)
+    debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n",
+        ablock->name, ob->server_mech);
+
+  rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx);
+  if (rc != GSASL_OK) {
+    auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
+        gsasl_strerror_name(rc), gsasl_strerror(rc));
+    HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
+    return DEFER;
+  }
+  /* Hereafter: gsasl_finish(sctx) please */
+
+  gsasl_session_hook_set(sctx, (void *)ablock);
+  cb_state.ablock = ablock;
+  cb_state.currently = CURRENTLY_SERVER;
+  gsasl_session_hook_set(sctx, (void *)&cb_state);
+
+  tmps = CS expand_string(ob->server_service);
+  gsasl_property_set(sctx, GSASL_SERVICE, tmps);
+  tmps = CS expand_string(ob->server_hostname);
+  gsasl_property_set(sctx, GSASL_HOSTNAME, tmps);
+  if (ob->server_realm) {
+    tmps = CS expand_string(ob->server_realm);
+    if (tmps && *tmps) {
+      gsasl_property_set(sctx, GSASL_REALM, tmps);
+    }
+  }
+  /* We don't support protection layers. */
+  gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
+#ifdef SUPPORT_TLS
+  if (tls_channelbinding_b64) {
+    /* Some auth mechanisms can ensure that both sides are talking withing the
+    same security context; for TLS, this means that even if a bad certificate
+    has been accepted, they remain MitM-proof because both sides must be within
+    the same negotiated session; if someone is terminating one sesson and
+    proxying data on within a second, authentication will fail.
+
+    We might not have this available, depending upon TLS implementation,
+    ciphersuite, phase of moon ...
+
+    If we do, it results in extra SASL mechanisms being available; here,
+    Exim's one-mechanism-per-authenticator potentially causes problems.
+    It depends upon how GNU SASL will implement the PLUS variants of GS2
+    and whether it automatically mandates a switch to the bound PLUS
+    if the data is available.  Since default-on, despite being more secure,
+    would then result in mechanism name changes on a library update, we
+    have little choice but to default it off and let the admin choose to
+    enable it.  *sigh*
+    */
+    if (ob->server_channelbinding) {
+      HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
+          ablock->name);
+      gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE,
+          (const char *) tls_channelbinding_b64);
+    } else {
+      HDEBUG(D_auth)
+        debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
+            ablock->name);
+    }
+  } else {
+    HDEBUG(D_auth)
+      debug_printf("Auth %s: no channel-binding data available\n",
+          ablock->name);
+  }
+#endif
+
+  checked_server_condition = FALSE;
+
+  received = CS initial_data;
+  to_send = NULL;
+  exim_error = exim_error_override = OK;
+
+  do {
+    rc = gsasl_step64(sctx, received, &to_send);
+
+    switch (rc) {
+      case GSASL_OK:
+        goto STOP_INTERACTION;
+
+      case GSASL_NEEDS_MORE:
+        break;
+
+      case GSASL_AUTHENTICATION_ERROR:
+      case GSASL_INTEGRITY_ERROR:
+      case GSASL_NO_AUTHID:
+      case GSASL_NO_ANONYMOUS_TOKEN:
+      case GSASL_NO_AUTHZID:
+      case GSASL_NO_PASSWORD:
+      case GSASL_NO_PASSCODE:
+      case GSASL_NO_PIN:
+      case GSASL_BASE64_ERROR:
+        HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n",
+            gsasl_strerror_name(rc), gsasl_strerror(rc));
+        log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
+            "GNU SASL permanent failure: %s (%s)",
+            ablock->name, ob->server_mech,
+            gsasl_strerror_name(rc), gsasl_strerror(rc));
+        if (rc == GSASL_BASE64_ERROR)
+          exim_error_override = BAD64;
+        goto STOP_INTERACTION;
+
+      default:
+        auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)",
+            gsasl_strerror_name(rc), gsasl_strerror(rc));
+        HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
+        exim_error_override = DEFER;
+        goto STOP_INTERACTION;
+    }
+
+    exim_error =
+      auth_get_no64_data((uschar **)&received, (uschar *)to_send);
+    if (exim_error)
+      break; /* handles * cancelled check */
+
+  } while (rc == GSASL_NEEDS_MORE);
+
+STOP_INTERACTION:
+  auth_result = rc;
+
+  gsasl_finish(sctx);
+
+  /* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */
+
+  if (exim_error != OK)
+    return exim_error;
+
+  if (auth_result != GSASL_OK) {
+    HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n",
+        gsasl_strerror_name(auth_result), gsasl_strerror(auth_result));
+    if (exim_error_override != OK)
+      return exim_error_override; /* might be DEFER */
+    if (sasl_error_should_defer) /* overriding auth failure SASL error */
+      return DEFER;
+    return FAIL;
+  }
+
+  /* Auth succeeded, check server_condition unless already done in callback */
+  return checked_server_condition ? OK : auth_check_serv_cond(ablock);
+}
+
+/* returns the GSASL status of expanding the Exim string given */
+static int
+condition_check(auth_instance *ablock, uschar *label, uschar *condition_string)
+{
+  int exim_rc;
+
+  exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL);
+
+  if (exim_rc == OK) {
+    return GSASL_OK;
+  } else if (exim_rc == DEFER) {
+    sasl_error_should_defer = TRUE;
+    return GSASL_AUTHENTICATION_ERROR;
+  } else if (exim_rc == FAIL) {
+    return GSASL_AUTHENTICATION_ERROR;
+  }
+
+  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+            "Unhandled return from checking %s: %d",
+            ablock->name, label, exim_rc);
+  /* NOTREACHED */
+  return GSASL_AUTHENTICATION_ERROR;
+}
+
+static int
+server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
+{
+  char *tmps;
+  uschar *propval;
+  int cbrc = GSASL_NO_CALLBACK;
+  int i;
+  auth_gsasl_options_block *ob =
+    (auth_gsasl_options_block *)(ablock->options_block);
+
+  HDEBUG(D_auth)
+    debug_printf("GNU SASL callback %d for %s/%s as server\n",
+        prop, ablock->name, ablock->public_name);
+
+  for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
+  expand_nmax = 0;
+
+  switch (prop) {
+    case GSASL_VALIDATE_SIMPLE:
+      /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHID);
+      auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
+      auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_PASSWORD);
+      auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
+      expand_nmax = 3;
+      for (i = 1; i <= 3; ++i)
+        expand_nlength[i] = Ustrlen(expand_nstring[i]);
+
+      cbrc = condition_check(ablock, US"server_condition", ablock->server_condition);
+      checked_server_condition = TRUE;
+      break;
+
+    case GSASL_VALIDATE_EXTERNAL:
+      if (ablock->server_condition == NULL) {
+        HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL.\n");
+        cbrc = GSASL_AUTHENTICATION_ERROR;
+        break;
+      }
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
+      /* We always set $auth1, even if only to empty string. */
+      auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+      expand_nlength[1] = Ustrlen(expand_nstring[1]);
+      expand_nmax = 1;
+
+      cbrc = condition_check(ablock,
+          US"server_condition (EXTERNAL)", ablock->server_condition);
+      checked_server_condition = TRUE;
+      break;
+
+    case GSASL_VALIDATE_ANONYMOUS:
+      if (ablock->server_condition == NULL) {
+        HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS.\n");
+        cbrc = GSASL_AUTHENTICATION_ERROR;
+        break;
+      }
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_ANONYMOUS_TOKEN);
+      /* We always set $auth1, even if only to empty string. */
+      auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+      expand_nlength[1] = Ustrlen(expand_nstring[1]);
+      expand_nmax = 1;
+
+      cbrc = condition_check(ablock,
+          US"server_condition (ANONYMOUS)", ablock->server_condition);
+      checked_server_condition = TRUE;
+      break;
+
+    case GSASL_VALIDATE_GSSAPI:
+      /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME */
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
+      auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_GSSAPI_DISPLAY_NAME);
+      auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
+      expand_nmax = 2;
+      for (i = 1; i <= 2; ++i)
+        expand_nlength[i] = Ustrlen(expand_nstring[i]);
+
+      /* In this one case, it perhaps makes sense to default back open?
+      But for consistency, let's just mandate server_condition here too. */
+      cbrc = condition_check(ablock,
+          US"server_condition (GSSAPI family)", ablock->server_condition);
+      checked_server_condition = TRUE;
+      break;
+
+    case GSASL_PASSWORD:
+      /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM
+         CRAM-MD5: GSASL_AUTHID
+         PLAIN: GSASL_AUTHID and GSASL_AUTHZID
+         LOGIN: GSASL_AUTHID
+       */
+      if (ob->server_scram_iter) {
+        tmps = CS expand_string(ob->server_scram_iter);
+        gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps);
+      }
+      if (ob->server_scram_salt) {
+        tmps = CS expand_string(ob->server_scram_salt);
+        gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps);
+      }
+      /* Asking for GSASL_AUTHZID will probably call back into us.
+      Do we really want to hardcode limits per mechanism?  What happens when
+      a new mechanism is added to the library.  It *shouldn't* result in us
+      needing to add more glue, since avoiding that is a large part of the
+      point of SASL. */
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHID);
+      auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
+      auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
+      propval = (uschar *) gsasl_property_get(sctx, GSASL_REALM);
+      auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
+      expand_nmax = 3;
+      for (i = 1; i <= 3; ++i)
+        expand_nlength[i] = Ustrlen(expand_nstring[i]);
+
+      tmps = CS expand_string(ob->server_password);
+      if (tmps == NULL) {
+        sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE;
+        HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
+            "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
+        return GSASL_AUTHENTICATION_ERROR;
+      }
+      gsasl_property_set(sctx, GSASL_PASSWORD, tmps);
+      /* This is inadequate; don't think Exim's store stacks are geared
+      for memory wiping, so expanding strings will leave stuff laying around.
+      But no need to compound the problem, so get rid of the one we can. */
+      memset(tmps, '\0', strlen(tmps));
+      cbrc = GSASL_OK;
+      break;
+
+    default:
+      HDEBUG(D_auth) debug_printf("Unrecognised callback: %d\n", prop);
+      cbrc = GSASL_NO_CALLBACK;
+  }
+
+  HDEBUG(D_auth) debug_printf("Returning %s (%s)\n",
+      gsasl_strerror_name(cbrc), gsasl_strerror(cbrc));
+
+  return cbrc;
+}
+
+
+/*************************************************
+*              Client entry point                *
+*************************************************/
+
+/* For interface, see auths/README */
+
+int
+auth_gsasl_client(
+  auth_instance *ablock,                 /* authenticator block */
+  smtp_inblock *inblock,                 /* connection inblock */
+  smtp_outblock *outblock,               /* connection outblock */
+  int timeout,                           /* command timeout */
+  uschar *buffer,                        /* buffer for reading response */
+  int buffsize)                          /* size of buffer */
+{
+  HDEBUG(D_auth)
+    debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
+  /* NOT IMPLEMENTED */
+  return FAIL;
+}
+
+static int
+client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
+{
+  int cbrc = GSASL_NO_CALLBACK;
+  HDEBUG(D_auth)
+    debug_printf("GNU SASL callback %d for %s/%s as client\n",
+        prop, ablock->name, ablock->public_name);
+
+  HDEBUG(D_auth)
+    debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
+
+  return cbrc;
+}
+
+/*************************************************
+*                Diagnostic API                  *
+*************************************************/
+
+void
+auth_gsasl_version_report(FILE *f)
+{
+  const char *runtime;
+  runtime = gsasl_check_version(NULL);
+  fprintf(f, "Library version: GNU SASL: Compile: %s\n"
+             "                           Runtime: %s\n",
+          GSASL_VERSION, runtime);
+}
+
+#endif  /* AUTH_GSASL */
+
+/* End of gsasl_exim.c */
diff --git a/src/src/auths/gsasl_exim.h b/src/src/auths/gsasl_exim.h
new file mode 100644 (file)
index 0000000..785b853
--- /dev/null
@@ -0,0 +1,42 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Copyright (c) Twitter Inc 2012 */
+
+/* Interface to GNU SASL library for generic authentication. */
+
+/* Authenticator-specific options. */
+
+typedef struct {
+  uschar *server_service;
+  uschar *server_hostname;
+  uschar *server_realm;
+  uschar *server_mech;
+  uschar *server_password;
+  uschar *server_scram_iter;
+  uschar *server_scram_salt;
+  BOOL    server_channelbinding;
+} auth_gsasl_options_block;
+
+/* Data for reading the authenticator-specific options. */
+
+extern optionlist auth_gsasl_options[];
+extern int auth_gsasl_options_count;
+
+/* Defaults for the authenticator-specific options. */
+
+extern auth_gsasl_options_block auth_gsasl_option_defaults;
+
+/* The entry points for the mechanism */
+
+extern void auth_gsasl_init(auth_instance *);
+extern int auth_gsasl_server(auth_instance *, uschar *);
+extern int auth_gsasl_client(auth_instance *, smtp_inblock *,
+                                smtp_outblock *, int, uschar *, int);
+extern void auth_gsasl_version_report(FILE *f);
+
+/* End of gsasl_exim.h */
index bc983c444e5007608a88e4319c81fcc6c12cc666..c562ee98ccf4b518e35c1b745e0a280b6e83c340 100644 (file)
@@ -20,6 +20,7 @@ it's a default value. */
 #define AUTH_CRAM_MD5
 #define AUTH_CYRUS_SASL
 #define AUTH_DOVECOT
 #define AUTH_CRAM_MD5
 #define AUTH_CYRUS_SASL
 #define AUTH_DOVECOT
+#define AUTH_GSASL
 #define AUTH_PLAINTEXT
 #define AUTH_SPA
 
 #define AUTH_PLAINTEXT
 #define AUTH_SPA
 
index 37ecf4f4b8cf92790a6fd3dfe292351538b82c79..6e42ef94fcfa4d4f1a5c6c5f598c027a445b7489 100644 (file)
@@ -37,6 +37,10 @@ set to NULL for those that are not compiled into the binary. */
 #include "auths/dovecot.h"
 #endif
 
 #include "auths/dovecot.h"
 #endif
 
+#ifdef AUTH_GSASL
+#include "auths/gsasl_exim.h"
+#endif
+
 #ifdef AUTH_PLAINTEXT
 #include "auths/plaintext.h"
 #endif
 #ifdef AUTH_PLAINTEXT
 #include "auths/plaintext.h"
 #endif
@@ -58,7 +62,8 @@ auth_info auths_available[] = {
   sizeof(auth_cram_md5_options_block),
   auth_cram_md5_init,                        /* init function */
   auth_cram_md5_server,                      /* server function */
   sizeof(auth_cram_md5_options_block),
   auth_cram_md5_init,                        /* init function */
   auth_cram_md5_server,                      /* server function */
-  auth_cram_md5_client                       /* client function */
+  auth_cram_md5_client,                      /* client function */
+  NULL                                       /* diagnostic function */
   },
 #endif
 
   },
 #endif
 
@@ -71,7 +76,8 @@ auth_info auths_available[] = {
   sizeof(auth_cyrus_sasl_options_block),
   auth_cyrus_sasl_init,                      /* init function */
   auth_cyrus_sasl_server,                    /* server function */
   sizeof(auth_cyrus_sasl_options_block),
   auth_cyrus_sasl_init,                      /* init function */
   auth_cyrus_sasl_server,                    /* server function */
-  NULL                                       /* client function */
+  NULL,                                      /* client function */
+  auth_cyrus_sasl_version_report             /* diagnostic function */
   },
 #endif
 
   },
 #endif
 
@@ -84,7 +90,22 @@ auth_info auths_available[] = {
   sizeof(auth_dovecot_options_block),
   auth_dovecot_init,                          /* init function */
   auth_dovecot_server,                        /* server function */
   sizeof(auth_dovecot_options_block),
   auth_dovecot_init,                          /* init function */
   auth_dovecot_server,                        /* server function */
-  NULL                                        /* client function */
+  NULL,                                       /* client function */
+  NULL                                        /* diagnostic function */
+  },
+#endif
+
+#ifdef AUTH_GSASL
+  {
+  US"gsasl",                                  /* lookup name */
+  auth_gsasl_options,
+  &auth_gsasl_options_count,
+  &auth_gsasl_option_defaults,
+  sizeof(auth_gsasl_options_block),
+  auth_gsasl_init,                            /* init function */
+  auth_gsasl_server,                          /* server function */
+  NULL,                                       /* client function */
+  auth_gsasl_version_report                   /* diagnostic function */
   },
 #endif
 
   },
 #endif
 
@@ -97,7 +118,8 @@ auth_info auths_available[] = {
   sizeof(auth_plaintext_options_block),
   auth_plaintext_init,                       /* init function */
   auth_plaintext_server,                     /* server function */
   sizeof(auth_plaintext_options_block),
   auth_plaintext_init,                       /* init function */
   auth_plaintext_server,                     /* server function */
-  auth_plaintext_client                      /* client function */
+  auth_plaintext_client,                     /* client function */
+  NULL                                       /* diagnostic function */
   },
 #endif
 
   },
 #endif
 
@@ -110,11 +132,12 @@ auth_info auths_available[] = {
   sizeof(auth_spa_options_block),
   auth_spa_init,                             /* init function */
   auth_spa_server,                           /* server function */
   sizeof(auth_spa_options_block),
   auth_spa_init,                             /* init function */
   auth_spa_server,                           /* server function */
-  auth_spa_client                            /* client function */
+  auth_spa_client,                           /* client function */
+  NULL                                       /* diagnostic function */
   },
 #endif
 
   },
 #endif
 
-{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL  }
+{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL  }
 };
 
 
 };
 
 
index a6c0d7832fbc6aa7a0a8c15785e03dfec3d16dcc..b4ea01dcd35fa64cd6adfb4a8ddabc9848bc12e6 100644 (file)
@@ -725,6 +725,8 @@ Returns:    nothing
 static void
 show_whats_supported(FILE *f)
 {
 static void
 show_whats_supported(FILE *f)
 {
+  auth_info *authi;
+
 #ifdef DB_VERSION_STRING
 fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
 #elif defined(BTREEVERSION) && defined(HASHVERSION)
 #ifdef DB_VERSION_STRING
 fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
 #elif defined(BTREEVERSION) && defined(HASHVERSION)
@@ -867,6 +869,9 @@ fprintf(f, "Authenticators:");
 #ifdef AUTH_DOVECOT
   fprintf(f, " dovecot");
 #endif
 #ifdef AUTH_DOVECOT
   fprintf(f, " dovecot");
 #endif
+#ifdef AUTH_GSASL
+  fprintf(f, " gsasl");
+#endif
 #ifdef AUTH_PLAINTEXT
   fprintf(f, " plaintext");
 #endif
 #ifdef AUTH_PLAINTEXT
   fprintf(f, " plaintext");
 #endif
@@ -962,9 +967,11 @@ DEBUG(D_any) do {
   tls_version_report(f);
 #endif
 
   tls_version_report(f);
 #endif
 
-#ifdef AUTH_CYRUS_SASL
-  auth_cyrus_sasl_version_report(f);
-#endif
+  for (authi = auths_available; *authi->driver_name != '\0'; ++authi) {
+    if (authi->version_report) {
+      (*authi->version_report)(f);
+    }
+  }
 
   fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
              "                       Runtime: %s\n",
 
   fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
              "                       Runtime: %s\n",
index a45ea0b30433a613353488cf5e7db27e661c34cb..626d33dae08a3da4efcaa3e1a9248085fd84be75 100644 (file)
@@ -538,12 +538,4 @@ default to EDQUOT if it exists, otherwise ENOSPC. */
   #endif
 #endif
 
   #endif
 #endif
 
-/* These are for reporting version information from various componenents, to
-figure out what's actually happening. They need to be available to the main
-function, so we declare them here. Unfortunate. */
-
-#ifdef AUTH_CYRUS_SASL
-extern void auth_cyrus_sasl_version_report(FILE *);
-#endif
-
 /* End of exim.h */
 /* End of exim.h */
index 5efcbbb0440457eb4171274ebb9b4c486bf33e09..d2575946b0e1d08e414279a3a513d51b9cfdb3b5 100644 (file)
@@ -54,6 +54,8 @@ extern int     auth_call_radius(uschar *, uschar **);
 extern int     auth_call_saslauthd(uschar *, uschar *, uschar *, uschar *,
                  uschar **);
 extern int     auth_check_serv_cond(auth_instance *);
 extern int     auth_call_saslauthd(uschar *, uschar *, uschar *, uschar *,
                  uschar **);
 extern int     auth_check_serv_cond(auth_instance *);
+extern int     auth_check_some_cond(auth_instance *, uschar *, uschar *, int);
+
 extern int     auth_get_data(uschar **, uschar *, int);
 extern int     auth_get_no64_data(uschar **, uschar *);
 extern uschar *auth_xtextencode(uschar *, int);
 extern int     auth_get_data(uschar **, uschar *, int);
 extern int     auth_get_no64_data(uschar **, uschar *);
 extern uschar *auth_xtextencode(uschar *, int);
index 4ed3950dfef60684b92031d810eb8e5deb0dda40..1066306ac2ed1add2f7faf4ec409af56032c1d8e 100644 (file)
@@ -90,6 +90,7 @@ extern uschar *openssl_options;        /* OpenSSL compatibility options */
 extern const pcre *regex_STARTTLS;     /* For recognizing STARTTLS settings */
 extern uschar *tls_advertise_hosts;    /* host for which TLS is advertised */
 extern uschar *tls_certificate;        /* Certificate file */
 extern const pcre *regex_STARTTLS;     /* For recognizing STARTTLS settings */
 extern uschar *tls_advertise_hosts;    /* host for which TLS is advertised */
 extern uschar *tls_certificate;        /* Certificate file */
+extern uschar *tls_channelbinding_b64; /* string of base64 channel binding */
 extern uschar *tls_crl;                /* CRL File */
 extern uschar *tls_dhparam;            /* DH param file */
 extern BOOL    tls_offered;            /* Server offered TLS */
 extern uschar *tls_crl;                /* CRL File */
 extern uschar *tls_dhparam;            /* DH param file */
 extern BOOL    tls_offered;            /* Server offered TLS */
@@ -101,6 +102,10 @@ extern uschar *tls_verify_certificates;/* Path for certificates to check */
 extern uschar *tls_verify_hosts;       /* Mandatory client verification */
 #endif
 
 extern uschar *tls_verify_hosts;       /* Mandatory client verification */
 #endif
 
+#ifdef USE_GNUTLS
+extern 
+#endif
+
 
 /* Input-reading functions for messages, so we can use special ones for
 incoming TCP/IP. */
 
 /* Input-reading functions for messages, so we can use special ones for
 incoming TCP/IP. */
index 3790c7fe878783dbab5b0f8db509213e9cb9a2ae..9b51d0b7c113b8e257e34260c3421236a8542625 100644 (file)
@@ -367,6 +367,8 @@ typedef struct auth_info {
     int,                          /* command timeout */
     uschar *,                     /* buffer for reading response */
     int);                         /* sizeof buffer */
     int,                          /* command timeout */
     uschar *,                     /* buffer for reading response */
     int);                         /* sizeof buffer */
+  void (*version_report)(         /* diagnostic version reporting */
+    FILE *);                      /* I/O stream to print to */
 } auth_info;
 
 
 } auth_info;
 
 
index f77768fb8bcad489fa18c1c2653fd66e0aa18102..2d1a327dea80307939e2fc23b0c9d5714fa2ae5c 100644 (file)
@@ -854,7 +854,10 @@ construct_cipher_name(gnutls_session session)
 {
 static uschar cipherbuf[256];
 uschar *ver;
 {
 static uschar cipherbuf[256];
 uschar *ver;
-int bits, c, kx, mac;
+int bits, c, kx, mac, rc;
+#ifdef GNUTLS_CB_TLS_UNIQUE
+gnutls_datum_t channel;
+#endif
 
 ver = string_copy(
   US gnutls_protocol_get_name(gnutls_protocol_get_version(session)));
 
 ver = string_copy(
   US gnutls_protocol_get_name(gnutls_protocol_get_version(session)));
@@ -871,6 +874,21 @@ string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
 tls_cipher = cipherbuf;
 
 DEBUG(D_tls) debug_printf("cipher: %s\n", cipherbuf);
 tls_cipher = cipherbuf;
 
 DEBUG(D_tls) debug_printf("cipher: %s\n", cipherbuf);
+
+if (tls_channelbinding_b64)
+  free(tls_channelbinding_b64);
+tls_channelbinding_b64 = NULL;
+
+#ifdef GNUTLS_CB_TLS_UNIQUE
+channel = { NULL, 0 };
+rc = gnutls_session_channel_binding(session, GNUTLS_CB_TLS_UNIQUE, &channel);
+if (rc) {
+  DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc));
+} else {
+  tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size);
+  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
+}
+#endif
 }
 
 
 }
 
 
index 7cb1550f35921dbc856ffe63834e7fef7ba43c2d..d975a2c89a64ec4340df2a65792138ec3442a9cc 100644 (file)
@@ -40,6 +40,7 @@ static int ssl_xfer_buffer_hwm = 0;
 static int ssl_xfer_eof = 0;
 static int ssl_xfer_error = 0;
 
 static int ssl_xfer_eof = 0;
 static int ssl_xfer_error = 0;
 
+uschar *tls_channelbinding_b64 = NULL;
 
 
 /*************************************************
 
 
 /*************************************************