diff options
author | Patrick Monnerat <patrick@monnerat.net> | 2021-11-25 15:03:56 +0100 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2021-11-26 14:32:12 +0100 |
commit | ac89965f996161f75403ddb3a2ad6d0e8d146a4c (patch) | |
tree | a7b1bbdc7715b85156680076c3357cdf9f10f481 | |
parent | 76d6e719d9545cdb95fb4473cc3899a1612cb996 (diff) | |
download | curl-ac89965f996161f75403ddb3a2ad6d0e8d146a4c.tar.gz |
openldap: handle connect phase with a state machine
Closes #8054
-rw-r--r-- | lib/openldap.c | 324 |
1 files changed, 213 insertions, 111 deletions
diff --git a/lib/openldap.c b/lib/openldap.c index fb5e743c2..ba632d85b 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -70,6 +70,15 @@ */ /* #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); |