Replication connection URI?

Started by Alex Shulginabout 11 years ago12 messages
#1Alex Shulgin
ash@commandprompt.com

Hackers,

It appears that replication connection doesn't support URI but only the
traditional conninfo string.

src/backend/replication/libpqwalreceiver/libpqwalreceiver.c:99: in libpqrcv_connect():

snprintf(conninfo_repl, sizeof(conninfo_repl),
"%s dbname=replication replication=true fallback_application_name=walreceiver",
conninfo);

A patch to fix this welcome?

--
Alex

PS: I wrote the original URI parser used in libpq.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#2Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Alex Shulgin (#1)
Re: Replication connection URI?

On 11/24/2014 02:41 PM, Alex Shulgin wrote:

Hackers,

It appears that replication connection doesn't support URI but only the
traditional conninfo string.

src/backend/replication/libpqwalreceiver/libpqwalreceiver.c:99: in libpqrcv_connect():

snprintf(conninfo_repl, sizeof(conninfo_repl),
"%s dbname=replication replication=true fallback_application_name=walreceiver",
conninfo);

A patch to fix this welcome?

Yeah, seems like an oversight. Hopefully you can fix that without
teaching libpqwalreceiver what connection URIs look like..

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Alex Shulgin
ash@commandprompt.com
In reply to: Heikki Linnakangas (#2)
3 attachment(s)
Re: Replication connection URI?

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

It appears that replication connection doesn't support URI but only the
traditional conninfo string.

src/backend/replication/libpqwalreceiver/libpqwalreceiver.c:99: in libpqrcv_connect():

snprintf(conninfo_repl, sizeof(conninfo_repl),
"%s dbname=replication replication=true fallback_application_name=walreceiver",
conninfo);

A patch to fix this welcome?

Yeah, seems like an oversight. Hopefully you can fix that without
teaching libpqwalreceiver what connection URIs look like..

Please see attached. We're lucky that PQconnectdbParams has an option
to parse and expand the first dbname parameter if it looks like a
connection string (or a URI).

The first patch is not on topic, I just spotted this missing check.

The second one is a self-contained fix, but the third one which is the
actual patch depends on the second one, because it specifies the dbname
keyword two times: first to parse the conninfo/URI, then to override any
dbname provided by the user with "replication" pseudo-database name.

Have a nice day!
--
Alex

Attachments:

0002-Allow-further-dbname-value-to-override-conninfo-pars.patchtext/x-diffDownload
>From 44d9d6a2c9cf5af83988f9d3b6eeb39c36104ef9 Mon Sep 17 00:00:00 2001
From: Alex Shulgin <ash@commandprompt.com>
Date: Mon, 24 Nov 2014 18:12:51 +0300
Subject: [PATCH 2/3] Allow further dbname=value to override conninfo parsed
 from an expanded dbname in conninfo_array_parse().

---
 src/interfaces/libpq/fe-connect.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
new file mode 100644
index d7f2ec2..5b45128
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
*************** conninfo_parse(const char *conninfo, PQE
*** 4302,4311 ****
   * Defaults are supplied (from a service file, environment variables, etc)
   * for unspecified options, but only if use_defaults is TRUE.
   *
!  * If expand_dbname is non-zero, and the value passed for keyword "dbname" is a
!  * connection string (as indicated by recognized_connection_string) then parse
!  * and process it, overriding any previously processed conflicting
!  * keywords. Subsequent keywords will take precedence, however.
   */
  static PQconninfoOption *
  conninfo_array_parse(const char *const * keywords, const char *const * values,
--- 4302,4313 ----
   * Defaults are supplied (from a service file, environment variables, etc)
   * for unspecified options, but only if use_defaults is TRUE.
   *
!  * If expand_dbname is non-zero, and the value passed for the first occurrence
!  * of "dbname" keyword is a connection string (as indicated by
!  * recognized_connection_string) then parse and process it, overriding any
!  * previously processed conflicting keywords. Subsequent keywords will take
!  * precedence, however. In particular, a further occurrence of "dbname" may
!  * override the dbname provided in the connection string.
   */
  static PQconninfoOption *
  conninfo_array_parse(const char *const * keywords, const char *const * values,
*************** conninfo_array_parse(const char *const *
*** 4381,4387 ****
  			}
  
  			/*
! 			 * If we are on the dbname parameter, and we have a parsed
  			 * connection string, copy those parameters across, overriding any
  			 * existing previous settings.
  			 */
--- 4383,4389 ----
  			}
  
  			/*
! 			 * If we are on the *first* dbname parameter, and we have a parsed
  			 * connection string, copy those parameters across, overriding any
  			 * existing previous settings.
  			 */
*************** conninfo_array_parse(const char *const *
*** 4415,4420 ****
--- 4417,4428 ----
  						}
  					}
  				}
+ 				/*
+ 				 * Clear out the parsed dbname, so that a possible further
+ 				 * occurrence of dbname have the chance to override it.
+ 				 */
+ 				PQconninfoFree(dbname_options);
+ 				dbname_options = NULL;
  			}
  			else
  			{
-- 
2.1.0

0003-Allow-URI-in-primary_conninfo-line-of-recovery.conf-.patchtext/x-diffDownload
>From 69e7c676e3af6f25520990cfb4337fc129741a95 Mon Sep 17 00:00:00 2001
From: Alex Shulgin <ash@commandprompt.com>
Date: Mon, 24 Nov 2014 18:54:01 +0300
Subject: [PATCH 3/3] Allow URI in primary_conninfo line of recovery.conf file.

---
 doc/src/sgml/high-availability.sgml                      |  6 +++---
 doc/src/sgml/recovery-config.sgml                        |  4 ++--
 src/backend/access/transam/recovery.conf.sample          |  3 ++-
 .../replication/libpqwalreceiver/libpqwalreceiver.c      | 16 +++++++---------
 4 files changed, 14 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
new file mode 100644
index d249959..61cf585
*** a/doc/src/sgml/high-availability.sgml
--- b/doc/src/sgml/high-availability.sgml
*************** protocol to make nodes agree on a serial
*** 681,689 ****
  
     <para>
       If you want to use streaming replication, fill in
!      <varname>primary_conninfo</> with a libpq connection string, including
!      the host name (or IP address) and any additional details needed to
!      connect to the primary server. If the primary needs a password for
       authentication, the password needs to be specified in
       <varname>primary_conninfo</> as well.
     </para>
--- 681,689 ----
  
     <para>
       If you want to use streaming replication, fill in
!      <varname>primary_conninfo</> with a libpq connection string or a URI,
!      including the host name (or IP address) and any additional details needed
!      to connect to the primary server. If the primary needs a password for
       authentication, the password needs to be specified in
       <varname>primary_conninfo</> as well.
     </para>
diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml
new file mode 100644
index 0f1ff34..9e17e20
*** a/doc/src/sgml/recovery-config.sgml
--- b/doc/src/sgml/recovery-config.sgml
*************** restore_command = 'copy "C:\\server\\arc
*** 342,349 ****
          </term>
          <listitem>
           <para>
!           Specifies a connection string to be used for the standby server
!           to connect with the primary. This string is in the format
            described in <xref linkend="libpq-connstring">. If any option is
            unspecified in this string, then the corresponding environment
            variable (see <xref linkend="libpq-envars">) is checked. If the
--- 342,349 ----
          </term>
          <listitem>
           <para>
!           Specifies a connection string or a URI to be used for the standby
!           server to connect with the primary. This string is in the format
            described in <xref linkend="libpq-connstring">. If any option is
            unspecified in this string, then the corresponding environment
            variable (see <xref linkend="libpq-envars">) is checked. If the
diff --git a/src/backend/access/transam/recovery.conf.sample b/src/backend/access/transam/recovery.conf.sample
new file mode 100644
index 7657df3..b1815be
*** a/src/backend/access/transam/recovery.conf.sample
--- b/src/backend/access/transam/recovery.conf.sample
***************
*** 116,124 ****
  # primary_conninfo
  #
  # If set, the PostgreSQL server will try to connect to the primary using this
! # connection string and receive XLOG records continuously.
  #
  #primary_conninfo = ''		# e.g. 'host=localhost port=5432'
  #
  # If set, the PostgreSQL server will use the specified replication slot when
  # connecting to the primary via streaming replication to control resource
--- 116,125 ----
  # primary_conninfo
  #
  # If set, the PostgreSQL server will try to connect to the primary using this
! # connection string or URI and receive XLOG records continuously.
  #
  #primary_conninfo = ''		# e.g. 'host=localhost port=5432'
+ 				# or 'postgres://localhost:5432/'
  #
  # If set, the PostgreSQL server will use the specified replication slot when
  # connecting to the primary via streaming replication to control resource
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
new file mode 100644
index 65e95c5..eebb3bb
*** a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
--- b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
*************** _PG_init(void)
*** 89,106 ****
  static void
  libpqrcv_connect(char *conninfo)
  {
! 	char		conninfo_repl[MAXCONNINFO + 75];
  
  	/*
! 	 * Connect using deliberately undocumented parameter: replication. The
! 	 * database name is ignored by the server in replication mode, but specify
! 	 * "replication" for .pgpass lookup.
  	 */
! 	snprintf(conninfo_repl, sizeof(conninfo_repl),
! 			 "%s dbname=replication replication=true fallback_application_name=walreceiver",
! 			 conninfo);
! 
! 	streamConn = PQconnectdb(conninfo_repl);
  	if (PQstatus(streamConn) != CONNECTION_OK)
  		ereport(ERROR,
  				(errmsg("could not connect to the primary server: %s",
--- 89,104 ----
  static void
  libpqrcv_connect(char *conninfo)
  {
! 	const char	*keys[] = { "dbname", "dbname",      "replication", "fallback_application_name", NULL };
! 	const char	*vals[] = { conninfo, "replication", "true",        "walreceiver",               NULL };
  
  	/*
! 	 * We exploit the expand_dbname parameter to process the connection string
! 	 * (or URI), then override the options that are necessary to establish the
! 	 * replication type of connection.  In particular, we override any
! 	 * database name provided in conninfo with "replication".
  	 */
! 	streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
  	if (PQstatus(streamConn) != CONNECTION_OK)
  		ereport(ERROR,
  				(errmsg("could not connect to the primary server: %s",
-- 
2.1.0

0001-Add-missing-check-on-OOM-in-expand_dbname-path-of-co.patchtext/x-diffDownload
>From 156e6faa96ad6a2ce58055ad72883ed78c576e5b Mon Sep 17 00:00:00 2001
From: Alex Shulgin <ash@commandprompt.com>
Date: Mon, 24 Nov 2014 16:55:50 +0300
Subject: [PATCH 1/3] Add missing check on OOM in expand_dbname path of
 conninfo_array_parse().

---
 src/interfaces/libpq/fe-connect.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
new file mode 100644
index 3fe8c21..d7f2ec2
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
*************** conninfo_array_parse(const char *const *
*** 4402,4407 ****
--- 4402,4415 ----
  								if (options[k].val)
  									free(options[k].val);
  								options[k].val = strdup(str_option->val);
+ 								if (!options[k].val)
+ 								{
+ 									printfPQExpBuffer(errorMessage,
+ 													  libpq_gettext("out of memory\n"));
+ 									PQconninfoFree(options);
+ 									PQconninfoFree(dbname_options);
+ 									return NULL;
+ 								}
  								break;
  							}
  						}
-- 
2.1.0

#4Alex Shulgin
ash@commandprompt.com
In reply to: Alex Shulgin (#3)
Re: Replication connection URI?

Alex Shulgin <ash@commandprompt.com> writes:

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

It appears that replication connection doesn't support URI but only the
traditional conninfo string.

src/backend/replication/libpqwalreceiver/libpqwalreceiver.c:99: in libpqrcv_connect():

snprintf(conninfo_repl, sizeof(conninfo_repl),
"%s dbname=replication replication=true fallback_application_name=walreceiver",
conninfo);

A patch to fix this welcome?

Yeah, seems like an oversight. Hopefully you can fix that without
teaching libpqwalreceiver what connection URIs look like..

Please see attached. We're lucky that PQconnectdbParams has an option
to parse and expand the first dbname parameter if it looks like a
connection string (or a URI).

The first patch is not on topic, I just spotted this missing check.

The second one is a self-contained fix, but the third one which is the
actual patch depends on the second one, because it specifies the dbname
keyword two times: first to parse the conninfo/URI, then to override any
dbname provided by the user with "replication" pseudo-database name.

These patches are really simple, I hope a committer will pick them up?
Or should I add them to the commitfest?

Also, I'd rather get this committed first, then rebase that
recovery.conf->GUC patch onto it and submit an updated version.

Thanks.
--
Alex

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Alex Shulgin (#3)
1 attachment(s)
Missing OOM checks in libpq (was Re: Replication connection URI?)

On 11/24/2014 06:05 PM, Alex Shulgin wrote:

The first patch is not on topic, I just spotted this missing check.

*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
*************** conninfo_array_parse(const char *const *
*** 4402,4407 ****
--- 4402,4415 ----
if (options[k].val)
free(options[k].val);
options[k].val = strdup(str_option->val);
+ 								if (!options[k].val)
+ 								{
+ 									printfPQExpBuffer(errorMessage,
+ 													  libpq_gettext("out of memory\n"));
+ 									PQconninfoFree(options);
+ 									PQconninfoFree(dbname_options);
+ 									return NULL;
+ 								}
break;
}
}

Oh. There are actually many more places in connection option parsing
that don't check the return value of strdup(). The one in fillPGConn
even has an XXX comment saying it probably should check it. You can get
quite strange behavior if one of them fails. If for example the strdup()
on dbname fails, you might end up connecting to different database than
intended. And if the "conn->sslmode = strdup(DefaultSSLMode);" call in
connectOptions2 fails, you'll get a segfault later because at least
connectDBstart assumes that sslmode is not NULL.

I think we need to fix all of those, and backpatch. Per attached.

- Heikki

Attachments:

fix-libpq-ooms.patchtext/x-diff; name=fix-libpq-ooms.patchDownload
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 3fe8c21..d4d8c3b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -333,7 +333,7 @@ static int	connectDBStart(PGconn *conn);
 static int	connectDBComplete(PGconn *conn);
 static PGPing internal_ping(PGconn *conn);
 static PGconn *makeEmptyPGconn(void);
-static void fillPGconn(PGconn *conn, PQconninfoOption *connOptions);
+static bool fillPGconn(PGconn *conn, PQconninfoOption *connOptions);
 static void freePGconn(PGconn *conn);
 static void closePGconn(PGconn *conn);
 static PQconninfoOption *conninfo_init(PQExpBuffer errorMessage);
@@ -585,7 +585,11 @@ PQconnectStartParams(const char *const * keywords,
 	/*
 	 * Move option values into conn structure
 	 */
-	fillPGconn(conn, connOptions);
+	if (!fillPGconn(conn, connOptions))
+	{
+		PQconninfoFree(connOptions);
+		return conn;
+	}
 
 	/*
 	 * Free the option info - all is in conn now
@@ -665,19 +669,19 @@ PQconnectStart(const char *conninfo)
 	return conn;
 }
 
-static void
+/*
+ * Move option values into conn structure
+ *
+ * Don't put anything cute here --- intelligence should be in
+ * connectOptions2 ...
+ *
+ * Returns true on success. On failure, returns false and sets error message.
+ */
+static bool
 fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
 {
 	const internalPQconninfoOption *option;
 
-	/*
-	 * Move option values into conn structure
-	 *
-	 * Don't put anything cute here --- intelligence should be in
-	 * connectOptions2 ...
-	 *
-	 * XXX: probably worth checking strdup() return value here...
-	 */
 	for (option = PQconninfoOptions; option->keyword; option++)
 	{
 		const char *tmp = conninfo_getval(connOptions, option->keyword);
@@ -688,9 +692,22 @@ fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
 
 			if (*connmember)
 				free(*connmember);
-			*connmember = tmp ? strdup(tmp) : NULL;
+			if (tmp)
+			{
+				*connmember = strdup(tmp);
+				if (*connmember == NULL)
+				{
+					printfPQExpBuffer(&conn->errorMessage,
+									  libpq_gettext("out of memory\n"));
+					return false;
+				}
+			}
+			else
+				*connmember = NULL;
 		}
 	}
+
+	return true;
 }
 
 /*
@@ -723,7 +740,12 @@ connectOptions1(PGconn *conn, const char *conninfo)
 	/*
 	 * Move option values into conn structure
 	 */
-	fillPGconn(conn, connOptions);
+	if (!fillPGconn(conn, connOptions))
+	{
+		conn->status = CONNECTION_BAD;
+		PQconninfoFree(connOptions);
+		return false;
+	}
 
 	/*
 	 * Free the option info - all is in conn now
@@ -753,6 +775,8 @@ connectOptions2(PGconn *conn)
 		if (conn->dbName)
 			free(conn->dbName);
 		conn->dbName = strdup(conn->pguser);
+		if (!conn->dbName)
+			goto oom_error;
 	}
 
 	/*
@@ -765,7 +789,12 @@ connectOptions2(PGconn *conn)
 		conn->pgpass = PasswordFromFile(conn->pghost, conn->pgport,
 										conn->dbName, conn->pguser);
 		if (conn->pgpass == NULL)
+		{
 			conn->pgpass = strdup(DefaultPassword);
+			if (!conn->pgpass)
+				goto oom_error;
+
+		}
 		else
 			conn->dot_pgpass_used = true;
 	}
@@ -823,7 +852,11 @@ connectOptions2(PGconn *conn)
 #endif
 	}
 	else
+	{
 		conn->sslmode = strdup(DefaultSSLMode);
+		if (!conn->sslmode)
+			goto oom_error;
+	}
 
 	/*
 	 * Resolve special "auto" client_encoding from the locale
@@ -833,6 +866,8 @@ connectOptions2(PGconn *conn)
 	{
 		free(conn->client_encoding_initial);
 		conn->client_encoding_initial = strdup(pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true)));
+		if (!conn->client_encoding_initial)
+			goto oom_error;
 	}
 
 	/*
@@ -843,6 +878,13 @@ connectOptions2(PGconn *conn)
 	conn->options_valid = true;
 
 	return true;
+
+oom_error:
+	conn->options_valid = false;
+	conn->status = CONNECTION_BAD;
+	printfPQExpBuffer(&conn->errorMessage,
+					  libpq_gettext("out of memory\n"));
+	return false;
 }
 
 /*
@@ -937,6 +979,8 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 			if (conn->dbName)
 				free(conn->dbName);
 			conn->dbName = strdup(dbName);
+			if (!conn->dbName)
+				goto oom_error;
 		}
 	}
 
@@ -949,6 +993,8 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 		if (conn->pghost)
 			free(conn->pghost);
 		conn->pghost = strdup(pghost);
+		if (!conn->pghost)
+			goto oom_error;
 	}
 
 	if (pgport && pgport[0] != '\0')
@@ -956,6 +1002,8 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 		if (conn->pgport)
 			free(conn->pgport);
 		conn->pgport = strdup(pgport);
+		if (!conn->pgport)
+			goto oom_error;
 	}
 
 	if (pgoptions && pgoptions[0] != '\0')
@@ -963,6 +1011,8 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 		if (conn->pgoptions)
 			free(conn->pgoptions);
 		conn->pgoptions = strdup(pgoptions);
+		if (!conn->pgoptions)
+			goto oom_error;
 	}
 
 	if (pgtty && pgtty[0] != '\0')
@@ -970,6 +1020,8 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 		if (conn->pgtty)
 			free(conn->pgtty);
 		conn->pgtty = strdup(pgtty);
+		if (!conn->pgtty)
+			goto oom_error;
 	}
 
 	if (login && login[0] != '\0')
@@ -977,6 +1029,8 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 		if (conn->pguser)
 			free(conn->pguser);
 		conn->pguser = strdup(login);
+		if (!conn->pguser)
+			goto oom_error;
 	}
 
 	if (pwd && pwd[0] != '\0')
@@ -984,6 +1038,8 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 		if (conn->pgpass)
 			free(conn->pgpass);
 		conn->pgpass = strdup(pwd);
+		if (!conn->pgpass)
+			goto oom_error;
 	}
 
 	/*
@@ -999,6 +1055,12 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 		(void) connectDBComplete(conn);
 
 	return conn;
+
+oom_error:
+	conn->status = CONNECTION_BAD;
+	printfPQExpBuffer(&conn->errorMessage,
+					  libpq_gettext("out of memory\n"));
+	return false;
 }
 
 
@@ -3760,7 +3822,16 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 				if (strcmp(options[i].keyword, optname) == 0)
 				{
 					if (options[i].val == NULL)
+					{
 						options[i].val = strdup(optval);
+						if (!options[i].val)
+						{
+							printfPQExpBuffer(errorMessage,
+											libpq_gettext("out of memory\n"));
+							free(result);
+							return 3;
+						}
+					}
 					found_keyword = true;
 					break;
 				}
@@ -3983,6 +4054,13 @@ parseServiceFile(const char *serviceFile,
 					{
 						if (options[i].val == NULL)
 							options[i].val = strdup(val);
+						if (!options[i].val)
+						{
+							printfPQExpBuffer(errorMessage,
+											libpq_gettext("out of memory\n"));
+							fclose(f);
+							return 3;
+						}
 						found_keyword = true;
 						break;
 					}
@@ -4402,6 +4480,14 @@ conninfo_array_parse(const char *const * keywords, const char *const * values,
 								if (options[k].val)
 									free(options[k].val);
 								options[k].val = strdup(str_option->val);
+								if (!options[k].val)
+								{
+									printfPQExpBuffer(errorMessage,
+													  libpq_gettext("out of memory\n"));
+									PQconninfoFree(options);
+									PQconninfoFree(dbname_options);
+									return NULL;
+								}
 								break;
 							}
 						}
@@ -4596,20 +4682,22 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
 {
 	int			prefix_len;
 	char	   *p;
-	char	   *buf = strdup(uri);		/* need a modifiable copy of the input
-										 * URI */
-	char	   *start = buf;
+	char	   *buf;
+	char	   *start;
 	char		prevchar = '\0';
 	char	   *user = NULL;
 	char	   *host = NULL;
 	bool		retval = false;
 
+	/* need a modifiable copy of the input URI */
+	buf = strdup(uri);
 	if (buf == NULL)
 	{
 		printfPQExpBuffer(errorMessage,
 						  libpq_gettext("out of memory\n"));
 		return false;
 	}
+	start = buf;
 
 	/* Skip the URI prefix */
 	prefix_len = uri_prefix_length(uri);
@@ -4951,15 +5039,17 @@ conninfo_uri_parse_params(char *params,
 static char *
 conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
 {
-	char	   *buf = malloc(strlen(str) + 1);
-	char	   *p = buf;
+	char	   *buf;
+	char	   *p;
 	const char *q = str;
 
+	buf = malloc(strlen(str) + 1);
 	if (buf == NULL)
 	{
 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
 		return NULL;
 	}
+	p = buf;
 
 	for (;;)
 	{
@@ -5104,7 +5194,6 @@ conninfo_storeval(PQconninfoOption *connOptions,
 	else
 	{
 		value_copy = strdup(value);
-
 		if (value_copy == NULL)
 		{
 			printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
@@ -5672,6 +5761,12 @@ PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
 		ret = strdup(t);
 		fclose(fp);
 
+		if (!ret)
+		{
+			/* Out of memory. XXX: an error message would be nice. */
+			return NULL;
+		}
+
 		/* De-escape password. */
 		for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2)
 		{
#6Alex Shulgin
ash@commandprompt.com
In reply to: Heikki Linnakangas (#5)
Re: Missing OOM checks in libpq (was Re: Replication connection URI?)

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

On 11/24/2014 06:05 PM, Alex Shulgin wrote:

The first patch is not on topic, I just spotted this missing check.

*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
*************** conninfo_array_parse(const char *const *
*** 4402,4407 ****
--- 4402,4415 ----
if (options[k].val)
free(options[k].val);
options[k].val = strdup(str_option->val);
+ 								if (!options[k].val)
+ 								{
+ 									printfPQExpBuffer(errorMessage,
+ 													  libpq_gettext("out of memory\n"));
+ 									PQconninfoFree(options);
+ 									PQconninfoFree(dbname_options);
+ 									return NULL;
+ 								}
break;
}
}

Oh. There are actually many more places in connection option parsing
that don't check the return value of strdup(). The one in fillPGConn
even has an XXX comment saying it probably should check it. You can
get quite strange behavior if one of them fails. If for example the
strdup() on dbname fails, you might end up connecting to different
database than intended. And if the "conn->sslmode =
strdup(DefaultSSLMode);" call in connectOptions2 fails, you'll get a
segfault later because at least connectDBstart assumes that sslmode is
not NULL.

I think we need to fix all of those, and backpatch. Per attached.

Yikes! Looks sane to me.

I've checked that patches #2 and #3 can be applied on top of this, w/o
rebasing.

--
Alex

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Alex Shulgin (#6)
Re: Missing OOM checks in libpq (was Re: Replication connection URI?)

On 11/25/2014 01:37 PM, Alex Shulgin wrote:

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

On 11/24/2014 06:05 PM, Alex Shulgin wrote:

The first patch is not on topic, I just spotted this missing check.

*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
*************** conninfo_array_parse(const char *const *
*** 4402,4407 ****
--- 4402,4415 ----
if (options[k].val)
free(options[k].val);
options[k].val = strdup(str_option->val);
+ 								if (!options[k].val)
+ 								{
+ 									printfPQExpBuffer(errorMessage,
+ 													  libpq_gettext("out of memory\n"));
+ 									PQconninfoFree(options);
+ 									PQconninfoFree(dbname_options);
+ 									return NULL;
+ 								}
break;
}
}

Oh. There are actually many more places in connection option parsing
that don't check the return value of strdup(). The one in fillPGConn
even has an XXX comment saying it probably should check it. You can
get quite strange behavior if one of them fails. If for example the
strdup() on dbname fails, you might end up connecting to different
database than intended. And if the "conn->sslmode =
strdup(DefaultSSLMode);" call in connectOptions2 fails, you'll get a
segfault later because at least connectDBstart assumes that sslmode is
not NULL.

I think we need to fix all of those, and backpatch. Per attached.

Yikes! Looks sane to me.

Ok thanks, committed. It didn't apply cleanly to 9.0, 9.1 and 9.2, so
the patch for those branches looks a bit different.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Alex Shulgin
ash@commandprompt.com
In reply to: Heikki Linnakangas (#7)
Re: Missing OOM checks in libpq (was Re: Replication connection URI?)

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

I think we need to fix all of those, and backpatch. Per attached.

Yikes! Looks sane to me.

Ok thanks, committed. It didn't apply cleanly to 9.0, 9.1 and 9.2, so
the patch for those branches looks a bit different.

Great. Are you looking at the actual replication URI patch? Or should
I add it to commitfest so we don't lose track of it?

--
Alex

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Alex Shulgin (#3)
Re: Replication connection URI?

On 11/24/2014 06:05 PM, Alex Shulgin wrote:

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

It appears that replication connection doesn't support URI but only the
traditional conninfo string.

src/backend/replication/libpqwalreceiver/libpqwalreceiver.c:99: in libpqrcv_connect():

snprintf(conninfo_repl, sizeof(conninfo_repl),
"%s dbname=replication replication=true fallback_application_name=walreceiver",
conninfo);

A patch to fix this welcome?

Yeah, seems like an oversight. Hopefully you can fix that without
teaching libpqwalreceiver what connection URIs look like..

Please see attached. We're lucky that PQconnectdbParams has an option
to parse and expand the first dbname parameter if it looks like a
connection string (or a URI).

The first patch is not on topic, I just spotted this missing check.

The second one is a self-contained fix, but the third one which is the
actual patch depends on the second one, because it specifies the dbname
keyword two times: first to parse the conninfo/URI, then to override any
dbname provided by the user with "replication" pseudo-database name.

Hmm. Should we backpatch the second patch? It sure seems like an
oversight rather than deliberate that you can't override dbname from the
connection string with a later dbname keyword. I'd say "yes".

How about the third patch? Probably not; it was an oversight with the
connection URI patch that it could not be used in primary_conninfo, but
it's not a big deal in practice as you can always use a non-URI
connection string instead.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Heikki Linnakangas (#9)
Re: Replication connection URI?

On 11/25/2014 05:11 PM, Heikki Linnakangas wrote:

On 11/24/2014 06:05 PM, Alex Shulgin wrote:

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

It appears that replication connection doesn't support URI but only the
traditional conninfo string.

src/backend/replication/libpqwalreceiver/libpqwalreceiver.c:99: in libpqrcv_connect():

snprintf(conninfo_repl, sizeof(conninfo_repl),
"%s dbname=replication replication=true fallback_application_name=walreceiver",
conninfo);

A patch to fix this welcome?

Yeah, seems like an oversight. Hopefully you can fix that without
teaching libpqwalreceiver what connection URIs look like..

Please see attached. We're lucky that PQconnectdbParams has an option
to parse and expand the first dbname parameter if it looks like a
connection string (or a URI).

The first patch is not on topic, I just spotted this missing check.

The second one is a self-contained fix, but the third one which is the
actual patch depends on the second one, because it specifies the dbname
keyword two times: first to parse the conninfo/URI, then to override any
dbname provided by the user with "replication" pseudo-database name.

Hmm. Should we backpatch the second patch? It sure seems like an
oversight rather than deliberate that you can't override dbname from the
connection string with a later dbname keyword. I'd say "yes".

How about the third patch? Probably not; it was an oversight with the
connection URI patch that it could not be used in primary_conninfo, but
it's not a big deal in practice as you can always use a non-URI
connection string instead.

Ok, committed the second patch to all stable branches, and the third
patch to master.

In the second patch, I added a sentence to the docs to mention that only
the first "dbname" parameter is expanded. It's debatable if that's what
we actually want. It would be handy to be able to merge multiple
connection strings, by specifying multiple dbname parameters. But now
the docs at least match the code, changing the behavior would be a
bigger change.

From the third patch, I removed the docs changes. It's necessary to say
"connection string or URI" everywhere, the URI format is just one kind
of a connection string. I also edited the code that builds the
keyword/value array, to make it a bit more readable.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Alex Shulgin
ash@commandprompt.com
In reply to: Heikki Linnakangas (#10)
Re: Replication connection URI?

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

The second one is a self-contained fix, but the third one which is the
actual patch depends on the second one, because it specifies the dbname
keyword two times: first to parse the conninfo/URI, then to override any
dbname provided by the user with "replication" pseudo-database name.

Hmm. Should we backpatch the second patch? It sure seems like an
oversight rather than deliberate that you can't override dbname from the
connection string with a later dbname keyword. I'd say "yes".

How about the third patch? Probably not; it was an oversight with the
connection URI patch that it could not be used in primary_conninfo, but
it's not a big deal in practice as you can always use a non-URI
connection string instead.

Ok, committed the second patch to all stable branches, and the third
patch to master.

In the second patch, I added a sentence to the docs to mention that
only the first "dbname" parameter is expanded. It's debatable if
that's what we actually want. It would be handy to be able to merge
multiple connection strings, by specifying multiple dbname
parameters. But now the docs at least match the code, changing the
behavior would be a bigger change.

From the third patch, I removed the docs changes. It's necessary to
say "connection string or URI" everywhere, the URI format is just one
kind of a connection string. I also edited the code that builds the
keyword/value array, to make it a bit more readable.

Yay, many thanks! :-)

--
Alex

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Oleksandr Shulgin
oleksandr.shulgin@zalando.de
In reply to: Heikki Linnakangas (#10)
Re: Replication connection URI?

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

On 11/25/2014 05:11 PM, Heikki Linnakangas wrote:

On 11/24/2014 06:05 PM, Alex Shulgin wrote:

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

It appears that replication connection doesn't support URI but only the
traditional conninfo string.

src/backend/replication/libpqwalreceiver/libpqwalreceiver.c:99: in libpqrcv_connect():

snprintf(conninfo_repl, sizeof(conninfo_repl),
"%s dbname=replication replication=true fallback_application_name=walreceiver",
conninfo);

A patch to fix this welcome?

Yeah, seems like an oversight. Hopefully you can fix that without
teaching libpqwalreceiver what connection URIs look like..

Please see attached. We're lucky that PQconnectdbParams has an option
to parse and expand the first dbname parameter if it looks like a
connection string (or a URI).

The first patch is not on topic, I just spotted this missing check.

The second one is a self-contained fix, but the third one which is the
actual patch depends on the second one, because it specifies the dbname
keyword two times: first to parse the conninfo/URI, then to override any
dbname provided by the user with "replication" pseudo-database name.

Hmm. Should we backpatch the second patch? It sure seems like an
oversight rather than deliberate that you can't override dbname from the
connection string with a later dbname keyword. I'd say "yes".

How about the third patch? Probably not; it was an oversight with the
connection URI patch that it could not be used in primary_conninfo, but
it's not a big deal in practice as you can always use a non-URI
connection string instead.

Ok, committed the second patch to all stable branches, and the third
patch to master.

It still looks like a bug that primary_conninfo doesn't accept URIs,
even though they were supposed to be handled transparently by all
interfaces using libpq...

Any chance we reconsider and back-patch this up to 9.2?

--
Alex

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers