On Wed, Mar 3, 2021 at 10:00 AM Magnus Hagander <mag...@hagander.net> wrote:
>
> On Wed, Mar 3, 2021 at 1:50 AM Jacob Champion <pchamp...@vmware.com> wrote:
> >
> > On Tue, 2021-03-02 at 18:43 +0100, Magnus Hagander wrote:
> > > PFA a simple patch that implements support for the PROXY protocol.
> >
> > I'm not all the way through the patch yet, but this part jumped out at
> > me:
> >
> > > +     if (memcmp(proxyheader.sig, 
> > > "\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a", 
> > > sizeof(proxyheader.sig)) != 0)
> > > +     {
> > > +             /*
> > > +              * Data is there but it wasn't a proxy header. Also fall 
> > > through to
> > > +              * normal processing
> > > +              */
> > > +             pq_endmsgread();
> > > +             return STATUS_OK;
> >
> > From my reading, the spec explicitly disallows this sort of fallback
> > behavior:
> >
> > > The receiver MUST be configured to only receive the protocol described in 
> > > this
> > > specification and MUST not try to guess whether the protocol header is 
> > > present
> > > or not. This means that the protocol explicitly prevents port sharing 
> > > between
> > > public and private access.
> >
> > You might say, "if we already trust the proxy server, why should we
> > care?" but I think the point is that you want to catch
> > misconfigurations where the middlebox is forwarding bare TCP without
> > adding a PROXY header of its own, which will "work" for innocent
> > clients but in reality is a ticking timebomb. If you've decided to
> > trust an intermediary to use PROXY connections, then you must _only_
> > accept PROXY connections from that intermediary. Does that seem like a
> > reasonable interpretation?
>
> I definitely missed that part of the spec. Ugh.
>
> That said, I'm not sure it's *actually* an issue in the case of
> PostgreSQL. Given that doing what you're suggesting, accidentally
> passing connections without PROXY, will get caught in pg_hba.conf.
>
> That said, I agree with your interpretation, and it's pretty easy to
> change it to that. Basically we just have to do the IP check *before*
> doing the PROXY protocol check. It makes testing a bit more difficult
> though, but maybe worth it?
>
> I've attached a POC that does that. Note that I have *not* updated the docs!
>
> Another option would of course be to listen on a separate port for it,
> which seems to be the "haproxy way". That would be slightly more code
> (we'd still want to keep the code for validating the list of trusted
> proxies I'd say), but maybe worth doing?

In order to figure that out, I hacked up a poc on that. Once again
without updates to the docs, but shows approximately how much code
complexity it adds (not much).

-- 
 Magnus Hagander
 Me: https://www.hagander.net/
 Work: https://www.redpill-linpro.com/
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index b420486a0a..d4f6fad5b0 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -353,6 +353,15 @@ hostnogssenc  <replaceable>database</replaceable>  <replaceable>user</replaceabl
        the client's host name instead of the IP address in the log.
       </para>
 
+      <para>
+       If <xref linkend="guc-proxy-servers"/> is enabled and the
+       connection is made through a proxy server using the PROXY
+       protocol, the actual IP address of the client will be used
+       for matching. If a connection is made through a proxy server
+       not using the PROXY protocol, the IP address of the
+       proxy server will be used.
+      </para>
+
       <para>
        These fields do not apply to <literal>local</literal> records.
       </para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index b5718fc136..fc7de25378 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -682,6 +682,30 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-proxy-servers" xreflabel="proxy_servers">
+      <term><varname>proxy_servers</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>proxy_servers</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        A comma separated list of one or more host names or cidr specifications
+        of proxy servers to trust. If a connection using the PROXY protocol is made
+        from one of these IP addresses, <productname>PostgreSQL</productname> will
+        read the client IP address from the PROXY header and consider that the
+        address of the client, instead of listing all connections as coming from
+        the proxy server.
+       </para>
+       <para>
+        If a proxy connection is made from an IP address not covered by this
+        list, the connection will be rejected. By default no proxy is trusted
+        and all proxy connections will be rejected.  This parameter can only
+        be set at server start.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-max-connections" xreflabel="max_connections">
       <term><varname>max_connections</varname> (<type>integer</type>)
       <indexterm>
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 27a298f110..401f2d2464 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -53,6 +53,7 @@
  *		pq_getmessage	- get a message with length word from connection
  *		pq_getbyte		- get next byte from connection
  *		pq_peekbyte		- peek at next byte from connection
+ *		pq_peekbytes    - peek at a known number of bytes from connection
  *		pq_putbytes		- send bytes to connection (not flushed until pq_flush)
  *		pq_flush		- flush pending output
  *		pq_flush_if_writable - flush pending output if writable without blocking
@@ -336,7 +337,7 @@ socket_close(int code, Datum arg)
 int
 StreamServerPort(int family, const char *hostName, unsigned short portNumber,
 				 const char *unixSocketDir,
-				 pgsocket ListenSocket[], int MaxListen)
+				 pgsocket ListenSocket[], bool ProxyList[], bool isProxy, int MaxListen)
 {
 	pgsocket	fd;
 	int			err;
@@ -602,6 +603,7 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber,
 							familyDesc, addrDesc, (int) portNumber)));
 
 		ListenSocket[listen_index] = fd;
+		ProxyList[listen_index] = isProxy;
 		added++;
 	}
 
@@ -1039,6 +1041,27 @@ pq_peekbyte(void)
 	return (unsigned char) PqRecvBuffer[PqRecvPointer];
 }
 
+
+/* --------------------------------
+ *		pq_peekbytes		- peek at a known number of bytes from connection.
+ *							  Note! Does NOT wait for more data to arrive.
+ *
+ *		returns 0 if OK, EOF if trouble
+ * --------------------------------
+ */
+int
+pq_peekbytes(char *s, size_t len)
+{
+	Assert(PqCommReadingMsg);
+
+	if (PqRecvLength - PqRecvPointer < len)
+		return EOF;
+
+	memcpy(s, PqRecvBuffer + PqRecvPointer, len);
+
+	return 0;
+}
+
 /* --------------------------------
  *		pq_getbyte_if_available - get a single byte from connection,
  *			if available
@@ -1135,7 +1158,7 @@ pq_getbytes(char *s, size_t len)
  *		returns 0 if OK, EOF if trouble
  * --------------------------------
  */
-static int
+int
 pq_discardbytes(size_t len)
 {
 	size_t		amount;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3f1ce135a8..5b1e4f5592 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -102,6 +102,7 @@
 #include "common/string.h"
 #include "lib/ilist.h"
 #include "libpq/auth.h"
+#include "libpq/ifaddr.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
@@ -195,15 +196,22 @@ BackgroundWorker *MyBgworkerEntry = NULL;
 
 
 
-/* The socket number we are listening for connections on */
+/* The TCP port number we are listening for connections on */
 int			PostPortNumber;
 
+/* The TCP port number we are listening for proxy connections on */
+int			ProxyPortNumber;
+
 /* The directory names for Unix socket(s) */
 char	   *Unix_socket_directories;
 
 /* The TCP listen address(es) */
 char	   *ListenAddresses;
 
+/* Trusted proxy servers */
+char	   *TrustedProxyServersString = NULL;
+struct sockaddr_storage *TrustedProxyServers = NULL;
+
 /*
  * ReservedBackends is the number of backends reserved for superuser use.
  * This number is taken out of the pool size given by MaxConnections so
@@ -218,6 +226,7 @@ int			ReservedBackends;
 /* The socket(s) we're listening to. */
 #define MAXLISTEN	64
 static pgsocket ListenSocket[MAXLISTEN];
+static bool ListenSocketIsProxy[MAXLISTEN];
 
 /*
  * These globals control the behavior of the postmaster in case some
@@ -1124,7 +1133,10 @@ PostmasterMain(int argc, char *argv[])
 	 * charged with closing the sockets again at postmaster shutdown.
 	 */
 	for (i = 0; i < MAXLISTEN; i++)
+	{
 		ListenSocket[i] = PGINVALID_SOCKET;
+		ListenSocketIsProxy[i] = false;
+	}
 
 	on_proc_exit(CloseServerPorts, 0);
 
@@ -1156,12 +1168,14 @@ PostmasterMain(int argc, char *argv[])
 				status = StreamServerPort(AF_UNSPEC, NULL,
 										  (unsigned short) PostPortNumber,
 										  NULL,
-										  ListenSocket, MAXLISTEN);
+										  ListenSocket, ListenSocketIsProxy,
+										  false, MAXLISTEN);
 			else
 				status = StreamServerPort(AF_UNSPEC, curhost,
 										  (unsigned short) PostPortNumber,
 										  NULL,
-										  ListenSocket, MAXLISTEN);
+										  ListenSocket, ListenSocketIsProxy,
+										  false, MAXLISTEN);
 
 			if (status == STATUS_OK)
 			{
@@ -1177,6 +1191,27 @@ PostmasterMain(int argc, char *argv[])
 				ereport(WARNING,
 						(errmsg("could not create listen socket for \"%s\"",
 								curhost)));
+
+			/* Also listen to the PROXY port on this address, if configured */
+			if (ProxyPortNumber)
+			{
+				if (strcmp(curhost, "*") == 0)
+					status = StreamServerPort(AF_UNSPEC, NULL,
+											  (unsigned short) ProxyPortNumber,
+											  NULL,
+											  ListenSocket, ListenSocketIsProxy,
+											  true, MAXLISTEN);
+				else
+					status = StreamServerPort(AF_UNSPEC, curhost,
+											  (unsigned short) ProxyPortNumber,
+											  NULL,
+											  ListenSocket, ListenSocketIsProxy,
+											  true, MAXLISTEN);
+				if (status != STATUS_OK)
+					ereport(WARNING,
+							(errmsg("could not create PROXY listen socket for \"%s\"",
+									curhost)));
+			}
 		}
 
 		if (!success && elemlist != NIL)
@@ -1254,7 +1289,8 @@ PostmasterMain(int argc, char *argv[])
 			status = StreamServerPort(AF_UNIX, NULL,
 									  (unsigned short) PostPortNumber,
 									  socketdir,
-									  ListenSocket, MAXLISTEN);
+									  ListenSocket, ListenSocketIsProxy,
+									  false, MAXLISTEN);
 
 			if (status == STATUS_OK)
 			{
@@ -1731,6 +1767,8 @@ ServerLoop(void)
 					port = ConnCreate(ListenSocket[i]);
 					if (port)
 					{
+						port->isProxy = ListenSocketIsProxy[i];
+
 						BackendStartup(port);
 
 						/*
@@ -1911,6 +1949,190 @@ initMasks(fd_set *rmask)
 	return maxsock + 1;
 }
 
+static int
+UnwrapProxyConnection(Port *port)
+{
+	char		proxyver;
+	uint16		proxyaddrlen;
+	SockAddr	raddr_save;
+	int			i;
+	bool		useproxy = false;
+
+	/*
+	 * These structs are from the PROXY protocol docs at
+	 * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
+	 */
+	union
+	{
+		struct
+		{						/* for TCP/UDP over IPv4, len = 12 */
+			uint32		src_addr;
+			uint32		dst_addr;
+			uint16		src_port;
+			uint16		dst_port;
+		}			ip4;
+		struct
+		{						/* for TCP/UDP over IPv6, len = 36 */
+			uint8		src_addr[16];
+			uint8		dst_addr[16];
+			uint16		src_port;
+			uint16		dst_port;
+		}			ip6;
+	}			proxyaddr;
+	struct
+	{
+		uint8		sig[12];	/* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
+		uint8		ver_cmd;	/* protocol version and command */
+		uint8		fam;		/* protocol family and address */
+		uint16		len;		/* number of following bytes part of the
+								 * header */
+	}			proxyheader;
+
+
+	if (TrustedProxyServers)
+	{
+		for (i = 0; i < *((int *) TrustedProxyServers); i += 2)
+		{
+			if (port->raddr.addr.ss_family == TrustedProxyServers[i + 1].ss_family &&
+				pg_range_sockaddr(&port->raddr.addr,
+								  &TrustedProxyServers[i + 1],
+								  &TrustedProxyServers[i + 2]))
+			{
+				useproxy = true;
+				break;
+			}
+		}
+	}
+	if (!useproxy)
+	{
+		/*
+		 * Connection is not from one of our trusted proxies, so reject it.
+		 */
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("connection from unauthorized proxy server")));
+		return STATUS_ERROR;
+	}
+
+	/* Store a copy of the original address, for logging */
+	memcpy(&raddr_save, &port->raddr, port->raddr.salen);
+
+	pq_startmsgread();
+
+	/*
+	 * PROXY requests always start with: \x0D \x0A \x0D \x0A \x00 \x0D \x0A
+	 * \x51 \x55 \x49 \x54 \x0A
+	 */
+
+	if (pq_getbytes((char *) &proxyheader, sizeof(proxyheader)) != 0)
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("incomplete proxy packet")));
+		return STATUS_ERROR;
+	}
+
+	if (memcmp(proxyheader.sig, "\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a", sizeof(proxyheader.sig)) != 0)
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("invalid proxy packet")));
+		return STATUS_ERROR;
+	}
+
+	/* Proxy version is in the high 4 bits of the first byte */
+	proxyver = (proxyheader.ver_cmd & 0xF0) >> 4;
+	if (proxyver != 2)
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("invalid proxy protocol version: %x", proxyver)));
+		return STATUS_ERROR;
+	}
+
+	proxyaddrlen = pg_ntoh16(proxyheader.len);
+
+	if (proxyaddrlen > sizeof(proxyaddr))
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("oversized proxy packet")));
+		return STATUS_ERROR;
+	}
+	if (pq_getbytes((char *) &proxyaddr, proxyaddrlen) == EOF)
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("incomplete proxy packet")));
+		return STATUS_ERROR;
+	}
+
+	/* Lower 4 bits hold type of connection */
+	if (proxyheader.fam == 0)
+	{
+		/* LOCAL connection, so we ignore the address included */
+	}
+	else if (proxyheader.fam == 0x11)
+	{
+		/* TCPv4 */
+		port->raddr.addr.ss_family = AF_INET;
+		port->raddr.salen = sizeof(struct sockaddr_in);
+		((struct sockaddr_in *) &port->raddr.addr)->sin_addr.s_addr = proxyaddr.ip4.src_addr;
+		((struct sockaddr_in *) &port->raddr.addr)->sin_port = proxyaddr.ip4.src_port;
+	}
+	else if (proxyheader.fam == 0x21)
+	{
+		/* TCPv6 */
+		port->raddr.addr.ss_family = AF_INET6;
+		port->raddr.salen = sizeof(struct sockaddr_in6);
+		memcpy(&((struct sockaddr_in6 *) &port->raddr.addr)->sin6_addr, proxyaddr.ip6.src_addr, 16);
+		((struct sockaddr_in6 *) &port->raddr.addr)->sin6_port = proxyaddr.ip6.src_port;
+	}
+	else
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("invalid proxy protocol connection type: %x", proxyheader.fam)));
+		return STATUS_ERROR;
+	}
+
+	/* If there is any more header data present, skip past it */
+	if (proxyaddrlen > sizeof(proxyaddr))
+		pq_discardbytes(proxyaddrlen - sizeof(proxyaddr));
+
+
+	pq_endmsgread();
+
+	/*
+	 * Log what we've done if connection logging is enabled. We log the proxy
+	 * connection here, and let the normal connection logging mechanism log
+	 * the unwrapped connection.
+	 */
+	if (Log_connections)
+	{
+		char		remote_host[NI_MAXHOST];
+		char		remote_port[NI_MAXSERV];
+		int			ret;
+
+		remote_host[0] = '\0';
+		remote_port[0] = '\0';
+		if ((ret = pg_getnameinfo_all(&raddr_save.addr, raddr_save.salen,
+									  remote_host, sizeof(remote_host),
+									  remote_port, sizeof(remote_port),
+									  (log_hostname ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV)) != 0)
+			ereport(WARNING,
+					(errmsg_internal("pg_getnameinfo_all() failed: %s",
+									 gai_strerror(ret))));
+
+		ereport(LOG,
+				(errmsg("proxy connection from: host=%s port=%s",
+						remote_host,
+						remote_port)));
+
+	}
+
+	return STATUS_OK;
+}
 
 /*
  * Read a client's startup packet and do something according to it.
@@ -4344,6 +4566,33 @@ BackendInitialize(Port *port)
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 	PG_SETMASK(&StartupBlockSig);
 
+	/*
+	 * Ready to begin client interaction.  We will give up and _exit(1) after
+	 * a time delay, so that a broken client can't hog a connection
+	 * indefinitely.  PreAuthDelay and any DNS interactions above don't count
+	 * against the time limit.
+	 *
+	 * Note: AuthenticationTimeout is applied here while waiting for the
+	 * startup packet, and then again in InitPostgres for the duration of any
+	 * authentication operations.  So a hostile client could tie up the
+	 * process for nearly twice AuthenticationTimeout before we kick him off.
+	 *
+	 * Note: because PostgresMain will call InitializeTimeouts again, the
+	 * registration of STARTUP_PACKET_TIMEOUT will be lost.  This is okay
+	 * since we never use it again after this function.
+	 */
+	RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler);
+
+	/* Check if this is a proxy connection and if so unwrap the proxying */
+	if (port->isProxy)
+	{
+		enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
+		if (UnwrapProxyConnection(port) != STATUS_OK)
+			proc_exit(0);
+		disable_timeout(STARTUP_PACKET_TIMEOUT, false);
+	}
+
+
 	/*
 	 * Get the remote host name and port for logging and status display.
 	 */
@@ -4395,28 +4644,11 @@ BackendInitialize(Port *port)
 		strspn(remote_host, "0123456789ABCDEFabcdef:") < strlen(remote_host))
 		port->remote_hostname = strdup(remote_host);
 
-	/*
-	 * Ready to begin client interaction.  We will give up and _exit(1) after
-	 * a time delay, so that a broken client can't hog a connection
-	 * indefinitely.  PreAuthDelay and any DNS interactions above don't count
-	 * against the time limit.
-	 *
-	 * Note: AuthenticationTimeout is applied here while waiting for the
-	 * startup packet, and then again in InitPostgres for the duration of any
-	 * authentication operations.  So a hostile client could tie up the
-	 * process for nearly twice AuthenticationTimeout before we kick him off.
-	 *
-	 * Note: because PostgresMain will call InitializeTimeouts again, the
-	 * registration of STARTUP_PACKET_TIMEOUT will be lost.  This is okay
-	 * since we never use it again after this function.
-	 */
-	RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler);
-	enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
-
 	/*
 	 * Receive the startup packet (which might turn out to be a cancel request
 	 * packet).
 	 */
+	enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
 	status = ProcessStartupPacket(port, false, false);
 
 	/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d626731723..9be50b1532 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -46,10 +46,12 @@
 #include "commands/user.h"
 #include "commands/vacuum.h"
 #include "commands/variable.h"
+#include "common/ip.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
+#include "libpq/ifaddr.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
@@ -227,6 +229,8 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou
 static void assign_recovery_target_lsn(const char *newval, void *extra);
 static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
 static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
+static bool check_proxy_servers(char **newval, void **extra, GucSource source);
+static void assign_proxy_servers(const char *newval, void *extra);
 
 /* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -2290,6 +2294,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"proxy_port", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+			gettext_noop("Sets the TCP port the server listens for PROXY connections on."),
+			NULL
+		},
+		&ProxyPortNumber,
+		0, 0, 65535,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"unix_socket_permissions", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
 			gettext_noop("Sets the access permissions of the Unix-domain socket."),
@@ -4241,6 +4255,17 @@ static struct config_string ConfigureNamesString[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"proxy_servers", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+			gettext_noop("Sets the addresses for trusted proxy servers."),
+			NULL,
+			GUC_LIST_INPUT
+		},
+		&TrustedProxyServersString,
+		"",
+		check_proxy_servers, assign_proxy_servers, NULL
+	},
+
 	{
 		/*
 		 * Can't be set by ALTER SYSTEM as it can lead to recursive definition
@@ -12228,4 +12253,108 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
 	return true;
 }
 
+static bool
+check_proxy_servers(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	struct sockaddr_storage *myextra;
+
+	/* Special case when it's empty */
+	if (**newval == '\0')
+	{
+		*extra = NULL;
+		return true;
+	}
+
+	/* Need a modifiable copy of string */
+	rawstring = pstrdup(*newval);
+
+	/* Parse string into list of identifiers */
+	if (!SplitIdentifierString(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		pfree(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	if (list_length(elemlist) == 0)
+	{
+		/* If it had only whitespace */
+		pfree(rawstring);
+		list_free(elemlist);
+
+		*extra = NULL;
+		return true;
+	}
+
+	/*
+	 * We store the result in an array of sockaddr_storage. The first entry is
+	 * just an overloaded int which holds the size of the array.
+	 */
+	myextra = (struct sockaddr_storage *) guc_malloc(ERROR, sizeof(struct sockaddr_storage) * (list_length(elemlist) * 2 + 1));
+	*((int *) &myextra[0]) = list_length(elemlist);
+
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *netmasktok = NULL;
+		int			ret;
+		struct addrinfo *gai_result;
+		struct addrinfo hints;
+
+		netmasktok = strchr(tok, '/');
+		if (netmasktok)
+		{
+			*netmasktok = '\0';
+			netmasktok++;
+		}
+
+		memset((char *) &hints, 0, sizeof(hints));
+		hints.ai_flags = AI_NUMERICHOST;
+		hints.ai_family = AF_UNSPEC;
+
+		ret = pg_getaddrinfo_all(tok, NULL, &hints, &gai_result);
+		if (ret != 0 || gai_result == NULL)
+		{
+			GUC_check_errdetail("Invalid IP addrress %s", tok);
+			pfree(rawstring);
+			list_free(elemlist);
+			free(myextra);
+			return false;
+		}
+
+		memcpy((char *) &myextra[foreach_current_index(l) * 2 + 1], gai_result->ai_addr, gai_result->ai_addrlen);
+		pg_freeaddrinfo_all(hints.ai_family, gai_result);
+
+		/* A NULL netmasktok means the fully set hostmask */
+		if (pg_sockaddr_cidr_mask(&myextra[foreach_current_index(l) * 2 + 2], netmasktok, myextra[foreach_current_index(l) * 2 + 1].ss_family) != 0)
+		{
+			if (netmasktok)
+				GUC_check_errdetail("Invalid netmask %s", netmasktok);
+			else
+				GUC_check_errdetail("Could not create netmask");
+			pfree(rawstring);
+			list_free(elemlist);
+			free(myextra);
+			return false;
+		}
+	}
+
+	pfree(rawstring);
+	list_free(elemlist);
+	*extra = (void *) myextra;
+
+	return true;
+}
+
+static void
+assign_proxy_servers(const char *newval, void *extra)
+{
+	TrustedProxyServers = (struct sockaddr_storage *) extra;
+}
+
 #include "guc-file.c"
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee06528bb0..aa7ac35f67 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -61,6 +61,8 @@
 					# defaults to 'localhost'; use '*' for all
 					# (change requires restart)
 #port = 5432				# (change requires restart)
+#proxy_servers = ''			# what IP/netmasks of proxy servers to trust
+					# (change requires restart)
 #max_connections = 100			# (change requires restart)
 #superuser_reserved_connections = 3	# (change requires restart)
 #unix_socket_directories = '/tmp'	# comma-separated list of directories
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7be1a67d69..57edda122a 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -121,6 +121,7 @@ typedef struct Port
 {
 	pgsocket	sock;			/* File descriptor */
 	bool		noblock;		/* is the socket in non-blocking mode? */
+	bool		isProxy;		/* is the connection using PROXY protocol */
 	ProtocolVersion proto;		/* FE/BE protocol version */
 	SockAddr	laddr;			/* local addr (postmaster) */
 	SockAddr	raddr;			/* remote addr (client) */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index e4e5c21565..549e2d86a7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -60,7 +60,7 @@ extern WaitEventSet *FeBeWaitSet;
 
 extern int	StreamServerPort(int family, const char *hostName,
 							 unsigned short portNumber, const char *unixSocketDir,
-							 pgsocket ListenSocket[], int MaxListen);
+							 pgsocket ListenSocket[], bool ProxyList[], bool isProxy, int MaxListen);
 extern int	StreamConnection(pgsocket server_fd, Port *port);
 extern void StreamClose(pgsocket sock);
 extern void TouchSocketFiles(void);
@@ -74,6 +74,8 @@ extern bool pq_is_reading_msg(void);
 extern int	pq_getmessage(StringInfo s, int maxlen);
 extern int	pq_getbyte(void);
 extern int	pq_peekbyte(void);
+extern int	pq_peekbytes(char *s, size_t len);
+extern int	pq_discardbytes(size_t len);
 extern int	pq_getbyte_if_available(unsigned char *c);
 extern int	pq_putbytes(const char *s, size_t len);
 
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index cfa59c4dc0..9ed219dfda 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -17,10 +17,13 @@
 extern bool EnableSSL;
 extern int	ReservedBackends;
 extern PGDLLIMPORT int PostPortNumber;
+extern PGDLLIMPORT int ProxyPortNumber;
 extern int	Unix_socket_permissions;
 extern char *Unix_socket_group;
 extern char *Unix_socket_directories;
 extern char *ListenAddresses;
+extern char *TrustedProxyServersString;
+extern struct sockaddr_storage *TrustedProxyServers;
 extern bool ClientAuthInProgress;
 extern int	PreAuthDelay;
 extern int	AuthenticationTimeout;

Reply via email to