From 08729b4e8aa3a27bf9b43a2dae6b962405f63222 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Mar 2010 14:44:51 +0000
Subject: [PATCH] Prevent ALTER USER f RESET ALL from removing the settings
 that were put there by a superuser -- "ALTER USER f RESET setting" already
 disallows removing such a setting.

Apply the same treatment to ALTER DATABASE d RESET ALL when run by a database
owner that's not superuser.
---
 src/backend/commands/dbcommands.c | 29 +++++++++--
 src/backend/commands/user.c       | 29 +++++++++--
 src/backend/utils/misc/guc.c      | 83 ++++++++++++++++++++++++++++++-
 src/include/utils/guc.h           |  3 +-
 4 files changed, 134 insertions(+), 10 deletions(-)

diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 1ea84fdbb2..cbc485f928 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.225 2009/06/11 14:48:55 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.225.2.1 2010/03/25 14:44:51 alvherre Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1454,9 +1454,30 @@ AlterDatabaseSet(AlterDatabaseSetStmt *stmt)
 
 	if (stmt->setstmt->kind == VAR_RESET_ALL)
 	{
-		/* RESET ALL, so just set datconfig to null */
-		repl_null[Anum_pg_database_datconfig - 1] = true;
-		repl_val[Anum_pg_database_datconfig - 1] = (Datum) 0;
+		ArrayType  *new = NULL;
+		Datum		datum;
+		bool		isnull;
+
+		/*
+		 * in RESET ALL, request GUC to reset the settings array; if none
+		 * left, we can set datconfig to null; otherwise use the returned
+		 * array
+		 */
+		datum = heap_getattr(tuple, Anum_pg_database_datconfig,
+							 RelationGetDescr(rel), &isnull);
+		if (!isnull)
+			new = GUCArrayReset(DatumGetArrayTypeP(datum));
+		if (new)
+		{
+			repl_val[Anum_pg_database_datconfig - 1] = PointerGetDatum(new);
+			repl_repl[Anum_pg_database_datconfig - 1] = true;
+			repl_null[Anum_pg_database_datconfig - 1] = false;
+		}
+		else
+		{
+			repl_null[Anum_pg_database_datconfig - 1] = true;
+			repl_val[Anum_pg_database_datconfig - 1] = (Datum) 0;
+		}
 	}
 	else
 	{
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 6796a1f5e2..cabbb232a6 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.187 2009/06/11 14:48:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.187.2.1 2010/03/25 14:44:51 alvherre Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -772,9 +772,30 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
 
 	if (stmt->setstmt->kind == VAR_RESET_ALL)
 	{
-		/* RESET ALL, so just set rolconfig to null */
-		repl_null[Anum_pg_authid_rolconfig - 1] = true;
-		repl_val[Anum_pg_authid_rolconfig - 1] = (Datum) 0;
+		ArrayType  *new = NULL;
+		Datum		datum;
+		bool		isnull;
+
+		/*
+		 * in RESET ALL, request GUC to reset the settings array; if none
+		 * left, we can set rolconfig to null; otherwise use the returned
+		 * array
+		 */
+		datum = SysCacheGetAttr(AUTHNAME, oldtuple,
+								Anum_pg_authid_rolconfig, &isnull);
+		if (!isnull)
+			new = GUCArrayReset(DatumGetArrayTypeP(datum));
+		if (new)
+		{
+			repl_val[Anum_pg_authid_rolconfig - 1] = PointerGetDatum(new);
+			repl_repl[Anum_pg_authid_rolconfig - 1] = true;
+			repl_null[Anum_pg_authid_rolconfig - 1] = false;
+		}
+		else
+		{
+			repl_null[Anum_pg_authid_rolconfig - 1] = true;
+			repl_val[Anum_pg_authid_rolconfig - 1] = (Datum) 0;
+		}
 	}
 	else
 	{
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d841dd7f34..87d2a7a11e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.505.2.5 2010/02/25 13:26:26 mha Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.505.2.6 2010/03/25 14:44:51 alvherre Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -7042,6 +7042,7 @@ ProcessGUCArray(ArrayType *array,
 		free(name);
 		if (value)
 			free(value);
+		pfree(s);
 	}
 }
 
@@ -7176,6 +7177,7 @@ GUCArrayDelete(ArrayType *array, const char *name)
 			&& val[strlen(name)] == '=')
 			continue;
 
+
 		/* else add it to the output array */
 		if (newarray)
 		{
@@ -7198,6 +7200,85 @@ GUCArrayDelete(ArrayType *array, const char *name)
 	return newarray;
 }
 
+/*
+ * Given a GUC array, delete all settings from it that our permission
+ * level allows: if superuser, delete them all; if regular user, only
+ * those that are PGC_USERSET
+ */
+ArrayType *
+GUCArrayReset(ArrayType *array)
+{
+	ArrayType  *newarray;
+	int			i;
+	int			index;
+
+	/* if array is currently null, nothing to do */
+	if (!array)
+		return NULL;
+
+	/* if we're superuser, we can delete everything */
+	if (superuser())
+		return NULL;
+
+	newarray = NULL;
+	index = 1;
+
+	for (i = 1; i <= ARR_DIMS(array)[0]; i++)
+	{
+		Datum		d;
+		char	   *val;
+		char	   *eqsgn;
+		bool		isnull;
+		struct config_generic *gconf;
+
+		d = array_ref(array, 1, &i,
+					  -1 /* varlenarray */ ,
+					  -1 /* TEXT's typlen */ ,
+					  false /* TEXT's typbyval */ ,
+					  'i' /* TEXT's typalign */ ,
+					  &isnull);
+
+		if (isnull)
+			continue;
+		val = TextDatumGetCString(d);
+
+		eqsgn = strchr(val, '=');
+		*eqsgn = '\0';
+
+		gconf = find_option(val, false, WARNING);
+		if (!gconf)
+			continue;
+
+		/* note: superuser-ness was already checked above */
+		/* skip entry if OK to delete */
+		if (gconf->context == PGC_USERSET)
+			continue;
+
+		/* XXX do we need to worry about database owner? */
+
+		/* else add it to the output array */
+		if (newarray)
+		{
+			newarray = array_set(newarray, 1, &index,
+								 d,
+								 false,
+								 -1 /* varlenarray */ ,
+								 -1 /* TEXT's typlen */ ,
+								 false /* TEXT's typbyval */ ,
+								 'i' /* TEXT's typalign */ );
+		}
+		else
+			newarray = construct_array(&d, 1,
+									   TEXTOID,
+									   -1, false, 'i');
+
+		index++;
+		pfree(val);
+	}
+
+	return newarray;
+}
+
 
 /*
  * assign_hook and show_hook subroutines
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 4b3dc315c6..e12591593a 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -7,7 +7,7 @@
  * Copyright (c) 2000-2009, PostgreSQL Global Development Group
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
- * $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.102.2.2 2009/12/09 21:58:04 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.102.2.3 2010/03/25 14:44:51 alvherre Exp $
  *--------------------------------------------------------------------
  */
 #ifndef GUC_H
@@ -280,6 +280,7 @@ extern void ProcessGUCArray(ArrayType *array,
 				GucContext context, GucSource source, GucAction action);
 extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, const char *value);
 extern ArrayType *GUCArrayDelete(ArrayType *array, const char *name);
+extern ArrayType *GUCArrayReset(ArrayType *array);
 
 extern int	GUC_complaint_elevel(GucSource source);