Re: [HACKERS] expand_dbname in postgres_fdw - Mailing list pgsql-hackers

From Arseny Sher
Subject Re: [HACKERS] expand_dbname in postgres_fdw
Date
Msg-id 87y3ra9ozk.fsf@ars-thinkpad
Whole thread Raw
In response to Re: [HACKERS] expand_dbname in postgres_fdw  (Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>)
Responses Re: [HACKERS] expand_dbname in postgres_fdw
List pgsql-hackers
On Thu, Jul 27, 2017 at 12:21 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> The main problem to my mind is that a connection string could possibly
> override items meant to be specified elsewhere.  In particular it ought
> not be allowed to specify the remote username or password, because those
> are supposed to come from the user mapping object not the server object.
> I suspect you could break things by trying to specify client_encoding
> there, as well.

Attached patch allows dbname expansion and makes sure that it doesn't
contain any invalid options. Whether you decide to commit it or not
(at the moment I don't see any security implications, at least not more than
in usual dbname expansion usage, e.g. in psql, but who knows), it seems
to me that the documentation should be updated since currently it is not
clear on the subject, as the beginning of this thread proves.
From 09e3924501d219331b8b8fca12a9ea35a689ba10 Mon Sep 17 00:00:00 2001
From: Arseny Sher <sher-ars@yandex.ru>
Date: Thu, 27 Jul 2017 12:41:17 +0300
Subject: [PATCH] Allow 'dbname' option contain full-fledged connstring in
 postgres_fdw

And if this is the case, validate that it doesn't contain any invalid options
such as client_encoding or user.
---
 contrib/postgres_fdw/connection.c |  2 +-
 contrib/postgres_fdw/option.c     | 80 ++++++++++++++++++++++++++++++++-------
 2 files changed, 68 insertions(+), 14 deletions(-)

diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index be4ec07cf9..bfcd9ed2e3 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -263,7 +263,7 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
         /* verify connection parameters and make connection */
         check_conn_params(keywords, values);
 
-        conn = PQconnectdbParams(keywords, values, false);
+        conn = PQconnectdbParams(keywords, values, true);
         if (!conn || PQstatus(conn) != CONNECTION_OK)
             ereport(ERROR,
                     (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 67e1c59951..26c11e5179 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -50,8 +50,10 @@ static PQconninfoOption *libpq_options;
  * Helper functions
  */
 static void InitPgFdwOptions(void);
+static void validate_dbname(const char *dbname);
+static void get_opts_hint(StringInfo buf, Oid context, bool libpq_options);
 static bool is_valid_option(const char *keyword, Oid context);
-static bool is_libpq_option(const char *keyword);
+static bool is_libpq_option(const char *keyword, Oid context);
 
 
 /*
@@ -86,16 +88,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
              * Unknown option specified, complain about it. Provide a hint
              * with list of valid options for the object.
              */
-            PgFdwOption *opt;
             StringInfoData buf;
-
-            initStringInfo(&buf);
-            for (opt = postgres_fdw_options; opt->keyword; opt++)
-            {
-                if (catalog == opt->optcontext)
-                    appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
-                                     opt->keyword);
-            }
+            get_opts_hint(&buf, catalog, false);
 
             ereport(ERROR,
                     (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
@@ -143,6 +137,10 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
                          errmsg("%s requires a non-negative integer value",
                                 def->defname)));
         }
+        else if (strcmp(def->defname, "dbname") == 0)
+        {
+            validate_dbname(defGetString(def));
+        }
     }
 
     PG_RETURN_VOID();
@@ -250,6 +248,59 @@ InitPgFdwOptions(void)
 }
 
 /*
+ * If dbname param specified, make sure it doesn't carry invalid
+ * options if it is a connstring.
+ */
+static void
+validate_dbname(const char *dbname)
+{
+    PQconninfoOption *lopts;
+    PQconninfoOption *lopt;
+
+    /* If it is not a connstring, just skip the check */
+    if ((lopts = PQconninfoParse(dbname, NULL)) != NULL)
+    {
+        for (lopt = lopts; lopt->keyword; lopt++)
+        {
+            if (lopt->val != NULL && !is_libpq_option(lopt->keyword,
+                                                      ForeignServerRelationId))
+            {
+                StringInfoData buf;
+                get_opts_hint(&buf, ForeignServerRelationId, true);
+
+                ereport(ERROR,
+                        (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+                         errmsg("invalid option in dbname connstring \"%s\"",
+                                lopt->keyword),
+                         errhint("Valid options in this context are: %s",
+                                 buf.data)));
+            }
+        }
+        PQconninfoFree(lopts);
+    }
+}
+
+/*
+ * Put to 'buf' a hint with list of valid options for the object 'context'. If
+ * libpq_options is true, only libpq options are provided. 'buf' must point to
+ * existing StringInfoData struct when called.
+ */
+static void
+get_opts_hint(StringInfo buf, Oid context, bool libpq_options)
+{
+    PgFdwOption *opt;
+
+    initStringInfo(buf);
+    for (opt = postgres_fdw_options; opt->keyword; opt++)
+    {
+        if (context == opt->optcontext &&
+            (!libpq_options || opt->is_libpq_opt))
+            appendStringInfo(buf, "%s%s", (buf->len > 0) ? ", " : "",
+                             opt->keyword);
+    }
+}
+
+/*
  * Check whether the given option is one of the valid postgres_fdw options.
  * context is the Oid of the catalog holding the object the option is for.
  */
@@ -271,9 +322,11 @@ is_valid_option(const char *keyword, Oid context)
 
 /*
  * Check whether the given option is one of the valid libpq options.
+ * context is the Oid of the catalog holding the object the option is for;
+ * if it is InvalidOid, don't check the catalog.
  */
 static bool
-is_libpq_option(const char *keyword)
+is_libpq_option(const char *keyword, Oid context)
 {
     PgFdwOption *opt;
 
@@ -281,7 +334,8 @@ is_libpq_option(const char *keyword)
 
     for (opt = postgres_fdw_options; opt->keyword; opt++)
     {
-        if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
+        if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0 &&
+            (context == InvalidOid || context == opt->optcontext))
             return true;
     }
 
@@ -308,7 +362,7 @@ ExtractConnectionOptions(List *defelems, const char **keywords,
     {
         DefElem    *d = (DefElem *) lfirst(lc);
 
-        if (is_libpq_option(d->defname))
+        if (is_libpq_option(d->defname, InvalidOid))
         {
             keywords[i] = d->defname;
             values[i] = defGetString(d);
-- 
2.11.0



--
Arseny Sher
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

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

pgsql-hackers by date:

Previous
From: Masahiko Sawada
Date:
Subject: Re: [HACKERS] Increase Vacuum ring buffer.
Next
From: Michael Paquier
Date:
Subject: Re: [HACKERS] A couple of postgresql.conf.sample discrepancies