]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
openldap: handle connect phase with a state machine
authorPatrick Monnerat <patrick@monnerat.net>
Thu, 25 Nov 2021 14:03:56 +0000 (15:03 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 26 Nov 2021 13:32:12 +0000 (14:32 +0100)
Closes #8054

lib/openldap.c

index fb5e743c27984f04697ceecca7b0c820b101a9cd..ba632d85bda760044397cd270f6d65bef39cda6b 100644 (file)
  */
 /* #define CURL_OPENLDAP_DEBUG */
 
+/* Machine states. */
+typedef enum {
+  OLDAP_STOP,           /* Do nothing state, stops the state machine */
+  OLDAP_SSL,            /* Performing SSL handshake. */
+  OLDAP_BIND,           /* Simple bind reply. */
+  OLDAP_BINDV2,         /* Simple bind reply in protocol version 2. */
+  OLDAP_LAST            /* Never used */
+} ldapstate;
+
 #ifndef _LDAP_PVT_H
 extern int ldap_pvt_url_scheme2proto(const char *);
 extern int ldap_init_fd(ber_socket_t fd, int proto, const char *url,
@@ -158,14 +167,12 @@ static const char *url_errs[] = {
 };
 
 struct ldapconninfo {
-  LDAP *ld;
-  Curl_recv *recv;  /* for stacking SSL handler */
+  LDAP *ld;                  /* Openldap connection handle. */
+  Curl_recv *recv;           /* For stacking SSL handler */
   Curl_send *send;
-  int proto;
-  int msgid;
-  bool ssldone;
-  bool sslinst;
-  bool didbind;
+  ldapstate state;           /* Current machine state. */
+  int proto;                 /* LDAP_PROTO_TCP/LDAP_PROTO_UDP/LDAP_PROTO_IPC */
+  int msgid;                 /* Current message id. */
 };
 
 struct ldapreqinfo {
@@ -173,6 +180,53 @@ struct ldapreqinfo {
   int nument;
 };
 
+/*
+ * state()
+ *
+ * This is the ONLY way to change LDAP state!
+ */
+static void state(struct Curl_easy *data, ldapstate newstate)
+{
+  struct ldapconninfo *ldapc = data->conn->proto.ldapc;
+
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  /* for debug purposes */
+  static const char * const names[] = {
+    "STOP",
+    "SSL",
+    "BIND",
+    "BINDV2",
+    /* LAST */
+  };
+
+  if(ldapc->state != newstate)
+    infof(data, "LDAP %p state change from %s to %s",
+          (void *)ldapc, names[ldapc->state], names[newstate]);
+#endif
+
+  ldapc->state = newstate;
+}
+
+/* Map some particular LDAP error codes to CURLcode values. */
+static CURLcode oldap_map_error(int rc, CURLcode result)
+{
+  switch(rc) {
+  case LDAP_NO_MEMORY:
+    result = CURLE_OUT_OF_MEMORY;
+    break;
+  case LDAP_INVALID_CREDENTIALS:
+    result = CURLE_LOGIN_DENIED;
+    break;
+  case LDAP_PROTOCOL_ERROR:
+    result = CURLE_UNSUPPORTED_PROTOCOL;
+    break;
+  case LDAP_INSUFFICIENT_ACCESS:
+    result = CURLE_REMOTE_ACCESS_DENIED;
+    break;
+  }
+  return result;
+}
+
 static CURLcode oldap_setup_connection(struct Curl_easy *data,
                                        struct connectdata *conn)
 {
@@ -205,8 +259,69 @@ static CURLcode oldap_setup_connection(struct Curl_easy *data,
   return CURLE_OK;
 }
 
+/* Starts LDAP simple bind. */
+static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate)
+{
+  CURLcode result = CURLE_OK;
+  struct connectdata *conn = data->conn;
+  struct ldapconninfo *li = conn->proto.ldapc;
+  char *binddn = NULL;
+  struct berval passwd;
+  int rc;
+
+  passwd.bv_val = NULL;
+  passwd.bv_len = 0;
+
+  if(conn->bits.user_passwd) {
+    binddn = conn->user;
+    passwd.bv_val = conn->passwd;
+    passwd.bv_len = strlen(passwd.bv_val);
+  }
+
+  rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd,
+                      NULL, NULL, &li->msgid);
+  if(rc == LDAP_SUCCESS)
+    state(data, newstate);
+  else
+    result = oldap_map_error(rc,
+                             conn->bits.user_passwd?
+                             CURLE_LOGIN_DENIED: CURLE_LDAP_CANNOT_BIND);
+  return result;
+}
+
 #ifdef USE_SSL
 static Sockbuf_IO ldapsb_tls;
+
+static bool ssl_installed(struct connectdata *conn)
+{
+  return conn->proto.ldapc->recv != NULL;
+}
+
+static CURLcode oldap_ssl_connect(struct Curl_easy *data)
+{
+  CURLcode result = CURLE_OK;
+  struct connectdata *conn = data->conn;
+  struct ldapconninfo *li = conn->proto.ldapc;
+  bool ssldone = 0;
+
+  result = Curl_ssl_connect_nonblocking(data, conn, FALSE,
+                                        FIRSTSOCKET, &ssldone);
+  if(!result) {
+    state(data, OLDAP_SSL);
+
+    if(ssldone) {
+      Sockbuf *sb;
+
+      /* Install the libcurl SSL handlers into the sockbuf. */
+      ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb);
+      ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data);
+      li->recv = conn->recv[FIRSTSOCKET];
+      li->send = conn->send[FIRSTSOCKET];
+    }
+  }
+
+  return result;
+}
 #endif
 
 static CURLcode oldap_connect(struct Curl_easy *data, bool *done)
@@ -216,6 +331,9 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done)
   int rc, proto = LDAP_VERSION3;
   char hosturl[1024];
   char *ptr;
+#ifdef CURL_OPENLDAP_DEBUG
+  static int do_trace = -1;
+#endif
 
   (void)done;
 
@@ -226,15 +344,6 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done)
   msnprintf(ptr, sizeof(hosturl)-(ptr-hosturl), "://%s:%d",
             conn->host.name, conn->remote_port);
 
-#ifdef CURL_OPENLDAP_DEBUG
-  static int do_trace = 0;
-  const char *env = getenv("CURL_OPENLDAP_TRACE");
-  do_trace = (env && strtol(env, NULL, 10) > 0);
-  if(do_trace) {
-    ldap_set_option(li->ld, LDAP_OPT_DEBUG_LEVEL, &do_trace);
-  }
-#endif
-
   rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld);
   if(rc) {
     failf(data, "LDAP local: Cannot connect to %s, %s",
@@ -242,125 +351,118 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done)
     return CURLE_COULDNT_CONNECT;
   }
 
+#ifdef CURL_OPENLDAP_DEBUG
+  if(do_trace < 0) {
+    const char *env = getenv("CURL_OPENLDAP_TRACE");
+    do_trace = (env && strtol(env, NULL, 10) > 0);
+  }
+  if(do_trace)
+    ldap_set_option(li->ld, LDAP_OPT_DEBUG_LEVEL, &do_trace);
+#endif
+
   ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto);
 
 #ifdef USE_SSL
-  if(conn->handler->flags & PROTOPT_SSL) {
-    CURLcode result;
-    result = Curl_ssl_connect_nonblocking(data, conn, FALSE,
-                                          FIRSTSOCKET, &li->ssldone);
-    if(result)
-      return result;
-  }
+  if(conn->handler->flags & PROTOPT_SSL)
+    return oldap_ssl_connect(data);
 #endif
 
-  return CURLE_OK;
+  /* 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);
 }
 
-static CURLcode oldap_connecting(struct Curl_easy *data, bool *done)
+/* Handle a simple bind response. */
+static CURLcode oldap_state_bind_resp(struct Curl_easy *data, LDAPMessage *msg,
+                                      int code)
 {
   struct connectdata *conn = data->conn;
   struct ldapconninfo *li = conn->proto.ldapc;
-  LDAPMessage *msg = NULL;
-  struct timeval tv = {0, 1}, *tvp;
-  int rc, err;
-  char *info = NULL;
+  CURLcode result = CURLE_OK;
+  struct berval *bv = NULL;
+  int rc;
 
-#ifdef USE_SSL
-  if(conn->handler->flags & PROTOPT_SSL) {
-    /* Is the SSL handshake complete yet? */
-    if(!li->ssldone) {
-      CURLcode result = Curl_ssl_connect_nonblocking(data, conn, FALSE,
-                                                     FIRSTSOCKET,
-                                                     &li->ssldone);
-      if(result || !li->ssldone)
-        return result;
-    }
+  if(code != LDAP_SUCCESS)
+    return oldap_map_error(code, CURLE_LDAP_CANNOT_BIND);
 
-    /* Have we installed the libcurl SSL handlers into the sockbuf yet? */
-    if(!li->sslinst) {
-      Sockbuf *sb;
-      ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb);
-      ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data);
-      li->sslinst = TRUE;
-      li->recv = conn->recv[FIRSTSOCKET];
-      li->send = conn->send[FIRSTSOCKET];
-    }
+  rc = ldap_parse_sasl_bind_result(li->ld, msg, &bv, 0);
+  if(rc != LDAP_SUCCESS) {
+    failf(data, "LDAP local: bind ldap_parse_sasl_bind_result %s",
+          ldap_err2string(rc));
+    result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND);
   }
-#endif
+  else
+    state(data, OLDAP_STOP);
 
-  tvp = &tv;
-
-  retry:
-  if(!li->didbind) {
-    char *binddn;
-    struct berval passwd;
+  if(bv)
+    ber_bvfree(bv);
+  return result;
+}
 
-    if(conn->bits.user_passwd) {
-      binddn = conn->user;
-      passwd.bv_val = conn->passwd;
-      passwd.bv_len = strlen(passwd.bv_val);
-    }
-    else {
-      binddn = NULL;
-      passwd.bv_val = NULL;
-      passwd.bv_len = 0;
+static CURLcode oldap_connecting(struct Curl_easy *data, bool *done)
+{
+  CURLcode result = CURLE_OK;
+  struct connectdata *conn = data->conn;
+  struct ldapconninfo *li = conn->proto.ldapc;
+  LDAPMessage *msg = NULL;
+  struct timeval tv = {0, 0};
+  int code = LDAP_SUCCESS;
+  int rc;
+
+  if(li->state != OLDAP_SSL) {
+    /* 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);
     }
-    rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd,
-                        NULL, NULL, &li->msgid);
-    if(rc)
-      return CURLE_LDAP_CANNOT_BIND;
-    li->didbind = TRUE;
-    if(tvp)
-      return CURLE_OK;
-  }
 
-  rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, tvp, &msg);
-  if(rc < 0) {
-    failf(data, "LDAP local: bind ldap_result %s", ldap_err2string(rc));
-    return CURLE_LDAP_CANNOT_BIND;
-  }
-  if(rc == 0) {
-    /* timed out */
-    return CURLE_OK;
-  }
+    /* Get error code from message. */
+    rc = ldap_parse_result(li->ld, msg, &code, NULL, NULL, NULL, NULL, 0);
+    if(rc)
+      code = rc;
 
-  rc = ldap_parse_result(li->ld, msg, &err, NULL, &info, NULL, NULL, 1);
-  if(rc) {
-    failf(data, "LDAP local: bind ldap_parse_result %s", ldap_err2string(rc));
-    return CURLE_LDAP_CANNOT_BIND;
-  }
+    /* If protocol version 3 is not supported, fallback to version 2. */
+    if(code == LDAP_PROTOCOL_ERROR && li->state != OLDAP_BINDV2) {
+      static const int version = LDAP_VERSION2;
 
-  /* Try to fallback to LDAPv2? */
-  if(err == LDAP_PROTOCOL_ERROR) {
-    int proto;
-    ldap_get_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto);
-    if(proto == LDAP_VERSION3) {
-      if(info) {
-        ldap_memfree(info);
-        info = NULL;
-      }
-      proto = LDAP_VERSION2;
-      ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto);
-      li->didbind = FALSE;
-      goto retry;
+      ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
+      ldap_msgfree(msg);
+      return oldap_perform_bind(data, OLDAP_BINDV2);
     }
   }
 
-  if(err) {
-    failf(data, "LDAP remote: bind failed %s %s", ldap_err2string(rc),
-          info ? info : "");
-    if(info)
-      ldap_memfree(info);
-    return CURLE_LOGIN_DENIED;
+  /* Handle response message according to current state. */
+  switch(li->state) {
+
+#ifdef USE_SSL
+  case OLDAP_SSL:
+    result = oldap_ssl_connect(data);
+    if(!result && ssl_installed(conn))
+      result = oldap_perform_bind(data, OLDAP_BIND);
+    break;
+#endif
+
+  case OLDAP_BIND:
+  case OLDAP_BINDV2:
+    result = oldap_state_bind_resp(data, msg, code);
+    break;
+  default:
+    /* internal error */
+    result = CURLE_COULDNT_CONNECT;
+    break;
   }
 
-  if(info)
-    ldap_memfree(info);
-  conn->recv[FIRSTSOCKET] = oldap_recv;
-  *done = TRUE;
+  ldap_msgfree(msg);
 
-  return CURLE_OK;
+  *done = li->state == OLDAP_STOP;
+  if(*done)
+    conn->recv[FIRSTSOCKET] = oldap_recv;
+
+  return result;
 }
 
 static CURLcode oldap_disconnect(struct Curl_easy *data,
@@ -373,7 +475,7 @@ static CURLcode oldap_disconnect(struct Curl_easy *data,
   if(li) {
     if(li->ld) {
 #ifdef USE_SSL
-      if(conn->ssl[FIRSTSOCKET].use) {
+      if(ssl_installed(conn)) {
         Sockbuf *sb;
         ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb);
         ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data);