From b56968599037f402b0b4c040483bd67e61bf2429 Mon Sep 17 00:00:00 2001
From: Andrew Jackson <ajackson@drwholdings.com>
Date: Sat, 28 Mar 2026 23:29:48 -0500
Subject: [PATCH] Add ldapservice 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 adds the PGLDAPSERVICE env var that provides an envvar interface
to this functionality. Also the functionality has been moved to
conninfo_add_defaults after review. Also 2 tests have been added. One to
validate the env var functionality and one to validate that this
parameter is ignored when used in pg_service.conf files.

-- 0005 patch changes
This patch changes the relevant naming from ldapservice to
ldapserviceurl. I also add a comment above the new ldapservicelookup
call that explains the reasoning for ignoring all non zero return codes.
Also I increase the value of dispsize for ldapserviceurl from 20 to 64.
It could be reasonable to increase it past 64, was originally thinking
about bumping it to 1024 but I am not sure what these values are used
for in the codebase and thought it was safer to just bump it to the
currently existing max for any parameter. Moves the placement of
ldapserviceurl to below the GSS options and adds a comment explaining
that the parameter is exposed even in non LDAP builds. This was done to
reflect similar comments in GSS/SSL, though this comment does not exist
for the new OAUTH parameters so this may not have been a good choice.
Also Add filename tags to filename reference in docs
---
 doc/src/sgml/libpq.sgml                       | 23 +++++++++++++++
 src/interfaces/libpq/fe-connect.c             | 24 +++++++++++++++
 src/interfaces/libpq/libpq-int.h              |  1 +
 .../t/003_ldap_connection_param_lookup.pl     | 29 +++++++++++++++++++
 4 files changed, 77 insertions(+)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6db823808fc..0e4b7f81551 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2350,6 +2350,19 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-ldapserviceurl" xreflabel="ldapserviceurl">
+      <term><literal>ldapserviceurl</literal></term>
+      <listitem>
+       <para>
+        This option specifies an LDAP query that can be used to reference connection parameters
+        stored on an LDAP server. Any connection parameter that is looked up in this way is
+        overridden by explicitly named connection parameters. This parameter is ignored when used
+        in a <filename>pg_service.conf</filename> file. This functionality is described in more
+        detail in <xref linkend="libpq-ldap"/>.
+        </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-target-session-attrs" xreflabel="target_session_attrs">
       <term><literal>target_session_attrs</literal></term>
       <listitem>
@@ -9170,6 +9183,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGLDAPSERVICEURL</envar></primary>
+      </indexterm>
+      <envar>PGLDAPSERVICEURL</envar> behaves the same as the
+      <xref linkend="libpq-connect-ldapserviceurl"/> connection parameter.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db9b4c8edbf..d076bfe7701 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -376,6 +376,15 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"GSS-delegation", "", 1,
 	offsetof(struct pg_conn, gssdelegation)},
 
+	/*
+	 * As with SSL and GSS options, ldapserviceurl is exposed even in builds
+	 * that do not have support
+	 */
+	{"ldapserviceurl", "PGLDAPSERVICEURL", NULL, NULL,
+		"Database-LDAP-Service", "", 64,
+	offsetof(struct pg_conn, pgldapserviceurl)},
+
+
 	{"replication", NULL, NULL, NULL,
 		"Replication", "D", 5,
 	offsetof(struct pg_conn, replication)},
@@ -6726,6 +6735,21 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 	PQconninfoOption *sslmode_default = NULL,
 			   *sslrootcert = NULL;
 	char	   *tmp;
+#ifdef USE_LDAP
+	const char *ldapserviceurl = conninfo_getval(options, "ldapserviceurl");
+
+	if (ldapserviceurl == NULL)
+		ldapserviceurl = getenv("PGLDAPSERVICEURL");
+
+	if (ldapserviceurl != NULL)
+
+		/*
+		 * ldapServiceLookup has 4 potential return values. We only care here
+		 * if it succeeded, if it failed we dont care why, return failure.
+		 */
+		if (ldapServiceLookup(ldapserviceurl, options, errorMessage) != 0)
+			return false;
+#endif
 
 	/*
 	 * If there's a service spec, use it to obtain any not-explicitly-given
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index bd7eb59f5f8..f8b10635d41 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -392,6 +392,7 @@ struct pg_conn
 	char	   *pgservice;		/* Postgres service, if any */
 	char	   *pgservicefile;	/* path to a service file containing
 								 * service(s) */
+	char	   *pgldapserviceurl;	/* Postgres LDAP service URL, if any */
 	char	   *pguser;			/* Postgres username and password, if any */
 	char	   *pgpass;
 	char	   *pgpassfile;		/* path to a file containing password(s) */
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..f1c5cc85eb6 100644
--- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl
+++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl
@@ -80,6 +80,9 @@ append_to_file(
 	$srvfile_valid, qq{
 [my_srv]
 ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
+
+[my_srv_2]
+ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)
 });
 
 # File defined with no contents, used as default value for
@@ -196,6 +199,32 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
 		expected_stdout =>
 		  qr/definition of service "undefined-service" not found/);
 
+	delete $ENV{PGSERVICE};
+
+	$dummy_node->connect_ok(
+		"ldapserviceurl=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)",
+		'connection with correct "ldapservice" string',
+		sql => "SELECT 'connect2_4'",
+		expected_stdout => qr/connect2_4/);
+
+	$dummy_node->connect_ok(
+		"postgres://?ldapserviceurl=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29",
+		'connection with correct "ldapserviceurl"',
+		sql => "SELECT 'connect2_5'",
+		expected_stdout => qr/connect2_5/);
+
+	local $ENV{PGLDAPSERVICEURL} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)";
+	$dummy_node->connect_ok(
+		"",
+		'connection with correct "ldapserviceurl" provided by env var',
+		sql => "SELECT 'connect2_6'",
+		expected_stdout => qr/connect2_6/);
+	delete $ENV{PGLDAPSERVICEURL};
+
+	$dummy_node->connect_fails(
+		'',
+		'connection fails with ldapserviceurl specified in pg_service.conf file');
+
 	# Remove default pg_service.conf.
 	unlink($srvfile_default);
 }
-- 
2.51.2

