From 0020d2027b581e6bdfc5b78785a9b9171b7f7ef2 Mon Sep 17 00:00:00 2001
From: Andrew Jackson <ajackson@drwholdings.com>
Date: Sat, 28 Mar 2026 23:29:48 -0500
Subject: [PATCH] Allow LDAP lookup from pgservice connection parameter

Currently there exists, only in pg_service.conf, the ability to look
up connection parameters from a centralized LDAP server. This patch
expands the usability of this be allowing it to be specified directly in
a connection string instead of only in a pg_service.conf file.

This patch adds a check in parseServiceInfo that checks if pgservice is
an LDAP scheme address and if so attempts to connect to that LDAP server
and grab connection parameters from there. This is a breaking change in
that it is possible that people previously named their pgservice after
an LDAP address.

v0002 patch

This patch Removes all backwards compatibility issues. Now if the LDAP
URL was specified in pg_service.conf then the values from
pg_service.conf will be used and the LDAP lookup will not be performed.

Cool
---
 doc/src/sgml/libpq.sgml                       |  6 +++
 src/interfaces/libpq/fe-connect.c             |  5 ++
 .../t/003_ldap_connection_param_lookup.pl     | 48 +++++++++++++++++++
 3 files changed, 59 insertions(+)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6db823808fc..88a5db2388d 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2333,6 +2333,12 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         name in <filename>pg_service.conf</filename> that holds additional connection parameters.
         This allows applications to specify only a service name so connection parameters
         can be centrally maintained. See <xref linkend="libpq-pgservice"/>.
+
+        You can also specify an LDAP URL here which will look up connection parameters from an
+        LDAP server. See <xref linkend="libpq-ldap"/>. Please note that if the LDAP URL
+        specified here exists as a service name in <filename>pg_service.conf</filename>, then
+        the <filename>pg_service.conf</filename> values will be used instead and no LDAP lookup
+        will be performed.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db9b4c8edbf..b12b9bceb12 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -6048,6 +6048,11 @@ next_file:
 last_file:
 	if (!group_found)
 	{
+#ifdef USE_LDAP
+		if (strncmp(service, "ldap", 4) == 0)
+			/* if error ignore ldapServiceLookup return value, just return 3 */
+			return ldapServiceLookup(service, options, errorMessage) ? 3 : 0;
+#endif
 		libpq_append_error(errorMessage, "definition of service \"%s\" not found", service);
 		return 3;
 	}
diff --git a/src/test/ldap/t/003_ldap_connection_param_lookup.pl b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
index 359fc7a998a..976c196df8c 100644
--- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl
+++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
@@ -62,6 +62,20 @@ description:port=} . $node->port . qq{
 
 $ldap->ldapadd_file($ldif_valid);
 
+my $ldif_valid_invalid = "$td/connection_params_ldif_valid_invalid.ldif";
+append_to_file(
+	$ldif_valid_invalid , qq{
+version:1
+dn:cn=mydatabasefoundinpgservice,dc=example,dc=net
+changetype:add
+objectclass:top
+objectclass:device
+cn:mydatabasefoundinpgservice
+description:host=} . $node->host . qq{
+description:port=} . $node->port . qq{
+});
+$ldap->ldapadd_file($ldif_valid_invalid);
+
 my ($ldap_server, $ldap_port, $ldaps_port, $ldap_url,
 	$ldaps_url, $ldap_basedn, $ldap_rootdn
 ) = $ldap->prop(qw(server port s_port url s_url basedn rootdn));
@@ -80,6 +94,9 @@ append_to_file(
 	$srvfile_valid, qq{
 [my_srv]
 ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
+
+[ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabasefoundinpgservice)]
+port=1234
 });
 
 # File defined with no contents, used as default value for
@@ -196,6 +213,37 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);
 
+	delete $ENV{PGSERVICE};
+
+	$dummy_node->connect_ok(
+		"service=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "service" string populated with LDAP address',
+		sql => "SELECT 'connect2_4'",
+		expected_stdout => qr/connect2_4/);
+
+	$dummy_node->connect_ok(
+		"postgres://?service=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapservice" string populated with LDAP address',
+		sql => "SELECT 'connect2_5'",
+		expected_stdout => qr/connect2_5/);
+
+	local $ENV{PGSERVICE} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)";
+	$dummy_node->connect_ok(
+		"",
+		'connection with correct "service" provided by env var populated with LDAP address',
+		sql => "SELECT 'connect2_6'",
+		expected_stdout => qr/connect2_6/);
+	delete $ENV{PGLDAPSERVICE};
+
+	# Below test should fail because the service value is defined as a literal service in pg_service.conf.
+	# The pg_service.conf entry should have an incorrect port and LDAP should not be looked up after reading
+	# the incorrect port
+	$dummy_node->connect_fails(
+		'service=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabasefoundinpgservice)"',
+		'connection using cn=mydatabasefoundinpgservice',
+		expected_stdout =>
+		  qr/definition of service "undefined-service" not found/);
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }
-- 
2.51.2

