From fa98d5230405a8db734e359e73535a71b2e8e8d7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 18 Mar 2016 21:57:36 +0900
Subject: [PATCH 2/9] Introduce password_protocols

This new superuser GUC parameters specifies a list of supported password
protocols in Postgres backend. This is useful for system maintainers to
prevent the creation of password using a protocol thought as unsafe in
certain deployments.

The current default is 'plain,md5', authorizing the creation of both plain
passwords and MD5-encrypted passwords in the system.
---
 doc/src/sgml/config.sgml                      | 27 ++++++++++++++++
 src/backend/commands/user.c                   | 46 +++++++++++++++++++++++++++
 src/backend/utils/misc/guc.c                  | 29 +++++++++++++----
 src/backend/utils/misc/postgresql.conf.sample |  2 ++
 src/include/commands/user.h                   |  3 +-
 src/test/regress/expected/password.out        | 28 ++++++++++++++++
 src/test/regress/sql/password.sql             | 20 ++++++++++++
 7 files changed, 147 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 80fc479..a11beab 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1190,6 +1190,33 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-password-protocols" xreflabel="password_protocols">
+      <term><varname>password_protocols</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>password_protocols</> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Specifies a comma-separated list of supported password formats by
+        the server. Supported formats are currently <literal>plain</> and
+        <literal>md5</>.
+       </para>
+
+       <para>
+        When a password is specified in <xref linkend="sql-createuser"> or
+        <xref linkend="sql-alterrole">, this parameter determines if the
+        password specified is authorized to be stored or not, returning
+        an error message to caller if it is not.
+       </para>
+
+       <para>
+        The default is <literal>plain,md5</>, meaning that MD5-encrypted
+        passwords and plain passwords are both accepted.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
       <term><varname>krb_server_keyfile</varname> (<type>string</type>)
       <indexterm>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index e8f23e6..95583e6 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1597,6 +1597,8 @@ static void
 FlattenPasswordIdentifiers(List *verifiers, char *rolname)
 {
 	ListCell   *l;
+	char	   *rawstring;
+	List	   *elemlist;
 
 	/*
 	 * This machinery is here for for sanity checks and backward
@@ -1641,6 +1643,50 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
 				Assert(0);	/* should not happen */
 		}
 	}
+
+	/*
+	 * Now that the list of verifiers is built and consistent with the input
+	 * values, check that the list of verifiers specified is actually
+	 * supported by server or not.
+	 */
+	rawstring = pstrdup(password_protocols);
+
+	if (!SplitIdentifierString(rawstring, ',', &elemlist))
+		Assert(false); /* should not happen */
+
+	/*
+	 * This is O(N ^ 2), but the small number of elements in the list of
+	 * protocols supported is not worth complicating this code.
+	 */
+	foreach(l, verifiers)
+	{
+		AuthVerifierSpec   *spec = (AuthVerifierSpec *) lfirst(l);
+		ListCell		   *l2;
+		bool				found_match = false;
+
+		foreach(l2, elemlist)
+		{
+			char       *meth_name = (char *) lfirst(l2);
+
+			if ((strcmp(meth_name, AUTH_VERIFIER_FULL_MD5) == 0 &&
+				 spec->veriftype == AUTH_VERIFIER_MD5) ||
+				(strcmp(meth_name, AUTH_VERIFIER_FULL_PLAIN) == 0 &&
+				 spec->veriftype == AUTH_VERIFIER_PLAIN))
+			{
+				found_match = true;
+				break;
+			}
+		}
+
+		if (!found_match)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("specified password protocol not allowed"),
+					 errdetail("List of authorized protocols is specified by password_protocols.")));
+	}
+
+	pfree(rawstring);
+	list_free(elemlist);
 }
 
 /*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ae880cb..ab2bc3f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -180,8 +180,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
 static bool check_application_name(char **newval, void **extra, GucSource source);
 static void assign_application_name(const char *newval, void *extra);
 static bool check_cluster_name(char **newval, void **extra, GucSource source);
-static bool check_password_encryption(char **newval, void **extra,
-									  GucSource source);
+static bool check_password_methods(char **newval, void **extra,
+								   GucSource source);
 static const char *show_unix_socket_permissions(void);
 static const char *show_log_file_mode(void);
 
@@ -437,6 +437,7 @@ int			temp_file_limit = -1;
 int			num_temp_buffers = 1024;
 
 char	   *Password_encryption;
+char	   *password_protocols;
 
 char	   *cluster_name = "";
 char	   *ConfigFileName;
@@ -3396,7 +3397,21 @@ static struct config_string ConfigureNamesString[] =
 		},
 		&Password_encryption,
 		"md5",
-		check_password_encryption, NULL, NULL
+		check_password_methods, NULL, NULL
+	},
+
+	{
+		{"password_protocols", PGC_SUSET, CONN_AUTH_SECURITY,
+			gettext_noop("List of password protocols supported."),
+			gettext_noop("The list of password protocols specified by this "
+						 "parameter determines what are the authorized methods "
+						 "on the server when running CREATE USER or ALTER "
+						 "USER."),
+			GUC_LIST_INPUT
+		},
+		&password_protocols,
+		"plain,md5",
+		check_password_methods, NULL, NULL
 	},
 
 	{
@@ -10235,7 +10250,7 @@ check_cluster_name(char **newval, void **extra, GucSource source)
 }
 
 static bool
-check_password_encryption(char **newval, void **extra, GucSource source)
+check_password_methods(char **newval, void **extra, GucSource source)
 {
 	char	   *rawstring = pstrdup(*newval);	/* get copy of list string */
 	List	   *elemlist;
@@ -10253,10 +10268,10 @@ check_password_encryption(char **newval, void **extra, GucSource source)
 	/* Check that only supported formats are listed */
 	foreach(l, elemlist)
 	{
-		char	   *encryption_name = (char *) lfirst(l);
+		char	   *method_name = (char *) lfirst(l);
 
-		if (strcmp(encryption_name, AUTH_VERIFIER_FULL_MD5) != 0 &&
-			strcmp(encryption_name, AUTH_VERIFIER_FULL_PLAIN) != 0)
+		if (strcmp(method_name, AUTH_VERIFIER_FULL_MD5) != 0 &&
+			strcmp(method_name, AUTH_VERIFIER_FULL_PLAIN) != 0)
 		{
 			pfree(rawstring);
 			list_free(elemlist);
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index bff25ca..a93cbe7 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -88,6 +88,8 @@
 #ssl_ca_file = ''			# (change requires restart)
 #ssl_crl_file = ''			# (change requires restart)
 #password_encryption = 'md5'
+#password_protocols = 'plain,md5'	# comma-separated list of supported
+					# password protocols.
 #db_user_namespace = off
 #row_security = on
 
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 636e8ac..7a73bc5 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,8 +14,9 @@
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
-/* GUC parameter */
+/* GUC parameters */
 extern char *Password_encryption;
+extern char *password_protocols;
 
 typedef void (*check_password_hook_type) (const char *username,
 								List *passwordVerifiers,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 5e6f04d..7f18799 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -9,6 +9,14 @@ ERROR:  invalid value for parameter "password_encryption": "true"
 SET password_encryption = 'md5'; -- ok
 SET password_encryption = 'plain'; -- ok
 SET password_encryption = 'md5,plain'; -- ok
+-- Tests for GUC password_protocols
+SET password_protocols = 'novalue'; -- error
+ERROR:  invalid value for parameter "password_protocols": "novalue"
+SET password_protocols = true; -- error
+ERROR:  invalid value for parameter "password_protocols": "true"
+SET password_protocols = 'md5'; -- ok
+SET password_protocols = 'plain'; -- ok
+SET password_protocols = 'md5,plain'; -- ok
 -- consistency of password entries
 SET password_encryption = 'plain';
 CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -88,6 +96,26 @@ SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
  role_passwd4 | m         | md5
 (4 rows)
 
+-- entries for password_protocols
+SET password_protocols = 'md5,plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo'); -- ok
+SET password_protocols = 'md5';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ERROR:  specified password protocol not allowed
+DETAIL:  List of authorized protocols is specified by password_protocols.
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR:  specified password protocol not allowed
+DETAIL:  List of authorized protocols is specified by password_protocols.
+SET password_protocols = '';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ERROR:  specified password protocol not allowed
+DETAIL:  List of authorized protocols is specified by password_protocols.
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR:  specified password protocol not allowed
+DETAIL:  List of authorized protocols is specified by password_protocols.
 DROP ROLE role_passwd1;
 DROP ROLE role_passwd2;
 DROP ROLE role_passwd3;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index c6addf9..e973c5e 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -9,6 +9,13 @@ SET password_encryption = 'md5'; -- ok
 SET password_encryption = 'plain'; -- ok
 SET password_encryption = 'md5,plain'; -- ok
 
+-- Tests for GUC password_protocols
+SET password_protocols = 'novalue'; -- error
+SET password_protocols = true; -- error
+SET password_protocols = 'md5'; -- ok
+SET password_protocols = 'plain'; -- ok
+SET password_protocols = 'md5,plain'; -- ok
+
 -- consistency of password entries
 SET password_encryption = 'plain';
 CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -59,6 +66,19 @@ SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
     WHERE a.rolname LIKE 'role_passwd%'
     ORDER BY a.rolname, v.vermethod;
 
+-- entries for password_protocols
+SET password_protocols = 'md5,plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo'); -- ok
+SET password_protocols = 'md5';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+SET password_protocols = '';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+
 DROP ROLE role_passwd1;
 DROP ROLE role_passwd2;
 DROP ROLE role_passwd3;
-- 
2.7.3

