]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
openldap: implement SASL authentication
authorPatrick Monnerat <patrick@monnerat.net>
Mon, 10 Jan 2022 10:57:02 +0000 (11:57 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 11 Jan 2022 10:17:27 +0000 (11:17 +0100)
As credentials can be quite different depending on the mechanism used,
there are no default mechanisms for LDAP and simple bind with a DN is
then used.

The caller has to provide mechanism(s) using CURLOPT_LOGIN_OPTIONS to
enable SASL authentication and disable simple bind.

Closes #8152

docs/cmdline-opts/login-options.d
docs/cmdline-opts/oauth2-bearer.d
docs/libcurl/curl_version_info.3
docs/libcurl/opts/CURLOPT_LOGIN_OPTIONS.3
docs/libcurl/opts/CURLOPT_SASL_AUTHZID.3
docs/libcurl/opts/CURLOPT_SERVICE_NAME.3
docs/libcurl/opts/CURLOPT_XOAUTH2_BEARER.3
lib/openldap.c

index 4c53db3fdaf9274038772b67679d6f732d077c82..b524ebb0a7b6eea06bad93a10e96d6156b2ea45a 100644 (file)
@@ -1,6 +1,6 @@
 Long: login-options
 Arg: <options>
-Protocols: IMAP POP3 SMTP
+Protocols: IMAP LDAP POP3 SMTP
 Help: Server login options
 Added: 7.34.0
 Category: imap pop3 smtp auth
index e5ed0e87f63d95f731c1f07f784543ec23520017..07bb54bde0543834adb3c0e92643e113ceede14a 100644 (file)
@@ -1,7 +1,7 @@
 Long: oauth2-bearer
 Help: OAuth 2 Bearer Token
 Arg: <token>
-Protocols: IMAP POP3 SMTP HTTP
+Protocols: IMAP LDAP POP3 SMTP HTTP
 Category: auth
 Example: --oauth2-bearer "mF_9.B5f-4.1JqM" $URL
 Added: 7.33.0
index 7fb494b0ccd7e3b1d273eb7b13a93d409d1280b4..d8f4b805b41ad179bc24bf3a29534681a10a1130 100644 (file)
@@ -5,7 +5,7 @@
 .\" *                            | (__| |_| |  _ <| |___
 .\" *                             \___|\___/|_| \_\_____|
 .\" *
-.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
 .\" *
 .\" * This software is licensed as described in the file COPYING, which
 .\" * you should have received as part of this distribution. The terms
@@ -172,8 +172,8 @@ supports IPv6
 .IP CURL_VERSION_KERBEROS4
 supports Kerberos V4 (when using FTP). Legacy bit. Deprecated since 7.33.0.
 .IP CURL_VERSION_KERBEROS5
-supports Kerberos V5 authentication for FTP, IMAP, POP3, SMTP and SOCKSv5 proxy
-(Added in 7.40.0)
+supports Kerberos V5 authentication for FTP, IMAP, LDAP, POP3, SMTP and
+SOCKSv5 proxy. (Added in 7.40.0)
 .IP CURL_VERSION_LARGEFILE
 libcurl was built with support for large files. (Added in 7.11.1)
 .IP CURL_VERSION_UNICODE
index 5bb997cb61aecee9e98f25e4755b03e231c07cac..5ec9421744a2143de16f1b451d54630f72070857 100644 (file)
@@ -5,7 +5,7 @@
 .\" *                            | (__| |_| |  _ <| |___
 .\" *                             \___|\___/|_| \_\_____|
 .\" *
-.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
 .\" *
 .\" * This software is licensed as described in the file COPYING, which
 .\" * you should have received as part of this distribution. The terms
@@ -46,7 +46,7 @@ option.
 .SH DEFAULT
 NULL
 .SH PROTOCOLS
-Only IMAP, POP3 and SMTP support login options.
+Only IMAP, LDAP, POP3 and SMTP support login options.
 .SH EXAMPLE
 .nf
 CURL *curl = curl_easy_init();
@@ -58,7 +58,7 @@ if(curl) {
 }
 .fi
 .SH AVAILABILITY
-Added in 7.34.0
+Added in 7.34.0. Support for OpenLDAP added in 7.82.0.
 .SH RETURN VALUE
 Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or
 CURLE_OUT_OF_MEMORY if there was insufficient heap space.
index 87cd42859c63b1650e74a2a59379d2e7ab217fad..7468235ccd58db6dde4870851fc3308f2c55b859 100644 (file)
@@ -5,7 +5,7 @@
 .\" *                            | (__| |_| |  _ <| |___
 .\" *                             \___|\___/|_| \_\_____|
 .\" *
-.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
 .\" *
 .\" * This software is licensed as described in the file COPYING, which
 .\" * you should have received as part of this distribution. The terms
@@ -45,7 +45,7 @@ or a shared mailbox for example.
 .SH DEFAULT
 blank
 .SH PROTOCOLS
-IMAP, POP3 and SMTP
+IMAP, LDAP, POP3 and SMTP
 .SH EXAMPLE
 .nf
 CURL *curl = curl_easy_init();
@@ -59,7 +59,7 @@ if(curl) {
 }
 .fi
 .SH AVAILABILITY
-Added in 7.66.0
+Added in 7.66.0. Support for OpenLDAP added in 7.82.0.
 .SH RETURN VALUE
 Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
 .SH "SEE ALSO"
index 9246f82492f85a60e6ec9120db651535b6b11d5f..7fc2b0fd9d134f68681998ee2424ac9e3d1bc456 100644 (file)
@@ -5,7 +5,7 @@
 .\" *                            | (__| |_| |  _ <| |___
 .\" *                             \___|\___/|_| \_\_____|
 .\" *
-.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
 .\" *
 .\" * This software is licensed as described in the file COPYING, which
 .\" * you should have received as part of this distribution. The terms
@@ -32,15 +32,15 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SERVICE_NAME, char *name);
 .SH DESCRIPTION
 Pass a char * as parameter to a string holding the \fIname\fP of the service
 for DIGEST-MD5, SPNEGO and Kerberos 5 authentication mechanisms. The default
-service names are "ftp", "HTTP", "imap", "pop" and "smtp". This option allows
-you to change them.
+service names are "ftp", "HTTP", "imap", "ldap", "pop" and "smtp". This option
+allows you to change them.
 
 The application does not have to keep the string around after setting this
 option.
 .SH DEFAULT
 See above
 .SH PROTOCOLS
-HTTP, FTP, IMAP, POP and SMTP
+HTTP, FTP, IMAP, LDAP, POP3 and SMTP
 .SH EXAMPLE
 .nf
 CURL *curl = curl_easy_init();
@@ -52,7 +52,8 @@ if(curl) {
 }
 .fi
 .SH AVAILABILITY
-Added in 7.43.0 for HTTP, 7.49.0 for FTP, IMAP, POP3 and SMTP.
+Added in 7.43.0 for HTTP, 7.49.0 for FTP, IMAP, POP3 and SMTP,
+7.82.0 for OpenLDAP.
 .SH RETURN VALUE
 Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or
 CURLE_OUT_OF_MEMORY if there was insufficient heap space.
index 3583287f7a565cc3cea42d01d13542f7c191096b..22ae955011abfb7eafea00a8f4f1106f2d09d7d2 100644 (file)
@@ -5,7 +5,7 @@
 .\" *                            | (__| |_| |  _ <| |___
 .\" *                             \___|\___/|_| \_\_____|
 .\" *
-.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
 .\" *
 .\" * This software is licensed as described in the file COPYING, which
 .\" * you should have received as part of this distribution. The terms
@@ -31,18 +31,18 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_XOAUTH2_BEARER, char *token);
 .fi
 .SH DESCRIPTION
 Pass a char * as parameter, which should point to the null-terminated OAuth
-2.0 Bearer Access Token for use with HTTP, IMAP, POP3 and SMTP servers
+2.0 Bearer Access Token for use with HTTP, IMAP, LDAP, POP3 and SMTP servers
 that support the OAuth 2.0 Authorization Framework.
 
-Note: For IMAP, POP3 and SMTP, the user name used to generate the Bearer Token
-should be supplied via the \fICURLOPT_USERNAME(3)\fP option.
+Note: For IMAP, LDAP, POP3 and SMTP, the user name used to generate the
+Bearer Token should be supplied via the \fICURLOPT_USERNAME(3)\fP option.
 
 The application does not have to keep the string around after setting this
 option.
 .SH DEFAULT
 NULL
 .SH PROTOCOLS
-IMAP, POP3 and SMTP
+HTTP, IMAP, LDAP, POP3 and SMTP
 .SH EXAMPLE
 .nf
 CURL *curl = curl_easy_init();
@@ -54,7 +54,7 @@ if(curl) {
 }
 .fi
 .SH AVAILABILITY
-Added in 7.33.0
+Added in 7.33.0. Support for OpenLAP added in 7.82.0.
 .SH RETURN VALUE
 Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or
 CURLE_OUT_OF_MEMORY if there was insufficient heap space.
index 0ffb6a36a2acf3a4764b954c4ddd0d2666187fa3..2488d15eeb81c2b686f663d65255c4a709637f02 100644 (file)
@@ -5,7 +5,7 @@
  *                | (__| |_| |  _ <| |___
  *                 \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2011 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
  * Copyright (C) 2010, Howard Chu, <hyc@openldap.org>
  *
  * This software is licensed as described in the file COPYING, which
@@ -46,6 +46,8 @@
 #include "curl_ldap.h"
 #include "curl_base64.h"
 #include "connect.h"
+#include "curl_sasl.h"
+#include "strcase.h"
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
 #include "curl_memory.h"
@@ -76,6 +78,8 @@ typedef enum {
   OLDAP_SSL,            /* Performing SSL handshake. */
   OLDAP_STARTTLS,       /* STARTTLS request sent. */
   OLDAP_TLS,            /* Performing TLS handshake. */
+  OLDAP_MECHS,          /* Get SASL authentication mechanisms. */
+  OLDAP_SASL,           /* SASL binding reply. */
   OLDAP_BIND,           /* Simple bind reply. */
   OLDAP_BINDV2,         /* Simple bind reply in protocol version 2. */
   OLDAP_LAST            /* Never used */
@@ -96,6 +100,13 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done);
 static CURLcode oldap_disconnect(struct Curl_easy *data,
                                  struct connectdata *conn, bool dead);
 
+static CURLcode oldap_perform_auth(struct Curl_easy *data, const char *mech,
+                                   const struct bufref *initresp);
+static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech,
+                                    const struct bufref *resp);
+static CURLcode oldap_cancel_auth(struct Curl_easy *data, const char *mech);
+static CURLcode oldap_get_message(struct Curl_easy *data, struct bufref *out);
+
 static Curl_recv oldap_recv;
 
 /*
@@ -154,10 +165,26 @@ const struct Curl_handler Curl_handler_ldaps = {
 };
 #endif
 
+/* SASL parameters for the ldap protocol */
+static const struct SASLproto saslldap = {
+  "ldap",                     /* The service name */
+  oldap_perform_auth,         /* Send authentication command */
+  oldap_continue_auth,        /* Send authentication continuation */
+  oldap_cancel_auth,          /* Send authentication cancellation */
+  oldap_get_message,          /* Get SASL response message */
+  0,                          /* Maximum initial response length (no max) */
+  LDAP_SASL_BIND_IN_PROGRESS, /* Code received when continuation is expected */
+  LDAP_SUCCESS,               /* Code to receive upon authentication success */
+  SASL_AUTH_NONE,             /* Default mechanisms */
+  0                           /* Configuration flags */
+};
+
 struct ldapconninfo {
+  struct SASL sasl;          /* SASL-related parameters */
   LDAP *ld;                  /* Openldap connection handle. */
   Curl_recv *recv;           /* For stacking SSL handler */
   Curl_send *send;
+  struct berval *servercred; /* SASL data from server. */
   ldapstate state;           /* Current machine state. */
   int proto;                 /* LDAP_PROTO_TCP/LDAP_PROTO_UDP/LDAP_PROTO_IPC */
   int msgid;                 /* Current message id. */
@@ -184,6 +211,8 @@ static void state(struct Curl_easy *data, ldapstate newstate)
     "SSL",
     "STARTTLS",
     "TLS",
+    "MECHS",
+    "SASL",
     "BIND",
     "BINDV2",
     /* LAST */
@@ -251,6 +280,37 @@ static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp)
   return result;
 }
 
+/* Parse the login options. */
+static CURLcode oldap_parse_login_options(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct ldapconninfo *li = conn->proto.ldapc;
+  const char *ptr = conn->options;
+
+  while(!result && ptr && *ptr) {
+    const char *key = ptr;
+    const char *value;
+
+    while(*ptr && *ptr != '=')
+        ptr++;
+
+    value = ptr + 1;
+
+    while(*ptr && *ptr != ';')
+      ptr++;
+
+    if(checkprefix("AUTH=", key))
+      result = Curl_sasl_parse_url_auth_option(&li->sasl, value, ptr - value);
+    else
+      result = CURLE_SETOPT_OPTION_SYNTAX;
+
+    if(*ptr == ';')
+      ptr++;
+  }
+
+  return result == CURLE_URL_MALFORMAT? CURLE_SETOPT_OPTION_SYNTAX: result;
+}
+
 static CURLcode oldap_setup_connection(struct Curl_easy *data,
                                        struct connectdata *conn)
 {
@@ -271,14 +331,94 @@ static CURLcode oldap_setup_connection(struct Curl_easy *data,
       conn->proto.ldapc = li;
       connkeep(conn, "OpenLDAP default");
 
+      /* Initialize the SASL storage */
+      Curl_sasl_init(&li->sasl, data, &saslldap);
+
       /* Clear the TLS upgraded flag */
       conn->bits.tls_upgraded = FALSE;
+
+      result = oldap_parse_login_options(conn);
     }
   }
 
   return result;
 }
 
+/*
+ * Get the SASL authentication challenge from the server credential buffer.
+ */
+static CURLcode oldap_get_message(struct Curl_easy *data, struct bufref *out)
+{
+  struct berval *servercred = data->conn->proto.ldapc->servercred;
+
+  if(!servercred || !servercred->bv_val)
+    return CURLE_WEIRD_SERVER_REPLY;
+  Curl_bufref_set(out, servercred->bv_val, servercred->bv_len, NULL);
+  return CURLE_OK;
+}
+
+/*
+ * Sends an initial SASL bind request to the server.
+ */
+static CURLcode oldap_perform_auth(struct Curl_easy *data, const char *mech,
+                                   const struct bufref *initresp)
+{
+  struct connectdata *conn = data->conn;
+  struct ldapconninfo *li = conn->proto.ldapc;
+  CURLcode result = CURLE_OK;
+  struct berval cred;
+  struct berval *pcred = &cred;
+  int rc;
+
+  cred.bv_val = (char *) Curl_bufref_ptr(initresp);
+  cred.bv_len = Curl_bufref_len(initresp);
+  if(!cred.bv_val)
+    pcred = NULL;
+  rc = ldap_sasl_bind(li->ld, NULL, mech, pcred, NULL, NULL, &li->msgid);
+  if(rc != LDAP_SUCCESS)
+    result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND);
+  return result;
+}
+
+/*
+ * Sends SASL continuation.
+ */
+static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech,
+                                    const struct bufref *resp)
+{
+  struct connectdata *conn = data->conn;
+  struct ldapconninfo *li = conn->proto.ldapc;
+  CURLcode result = CURLE_OK;
+  struct berval cred;
+  struct berval *pcred = &cred;
+  int rc;
+
+  cred.bv_val = (char *) Curl_bufref_ptr(resp);
+  cred.bv_len = Curl_bufref_len(resp);
+  if(!cred.bv_val)
+    pcred = NULL;
+  rc = ldap_sasl_bind(li->ld, NULL, mech, pcred, NULL, NULL, &li->msgid);
+  if(rc != LDAP_SUCCESS)
+    result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND);
+  return result;
+}
+
+/*
+ * Sends SASL bind cancellation.
+ */
+static CURLcode oldap_cancel_auth(struct Curl_easy *data, const char *mech)
+{
+  struct ldapconninfo *li = data->conn->proto.ldapc;
+  CURLcode result = CURLE_OK;
+  int rc = ldap_sasl_bind(li->ld, NULL, LDAP_SASL_NULL, NULL, NULL, NULL,
+                          &li->msgid);
+
+  (void)mech;
+  if(rc != LDAP_SUCCESS)
+    result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND);
+  return result;
+}
+
 /* Starts LDAP simple bind. */
 static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate)
 {
@@ -309,6 +449,40 @@ static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate)
   return result;
 }
 
+/* Query the supported SASL authentication mechanisms. */
+static CURLcode oldap_perform_mechs(struct Curl_easy *data)
+{
+  CURLcode result = CURLE_OK;
+  struct ldapconninfo *li = data->conn->proto.ldapc;
+  int rc;
+  static const char * const supportedSASLMechanisms[] = {
+    "supportedSASLMechanisms",
+    NULL
+  };
+
+  rc = ldap_search_ext(li->ld, "", LDAP_SCOPE_BASE, "(objectclass=*)",
+                       (char **) supportedSASLMechanisms, 0,
+                       NULL, NULL, NULL, 0, &li->msgid);
+  if(rc == LDAP_SUCCESS)
+    state(data, OLDAP_MECHS);
+  else
+    result = oldap_map_error(rc, CURLE_LOGIN_DENIED);
+  return result;
+}
+
+/* Starts SASL bind. */
+static CURLcode oldap_perform_sasl(struct Curl_easy *data)
+{
+  saslprogress progress = SASL_IDLE;
+  struct ldapconninfo *li = data->conn->proto.ldapc;
+  CURLcode result = Curl_sasl_start(&li->sasl, data, TRUE, &progress);
+
+  state(data, OLDAP_SASL);
+  if(!result && progress != SASL_INPROGRESS)
+    result = CURLE_LOGIN_DENIED;
+  return result;
+}
+
 #ifdef USE_SSL
 static Sockbuf_IO ldapsb_tls;
 
@@ -414,11 +588,106 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done)
   }
 #endif
 
+  if(li->sasl.prefmech != SASL_AUTH_NONE)
+    return oldap_perform_mechs(data);
+
   /* Force bind even if anonymous bind is not needed in protocol version 3
      to detect missing version 3 support. */
   return oldap_perform_bind(data, OLDAP_BIND);
 }
 
+/* Handle the supported SASL mechanisms query response */
+static CURLcode oldap_state_mechs_resp(struct Curl_easy *data,
+                                       LDAPMessage *msg, int code)
+{
+  struct connectdata *conn = data->conn;
+  struct ldapconninfo *li = conn->proto.ldapc;
+  int rc;
+  BerElement *ber = NULL;
+  CURLcode result = CURLE_OK;
+  struct berval bv, *bvals;
+
+  switch(ldap_msgtype(msg)) {
+  case LDAP_RES_SEARCH_ENTRY:
+    /* Got a list of supported SASL mechanisms. */
+    if(code != LDAP_SUCCESS && code != LDAP_NO_RESULTS_RETURNED)
+      return CURLE_LOGIN_DENIED;
+
+    rc = ldap_get_dn_ber(li->ld, msg, &ber, &bv);
+    if(rc < 0)
+      return oldap_map_error(rc, CURLE_BAD_CONTENT_ENCODING);
+    for(rc = ldap_get_attribute_ber(li->ld, msg, ber, &bv, &bvals);
+        rc == LDAP_SUCCESS;
+        rc = ldap_get_attribute_ber(li->ld, msg, ber, &bv, &bvals)) {
+      int i;
+
+      if(!bv.bv_val)
+        break;
+
+      if(bvals) {
+        for(i = 0; bvals[i].bv_val; i++) {
+          size_t llen;
+          unsigned short mech = Curl_sasl_decode_mech((char *) bvals[i].bv_val,
+                                                      bvals[i].bv_len, &llen);
+          if(bvals[i].bv_len == llen)
+            li->sasl.authmechs |= mech;
+        }
+        ber_memfree(bvals);
+      }
+    }
+    ber_free(ber, 0);
+    break;
+
+  case LDAP_RES_SEARCH_RESULT:
+    switch(code) {
+    case LDAP_SIZELIMIT_EXCEEDED:
+      infof(data, "Too many authentication mechanisms\n");
+      /* FALLTHROUGH */
+    case LDAP_SUCCESS:
+    case LDAP_NO_RESULTS_RETURNED:
+      if(Curl_sasl_can_authenticate(&li->sasl, conn))
+        result = oldap_perform_sasl(data);
+      else
+        result = CURLE_LOGIN_DENIED;
+      break;
+    default:
+      result = oldap_map_error(code, CURLE_LOGIN_DENIED);
+      break;
+    }
+    break;
+  default:
+    break;
+  }
+  return result;
+}
+
+/* Handle a SASL bind response. */
+static CURLcode oldap_state_sasl_resp(struct Curl_easy *data,
+                                      LDAPMessage *msg, int code)
+{
+  struct connectdata *conn = data->conn;
+  struct ldapconninfo *li = conn->proto.ldapc;
+  CURLcode result = CURLE_OK;
+  saslprogress progress;
+  int rc;
+
+  li->servercred = NULL;
+  rc = ldap_parse_sasl_bind_result(li->ld, msg, &li->servercred, 0);
+  if(rc != LDAP_SUCCESS) {
+    failf(data, "LDAP local: sasl ldap_parse_result %s", ldap_err2string(rc));
+    result = oldap_map_error(rc, CURLE_LOGIN_DENIED);
+  }
+  else {
+    result = Curl_sasl_continue(&li->sasl, data, code, &progress);
+    if(!result && progress != SASL_INPROGRESS)
+      state(data, OLDAP_STOP);
+  }
+
+  if(li->servercred)
+    ber_bvfree(li->servercred);
+  return result;
+}
+
 /* Handle a simple bind response. */
 static CURLcode oldap_state_bind_resp(struct Curl_easy *data, LDAPMessage *msg,
                                       int code)
@@ -459,12 +728,20 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done)
   if(li->state != OLDAP_SSL && li->state != OLDAP_TLS) {
     /* Get response to last command. */
     rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, &tv, &msg);
-    if(!rc)
-      return CURLE_OK;                    /* Timed out. */
-    if(rc < 0) {
-      failf(data, "LDAP local: connecting ldap_result %s",
-            ldap_err2string(rc));
-      return oldap_map_error(rc, CURLE_COULDNT_CONNECT);
+    switch(rc) {
+    case 0:                               /* Timed out. */
+      return CURLE_OK;
+    case LDAP_RES_SEARCH_ENTRY:
+    case LDAP_RES_SEARCH_REFERENCE:
+      break;
+    default:
+      li->msgid = 0;                      /* Nothing to abandon upon error. */
+      if(rc < 0) {
+        failf(data, "LDAP local: connecting ldap_result %s",
+              ldap_err2string(rc));
+        return oldap_map_error(rc, CURLE_COULDNT_CONNECT);
+      }
+      break;
     }
 
     /* Get error code from message. */
@@ -477,11 +754,11 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done)
     }
 
     /* If protocol version 3 is not supported, fallback to version 2. */
-    if(code == LDAP_PROTOCOL_ERROR && li->state != OLDAP_BINDV2
+    if(code == LDAP_PROTOCOL_ERROR && li->state != OLDAP_BINDV2 &&
 #ifdef USE_SSL
-       && (ssl_installed(conn) || data->set.use_ssl <= CURLUSESSL_TRY)
+       (ssl_installed(conn) || data->set.use_ssl <= CURLUSESSL_TRY) &&
 #endif
-       ) {
+       li->sasl.prefmech == SASL_AUTH_NONE) {
       static const int version = LDAP_VERSION2;
 
       ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
@@ -496,13 +773,19 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done)
 #ifdef USE_SSL
   case OLDAP_SSL:
     result = oldap_ssl_connect(data, OLDAP_SSL);
-    if(!result && ssl_installed(conn))
-      result = oldap_perform_bind(data, OLDAP_BIND);
+    if(!result && ssl_installed(conn)) {
+      if(li->sasl.prefmech != SASL_AUTH_NONE)
+        result = oldap_perform_mechs(data);
+      else
+        result = oldap_perform_bind(data, OLDAP_BIND);
+    }
     break;
   case OLDAP_STARTTLS:
     if(code != LDAP_SUCCESS) {
       if(data->set.use_ssl != CURLUSESSL_TRY)
         result = oldap_map_error(code, CURLE_USE_SSL_FAILED);
+      else if(li->sasl.prefmech != SASL_AUTH_NONE)
+        result = oldap_perform_mechs(data);
       else
         result = oldap_perform_bind(data, OLDAP_BIND);
       break;
@@ -514,7 +797,9 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done)
       result = oldap_map_error(code, CURLE_USE_SSL_FAILED);
     else if(ssl_installed(conn)) {
       conn->bits.tls_upgraded = TRUE;
-      if(conn->bits.user_passwd)
+      if(li->sasl.prefmech != SASL_AUTH_NONE)
+        result = oldap_perform_mechs(data);
+      else if(conn->bits.user_passwd)
         result = oldap_perform_bind(data, OLDAP_BIND);
       else {
         state(data, OLDAP_STOP); /* Version 3 supported: no bind required */
@@ -524,6 +809,12 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done)
     break;
 #endif
 
+  case OLDAP_MECHS:
+    result = oldap_state_mechs_resp(data, msg, code);
+    break;
+  case OLDAP_SASL:
+    result = oldap_state_sasl_resp(data, msg, code);
+    break;
   case OLDAP_BIND:
   case OLDAP_BINDV2:
     result = oldap_state_bind_resp(data, msg, code);
@@ -540,6 +831,10 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done)
   if(*done)
     conn->recv[FIRSTSOCKET] = oldap_recv;
 
+  if(result && li->msgid) {
+    ldap_abandon_ext(li->ld, li->msgid, NULL, NULL);
+    li->msgid = 0;
+  }
   return result;
 }
 
@@ -562,6 +857,7 @@ static CURLcode oldap_disconnect(struct Curl_easy *data,
       ldap_unbind_ext(li->ld, NULL, NULL);
       li->ld = NULL;
     }
+    Curl_sasl_cleanup(conn, li->sasl.authused);
     conn->proto.ldapc = NULL;
     free(li);
   }