hi

2016-10-04 9:18 GMT+02:00 Gilles Darold <gilles.dar...@dalibo.com>:

> Le 03/10/2016 à 23:23, Gilles Darold a écrit :
> > Le 03/10/2016 à 23:03, Robert Haas a écrit :
> >> On Mon, Oct 3, 2016 at 3:54 PM, Gilles Darold <gil...@darold.net>
> wrote:
> >>> 4) An other problem is that like this this patch will allow anyone to
> upload into a
> >>> column the content of any system file that can be read by postgres
> system user
> >>> and then allow non system user to read its content.
> >> I thought this was a client-side feature, so that it would let a
> >> client upload any file that the client can read, but not things that
> >> can only be read by the postgres system user.
> >>
> > Yes that's right, sorry for the noise, forget this fourth report.
> >
>
> After some more though there is still a security issue here. For a
> PostgreSQL user who also have login acces to the server, it is possible
> to read any file that the postgres system user can read, especially a
> .pgpass or a recovery.conf containing password.
>

here is new update - some mentioned issues are fixed + regress tests and
docs

regards

Pavel

>
>
> --
> Gilles Darold
> Consultant PostgreSQL
> http://dalibo.com - http://dalibo.org
>
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 4806e77..9fb9cd9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2714,6 +2714,24 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
       </varlistentry>
 
       <varlistentry>
+        <term><literal>\setfileref <replaceable class="parameter">name</replaceable> [ <replaceable class="parameter">value</replaceable> ]</literal></term>
+
+        <listitem>
+        <para>
+        Sets the internal variable name as a reference to the file path
+        set as value. To unset a variable, use the <command>\unset</command> command.
+
+        File references are shown as file path prefixed with the ^ character
+        when using the <command>\set</command> command alone.
+
+        Valid variable names can contain characters, digits, and underscores.
+        See the section Variables below for details. Variable names are
+        case-sensitive.
+        </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><literal>\sf[+] <replaceable class="parameter">function_description</> </literal></term>
 
         <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a9a2fdb..daa40b2 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1363,6 +1363,41 @@ exec_command(const char *cmd,
 		free(envval);
 	}
 
+	/* \setfileref - set variable by reference on file */
+	else if (strcmp(cmd, "setfileref") == 0)
+	{
+		char	   *name = psql_scan_slash_option(scan_state,
+												  OT_NORMAL, NULL, false);
+
+		char	   *ref = psql_scan_slash_option(scan_state,
+										 OT_NORMAL, NULL, false);
+
+		success = false;
+
+		if (!name && !ref)
+		{
+			/* list all variables */
+			PrintSetFileRefVariables(pset.vars);
+			success = true;
+		}
+		else
+		{
+			if (!name || !ref)
+			{
+				psql_error("\\%s: missing required argument\n", cmd);
+				success = false;
+			}
+			else
+			{
+				if (!SetFileRef(pset.vars, name, ref))
+				{
+					psql_error("\\%s: error while setting variable\n", cmd);
+					success = false;
+				}
+			}
+		}
+	}
+
 	/* \sf -- show a function's source code */
 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
 	{
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index a7789df..64fa5a2 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -12,6 +12,8 @@
 #include <limits.h>
 #include <math.h>
 #include <signal.h>
+#include <sys/stat.h>
+
 #ifndef WIN32
 #include <unistd.h>				/* for write() */
 #else
@@ -25,6 +27,7 @@
 #include "settings.h"
 #include "command.h"
 #include "copy.h"
+#include "catalog/pg_type.h"
 #include "crosstabview.h"
 #include "fe_utils/mbprint.h"
 
@@ -33,7 +36,6 @@ static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
 static bool command_no_begin(const char *query);
 static bool is_select_command(const char *query);
 
-
 /*
  * openQueryOutputFile --- attempt to open a query output file
  *
@@ -109,6 +111,143 @@ setQFout(const char *fname)
 	return true;
 }
 
+void
+psql_reset_query_params(void)
+{
+	int			i;
+
+	for (i = 0; i < pset.nparams; i++)
+		if (pset.params[i] != NULL)
+		{
+			PQfreemem(pset.params[i]);
+			pset.params[i] = NULL;
+		}
+
+	pset.nparams = 0;
+}
+
+/*
+ * Load a content of the file_ref related file to query params buffer.
+ * When escaping is requested, then the text content is expected.
+ * Without escaping the bytea content is expected and related bytea
+ * escaping is processed.
+ */
+static char *
+get_file_ref_content(const char *value, bool escape, bool as_ident)
+{
+	PQExpBufferData		buffer;
+	FILE			   *fd = NULL;
+	char			   *fname;
+	char			   *escaped_value;
+	char				line[1024];
+	size_t				size;
+	struct				stat fst;
+
+	fname = pstrdup(value);
+
+	expand_tilde(&fname);
+	if (!fname)
+	{
+		psql_error("missing valid path to file\n");
+		return NULL;
+	}
+
+	canonicalize_path(fname);
+
+	fd = fopen(fname, PG_BINARY_R);
+	if (!fd)
+	{
+		psql_error("%s: %s\n", fname, strerror(errno));
+		PQfreemem(fname);
+		return NULL;
+	}
+
+	/* ensure, max size of file content < 1GB */
+	if (fstat(fileno(fd), &fst) == -1)
+	{
+		psql_error("%s: %s\n", fname, strerror(errno));
+		PQfreemem(fname);
+		return NULL;
+	}
+
+	if (fst.st_size > ((int64) 1024) * 1024 * 1024)
+	{
+		psql_error("file %s is too big (bigger than 1GB)\n", fname);
+		PQfreemem(fname);
+		return NULL;
+	}
+
+	/* can append another parameter */
+	if (pset.nparams >= MAX_BINARY_PARAMS)
+	{
+		psql_error("too much binary parameters");
+		PQfreemem(fname);
+		return NULL;
+	}
+
+	if (!pset.db)
+	{
+		psql_error("cannot escape without active connection\n");
+		PQfreemem(fname);
+		return NULL;
+	}
+
+	initPQExpBuffer(&buffer);
+
+	while ((size = fread(line, 1, sizeof(line), fd)) > 0)
+		appendBinaryPQExpBuffer(&buffer, line, size);
+
+	if (ferror(fd))
+	{
+		psql_error("%s: %s\n", fname, strerror(errno));
+		PQfreemem(fname);
+		termPQExpBuffer(&buffer);
+		return NULL;
+	}
+
+	if (escape)
+	{
+		if (as_ident)
+		{
+			/* Identifiers should not be passed as query parameters */
+			psql_error("A file reference cannot be used as a identifier");
+			PQfreemem(fname);
+			termPQExpBuffer(&buffer);
+			return NULL;
+		}
+		else
+		{
+			/* escaping is not necessary for text parameters */
+			escaped_value = pstrdup(buffer.data);
+			pset.paramTypes[pset.nparams] = TEXTOID;
+		}
+	}
+	else
+	{
+		escaped_value = (char *)
+				PQescapeByteaConn(pset.db,
+					(const unsigned char *) buffer.data, buffer.len, &size);
+		pset.paramTypes[pset.nparams] = BYTEAOID;
+	}
+
+	/* fname, buffer is not necessary longer */
+	PQfreemem(fname);
+	termPQExpBuffer(&buffer);
+
+	if (escaped_value == NULL)
+	{
+		const char *error = PQerrorMessage(pset.db);
+
+		psql_error("%s", error);
+		return NULL;
+	}
+
+	pset.params[pset.nparams] = escaped_value;
+
+	snprintf(line, sizeof(line) - 1, "$%d", ++pset.nparams);
+
+	return pstrdup(line);
+}
 
 /*
  * Variable-fetching callback for flex lexer
@@ -125,11 +264,15 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 {
 	char	   *result;
 	const char *value;
+	bool        is_file_ref;
 
-	value = GetVariable(pset.vars, varname);
+	value = (char *) GetVariableOrFileRef(pset.vars, varname, &is_file_ref);
 	if (!value)
 		return NULL;
 
+	if (is_file_ref)
+		return get_file_ref_content(value, escape, as_ident);
+
 	if (escape)
 	{
 		char	   *escaped_value;
@@ -1287,7 +1430,16 @@ SendQuery(const char *query)
 		if (pset.timing)
 			INSTR_TIME_SET_CURRENT(before);
 
-		results = PQexec(pset.db, query);
+		if (pset.nparams > 0)
+			results = PQexecParams(pset.db, query,
+										pset.nparams,
+										pset.paramTypes,
+										(const char * const *) pset.params,
+										NULL,
+										NULL,
+										0);
+		else
+			results = PQexec(pset.db, query);
 
 		/* these operations are included in the timing result: */
 		ResetCancelConn();
@@ -1432,7 +1584,6 @@ sendquery_cleanup:
 	return OK;
 }
 
-
 /*
  * ExecQueryUsingCursor: run a SELECT-like query using a cursor
  *
diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h
index bdcb58f..4f46b9c 100644
--- a/src/bin/psql/common.h
+++ b/src/bin/psql/common.h
@@ -38,6 +38,8 @@ extern int	PSQLexecWatch(const char *query, const printQueryOpt *opt);
 
 extern bool SendQuery(const char *query);
 
+void psql_reset_query_params(void);
+
 extern bool is_superuser(void);
 extern bool standard_strings(void);
 extern const char *session_username(void);
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 37dfa4d..23fd6d3 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -23,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -403,6 +402,9 @@ MainLoop(FILE *source)
 		psql_scan_finish(scan_state);
 		free(line);
 
+		/* reset binary parameters */
+		psql_reset_query_params();
+
 		if (slashCmdStatus == PSQL_CMD_TERMINATE)
 		{
 			successResult = EXIT_SUCCESS;
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 8cfe9d2..2874704 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -77,6 +77,8 @@ enum trivalue
 	TRI_YES
 };
 
+#define MAX_BINARY_PARAMS		32
+
 typedef struct _psqlSettings
 {
 	PGconn	   *db;				/* connection to backend */
@@ -135,6 +137,9 @@ typedef struct _psqlSettings
 	const char *prompt3;
 	PGVerbosity verbosity;		/* current error verbosity level */
 	PGContextVisibility show_context;	/* current context display level */
+	int		nparams;
+	Oid		paramTypes[MAX_BINARY_PARAMS];
+	char	*params[MAX_BINARY_PARAMS];
 } PsqlSettings;
 
 extern PsqlSettings pset;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 7ce05fb..05715a6 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -107,6 +107,7 @@ main(int argc, char *argv[])
 	char		password[100];
 	char	   *password_prompt = NULL;
 	bool		new_pass;
+	int			i;
 
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("psql"));
 
@@ -140,6 +141,10 @@ main(int argc, char *argv[])
 	pset.cur_cmd_source = stdin;
 	pset.cur_cmd_interactive = false;
 
+	pset.nparams = 0;
+	for (i = 0; i < MAX_BINARY_PARAMS; i++)
+		pset.params[i] = NULL;
+
 	/* We rely on unmentioned fields of pset.popt to start out 0/false/NULL */
 	pset.popt.topt.format = PRINT_ALIGNED;
 	pset.popt.topt.border = 1;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..ff4bf73 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1294,7 +1294,7 @@ psql_completion(const char *text, int start, int end)
 		"\\f", "\\g", "\\gexec", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
 		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
-		"\\s", "\\set", "\\setenv", "\\sf", "\\sv", "\\t", "\\T",
+		"\\s", "\\set", "\\setenv", "\\setfileref", "\\sf", "\\sv", "\\t", "\\T",
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
@@ -3128,6 +3128,12 @@ psql_completion(const char *text, int start, int end)
 		matches = completion_matches(text, complete_from_files);
 	}
 
+	else if (Matches2("\\setfileref", MatchAny))
+	{
+		completion_charp = "\\";
+		matches = completion_matches(text, complete_from_files);
+	}
+
 	/*
 	 * Finally, we look through the list of "things", such as TABLE, INDEX and
 	 * check if that was the previous word. If so, execute the query to get a
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index f43f418..74b3f7b 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -71,6 +71,31 @@ GetVariable(VariableSpace space, const char *name)
 		if (strcmp(current->name, name) == 0)
 		{
 			/* this is correct answer when value is NULL, too */
+
+			return current->value;
+		}
+	}
+
+	return NULL;
+}
+
+const char *
+GetVariableOrFileRef(VariableSpace space, const char *name, bool *is_file_ref)
+{
+	struct _variable *current;
+
+	if (!space)
+		return NULL;
+
+	for (current = space->next; current; current = current->next)
+	{
+		if (strcmp(current->name, name) == 0)
+		{
+			/* this is correct answer when value is NULL, too */
+
+			if (is_file_ref)
+				*is_file_ref = current->is_file_ref;
+
 			return current->value;
 		}
 	}
@@ -178,7 +203,7 @@ PrintVariables(VariableSpace space)
 	for (ptr = space->next; ptr; ptr = ptr->next)
 	{
 		if (ptr->value)
-			printf("%s = '%s'\n", ptr->name, ptr->value);
+			printf("%s = %s'%s'\n", ptr->name, ptr->is_file_ref ? "^" : "", ptr->value);
 		if (cancel_pressed)
 			break;
 	}
@@ -218,6 +243,64 @@ SetVariable(VariableSpace space, const char *name, const char *value)
 	/* not present, make new entry */
 	current = pg_malloc(sizeof *current);
 	current->name = pg_strdup(name);
+	current->is_file_ref = false;
+	current->value = pg_strdup(value);
+	current->assign_hook = NULL;
+	current->next = NULL;
+	previous->next = current;
+	return true;
+}
+
+void
+PrintSetFileRefVariables(VariableSpace space)
+{
+	struct _variable *ptr;
+
+	if (!space)
+		return;
+
+	for (ptr = space->next; ptr; ptr = ptr->next)
+	{
+		if (ptr->is_file_ref && ptr->value)
+			printf("%s = '%s'\n", ptr->name, ptr->value);
+		if (cancel_pressed)
+			break;
+	}
+}
+
+bool
+SetFileRef(VariableSpace space, const char *name, const char *value)
+{
+	struct _variable *current,
+			   *previous;
+
+	if (!space)
+		return false;
+
+	if (!valid_variable_name(name))
+		return false;
+
+	if (!value)
+		return DeleteVariable(space, name);
+
+	for (previous = space, current = space->next;
+		 current;
+		 previous = current, current = current->next)
+	{
+		if (strcmp(current->name, name) == 0)
+		{
+			/* found entry, so update */
+			if (current->value)
+				free(current->value);
+			current->value = pg_strdup(value);
+			return true;
+		}
+	}
+
+	/* not present, make new entry */
+	current = pg_malloc(sizeof *current);
+	current->is_file_ref = true;
+	current->name = pg_strdup(name);
 	current->value = pg_strdup(value);
 	current->assign_hook = NULL;
 	current->next = NULL;
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index d7a05a1..bad4e37 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -26,6 +26,7 @@ struct _variable
 {
 	char	   *name;
 	char	   *value;
+	bool		is_file_ref;
 	VariableAssignHook assign_hook;
 	struct _variable *next;
 };
@@ -34,6 +35,7 @@ typedef struct _variable *VariableSpace;
 
 VariableSpace CreateVariableSpace(void);
 const char *GetVariable(VariableSpace space, const char *name);
+const char *GetVariableOrFileRef(VariableSpace space, const char *name, bool *is_file_ref);
 
 bool		ParseVariableBool(const char *value, const char *name);
 int ParseVariableNum(const char *val,
@@ -47,8 +49,11 @@ int GetVariableNum(VariableSpace space,
 			   bool allowtrail);
 
 void		PrintVariables(VariableSpace space);
+void		PrintSetFileRefVariables(VariableSpace space);
+
 
 bool		SetVariable(VariableSpace space, const char *name, const char *value);
+bool		SetFileRef(VariableSpace space, const char *name, const char *value);
 bool		SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook hook);
 bool		SetVariableBool(VariableSpace space, const char *name);
 bool		DeleteVariable(VariableSpace space, const char *name);
diff --git a/src/test/regress/input/misc.source b/src/test/regress/input/misc.source
index dd2d1b2..1ca29fb 100644
--- a/src/test/regress/input/misc.source
+++ b/src/test/regress/input/misc.source
@@ -263,6 +263,29 @@ select i, length(t), octet_length(t), oldstyle_length(i,t) from oldstyle_test;
 drop table oldstyle_test;
 
 --
+-- psql refvariables
+--
+
+CREATE TABLE test_setref(a text, b bytea);
+
+-- use two different ways for import data - result should be same
+\lo_import @abs_builddir@/data/hash.data
+\set lo_oid :LASTOID
+INSERT INTO test_setref (b) VALUES(lo_get(:lo_oid));
+\lo_unlink :lo_oid
+SELECT md5(b) FROM test_setref;
+TRUNCATE test_setref;
+
+\setfileref testdata @abs_builddir@/data/hash.data
+INSERT INTO test_setref VALUES(convert_from(:testdata, current_setting('server_encoding')), :testdata);
+SELECT md5(a), md5(b) FROM test_setref;
+TRUNCATE test_setref;
+INSERT INTO test_setref(a) VALUES(:'testdata');
+SELECT md5(a) FROM test_setref;
+
+DROP TABLE test_setref;
+
+--
 -- functional joins
 --
 
diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source
index 574ef0d..a26b461 100644
--- a/src/test/regress/output/misc.source
+++ b/src/test/regress/output/misc.source
@@ -700,6 +700,39 @@ select i, length(t), octet_length(t), oldstyle_length(i,t) from oldstyle_test;
 
 drop table oldstyle_test;
 --
+-- psql refvariables
+--
+CREATE TABLE test_setref(a text, b bytea);
+-- use two different ways for import data - result should be same
+\lo_import @abs_builddir@/data/hash.data
+\set lo_oid :LASTOID
+INSERT INTO test_setref (b) VALUES(lo_get(:lo_oid));
+\lo_unlink :lo_oid
+SELECT md5(b) FROM test_setref;
+               md5                
+----------------------------------
+ e446fe6ea5a347e69670633412c7f8cb
+(1 row)
+
+TRUNCATE test_setref;
+\setfileref testdata @abs_builddir@/data/hash.data
+INSERT INTO test_setref VALUES(convert_from(:testdata, current_setting('server_encoding')), :testdata);
+SELECT md5(a), md5(b) FROM test_setref;
+               md5                |               md5                
+----------------------------------+----------------------------------
+ e446fe6ea5a347e69670633412c7f8cb | e446fe6ea5a347e69670633412c7f8cb
+(1 row)
+
+TRUNCATE test_setref;
+INSERT INTO test_setref(a) VALUES(:'testdata');
+SELECT md5(a) FROM test_setref;
+               md5                
+----------------------------------
+ e446fe6ea5a347e69670633412c7f8cb
+(1 row)
+
+DROP TABLE test_setref;
+--
 -- functional joins
 --
 --
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to