From 0d29eee9c5609bc14ec45bfca6b487d174817176 Mon Sep 17 00:00:00 2001 From: roman khapov Date: Fri, 12 Jun 2026 06:53:57 +0000 Subject: [PATCH] Add timeout options for LDAP authentication connections Previously, if the LDAP server became unreachable during authentication, a backend process could hang undefined amount of time while waiting for a response, holding a connection slot. This patch adds two new pg_hba.conf options: ldapnetworktimeout, which controls the timeout for individual network I/O operations, and ldaptimeout, which controls the timeout for a complete LDAP operation such as a search or bind. When available, also set TCP user timeout from the tcp_user_timeout GUC. Signed-off-by: roman khapov --- doc/src/sgml/client-auth.sgml | 39 ++++++++++++++++++ src/backend/libpq/auth.c | 68 ++++++++++++++++++++++++++++++-- src/backend/libpq/hba.c | 42 ++++++++++++++++++++ src/backend/utils/adt/hbafuncs.c | 20 +++++++++- src/include/libpq/hba.h | 2 + 5 files changed, 165 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index e4e65f8feb1..09e21746370 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1841,6 +1841,45 @@ omicron bryanh guest1 + + ldapnetworktimeout + + + Maximum time in seconds to wait for a response from the LDAP + server when establishing a connection or waiting for data on an + existing connection. A value of 0 disables the timeout. + If not specified, the behavior depends on the LDAP client library + configuration (typically no timeout, meaning authentication can + hang indefinitely if the LDAP server is unreachable). + + + Note that on Linux, this option interacts with the + parameter, which provides + an additional TCP-level timeout. + + + + + + ldaptimeout + + + Maximum time in seconds to wait for the completion of a + synchronous LDAP operation, such as a search or bind request. + A value of 0 disables the timeout. If not specified, the + behavior depends on the LDAP client library configuration + (typically no timeout). + + + Note that ldapnetworktimeout and + ldaptimeout control different aspects of + timeouts: ldapnetworktimeout applies to + individual network I/O operations, while + ldaptimeout applies to the overall duration of + an LDAP operation. + + + diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 2af5615e54a..8c6a9685f56 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2234,6 +2234,15 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) int ldapversion = LDAP_VERSION3; int r; +#ifdef LDAP_OPT_TCP_USER_TIMEOUT + int tcp_usr_timeout; +#endif +#ifdef WIN32 + ULONG option; +#else + struct timeval tv; +#endif + scheme = port->hba->ldapscheme; if (scheme == NULL) scheme = "ldap"; @@ -2375,10 +2384,58 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r)), errdetail_for_ldap(*ldap))); - ldap_unbind(*ldap); - return STATUS_ERROR; + goto error_cleanup; + } + +#ifdef WIN32 + option = (ULONG) port->hba->ldapnetworktimeout; + if (port->hba->ldapnetworktimeout != LDAP_NO_LIMIT + && (r = ldap_set_option(*ldap, LDAP_OPT_SEND_TIMEOUT, &option)) != LDAP_SUCCESS) +#else + tv.tv_sec = port->hba->ldapnetworktimeout; + tv.tv_usec = 0; + if (port->hba->ldapnetworktimeout != -1 + && (r = ldap_set_option(*ldap, LDAP_OPT_NETWORK_TIMEOUT, &tv)) != LDAP_SUCCESS) +#endif + { + ereport(LOG, + (errmsg("could not set LDAP network timeout: %s", + ldap_err2string(r)), + errdetail_for_ldap(*ldap))); + goto error_cleanup; + } + +#ifdef WIN32 + option = (ULONG) port->hba->ldaptimeout; + if (port->hba->ldaptimeout != LDAP_NO_LIMIT + && (r = ldap_set_option(*ldap, LDAP_OPT_TIMELIMIT, &option)) != LDAP_SUCCESS) +#else + tv.tv_sec = port->hba->ldaptimeout; + tv.tv_usec = 0; + if (port->hba->ldaptimeout != -1 + && (r = ldap_set_option(*ldap, LDAP_OPT_TIMEOUT, &tv)) != LDAP_SUCCESS) +#endif + { + ereport(LOG, + (errmsg("could not set LDAP timeout: %s", + ldap_err2string(r)), + errdetail_for_ldap(*ldap))); + goto error_cleanup; } +#ifdef LDAP_OPT_TCP_USER_TIMEOUT + tcp_usr_timeout = pq_gettcpusertimeout(port); + if (tcp_usr_timeout > 0 + && (r = ldap_set_option(*ldap, LDAP_OPT_TCP_USER_TIMEOUT, &tcp_usr_timeout)) != LDAP_SUCCESS) + { + ereport(LOG, + (errmsg("could not set LDAP tcp user timeout: %s", + ldap_err2string(r)), + errdetail_for_ldap(*ldap))); + goto error_cleanup; + } +#endif + if (port->hba->ldaptls) { #ifndef WIN32 @@ -2391,12 +2448,15 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r)), errdetail_for_ldap(*ldap))); - ldap_unbind(*ldap); - return STATUS_ERROR; + goto error_cleanup; } } return STATUS_OK; + +error_cleanup: + ldap_unbind(*ldap); + return STATUS_ERROR; } /* Placeholders recognized by FormatSearchFilter. For now just one. */ diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index d47eab2cba0..e937f8df303 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1343,6 +1343,14 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) parsedline->linenumber = line_num; parsedline->rawline = pstrdup(tok_line->raw_line); +#ifndef WIN32 + parsedline->ldapnetworktimeout = -1; + parsedline->ldaptimeout = -1; +#else + parsedline->ldapnetworktimeout = LDAP_NO_LIMIT; + parsedline->ldaptimeout = LDAP_NO_LIMIT; +#endif + /* Check the record type. */ Assert(tok_line->fields != NIL); field = list_head(tok_line->fields); @@ -2002,6 +2010,8 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, { int line_num = hbaline->linenumber; char *file_name = hbaline->sourcefile; + char *endp; + long long_val; #ifdef USE_LDAP hbaline->ldapscope = LDAP_SCOPE_SUBTREE; @@ -2196,6 +2206,38 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, return false; } } + else if (strcmp(name, "ldapnetworktimeout") == 0) + { + REQUIRE_AUTH_OPTION(uaLDAP, "ldapnetworktimeout", "ldap"); + long_val = strtol(val, &endp, 10); + if (endp == val || long_val > INT_MAX || long_val < 0) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid LDAP network timeout number: \"%s\"", val), + errcontext("line %d of configuration file \"%s\"", + line_num, file_name))); + *err_msg = psprintf("invalid LDAP network timeout number: \"%s\"", val); + return false; + } + hbaline->ldapnetworktimeout = (int) long_val; + } + else if (strcmp(name, "ldaptimeout") == 0) + { + REQUIRE_AUTH_OPTION(uaLDAP, "ldaptimeout", "ldap"); + long_val = strtol(val, &endp, 10); + if (endp == val || long_val > INT_MAX || long_val < 0) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid LDAP timeout number: \"%s\"", val), + errcontext("line %d of configuration file \"%s\"", + line_num, file_name))); + *err_msg = psprintf("invalid LDAP timeout number: \"%s\"", val); + return false; + } + hbaline->ldaptimeout = (int) long_val; + } else if (strcmp(name, "ldapbinddn") == 0) { REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap"); diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c index bd23eda3f79..67cf0aa37bb 100644 --- a/src/backend/utils/adt/hbafuncs.c +++ b/src/backend/utils/adt/hbafuncs.c @@ -39,12 +39,12 @@ static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); /* * This macro specifies the maximum number of authentication options * that are possible with any given authentication method that is supported. - * Currently LDAP supports 12, and there are 3 that are not dependent on + * Currently LDAP supports 14, and there are 3 that are not dependent on * the auth method here. It may not actually be possible to set all of them * at the same time, but we'll set the macro value high enough to be * conservative and avoid warnings from static analysis tools. */ -#define MAX_HBA_OPTIONS 15 +#define MAX_HBA_OPTIONS 17 /* * Create a text array listing the options specified in the HBA line. @@ -91,6 +91,22 @@ get_hba_options(HbaLine *hba) options[noptions++] = CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport)); +#ifdef WIN32 + if (hba->ldapnetworktimeout != LDAP_NO_LIMIT) +#else + if (hba->ldapnetworktimeout != -1) +#endif + options[noptions++] = + CStringGetTextDatum(psprintf("ldapnetworktimeout=%d", hba->ldapnetworktimeout)); + +#ifdef WIN32 + if (hba->ldaptimeout != LDAP_NO_LIMIT) +#else + if (hba->ldaptimeout != -1) +#endif + options[noptions++] = + CStringGetTextDatum(psprintf("ldaptimeout=%d", hba->ldaptimeout)); + if (hba->ldapscheme) options[noptions++] = CStringGetTextDatum(psprintf("ldapscheme=%s", hba->ldapscheme)); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 4aa6258a345..dc0a886f3ad 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -113,6 +113,8 @@ typedef struct HbaLine char *ldapscheme; char *ldapserver; int ldapport; + int ldapnetworktimeout; + int ldaptimeout; char *ldapbinddn; char *ldapbindpasswd; char *ldapsearchattribute; -- 2.43.0