From 488b1d547cbf132bd3980971e91bbf8b8c6e442c Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 3 Mar 2015 20:10:10 +0200
Subject: [PATCH 3/4] Add support for multiple verifiers.

pg_authid.rolpassword field has been replaced with an array.

Add CREATE/ALTER ROLE support for password verifiers.
---
 src/backend/catalog/system_views.sql |   2 +-
 src/backend/commands/user.c          | 195 +++++++++++++++++++++++++----------
 src/backend/libpq/crypt.c            | 117 ++++++++++++++++++---
 src/backend/parser/gram.y            |   8 +-
 src/bin/pg_dump/pg_dumpall.c         |  28 ++++-
 src/include/catalog/pg_authid.h      |   4 +-
 src/include/parser/kwlist.h          |   1 +
 src/test/regress/expected/rules.out  |   2 +-
 8 files changed, 281 insertions(+), 76 deletions(-)

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2800f73..cd78061 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -32,7 +32,7 @@ CREATE VIEW pg_shadow AS
         rolsuper AS usesuper,
         rolreplication AS userepl,
         rolbypassrls AS usebypassrls,
-        rolpassword AS passwd,
+        rolverifiers AS verifiers,
         rolvaliduntil::abstime AS valuntil,
         setconfig AS useconfig
     FROM pg_authid LEFT JOIN pg_db_role_setting s
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 75f1b3c..42e473d 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
 #include "commands/seclabel.h"
@@ -55,6 +56,9 @@ static void DelRoleMems(const char *rolename, Oid roleid,
 			List *memberSpecs, List *memberIds,
 			bool admin_opt);
 
+static Datum buildPasswordVerifiers(char *rolname, char *password,
+									bool store_encrypted,
+					   char *verifiers, bool *isnull);
 
 /* Check if current user has createrole privileges */
 static bool
@@ -80,7 +84,7 @@ CreateRole(CreateRoleStmt *stmt)
 	ListCell   *option;
 	char	   *password = NULL;	/* user password */
 	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	char	   *passwordVerifiers = NULL;
 	bool		issuper = false;	/* Make the user a superuser? */
 	bool		inherit = true; /* Auto inherit privileges? */
 	bool		createrole = false;		/* Can this user create roles? */
@@ -96,6 +100,7 @@ CreateRole(CreateRoleStmt *stmt)
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
 	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordverifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -141,6 +146,14 @@ CreateRole(CreateRoleStmt *stmt)
 			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
 				encrypt_password = false;
 		}
+		else if (strcmp(defel->defname, "passwordVerifiers") == 0)
+		{
+			if (dpassword || dpasswordverifiers)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dpasswordverifiers = defel;
+		}
 		else if (strcmp(defel->defname, "sysid") == 0)
 		{
 			ereport(NOTICE,
@@ -249,6 +262,8 @@ CreateRole(CreateRoleStmt *stmt)
 
 	if (dpassword && dpassword->arg)
 		password = strVal(dpassword->arg);
+	if (dpasswordverifiers && dpasswordverifiers->arg)
+		passwordVerifiers = strVal(dpasswordverifiers->arg);
 	if (dissuper)
 		issuper = intVal(dissuper->arg) != 0;
 	if (dinherit)
@@ -346,7 +361,10 @@ CreateRole(CreateRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Call the password checking hook if there is one defined. Pass the
+	 * plaintext password to it if we have it, otherwise just the MD5 hash.
+	 *
+	 * XXX: what to do with other verifiers? Need to change the hook API...
 	 */
 	if (check_password_hook && password)
 		(*check_password_hook) (stmt->role,
@@ -371,24 +389,10 @@ CreateRole(CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
 	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
-	if (password)
-	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-	}
-	else
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
+	new_record[Anum_pg_authid_rolverifiers - 1] =
+		buildPasswordVerifiers(stmt->role, password, encrypt_password,
+							   passwordVerifiers,
+							   &new_record_nulls[Anum_pg_authid_rolverifiers - 1]);
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
@@ -487,7 +491,7 @@ AlterRole(AlterRoleStmt *stmt)
 	char	   *rolename = NULL;
 	char	   *password = NULL;	/* user password */
 	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	char	   *passwordVerifiers = NULL;
 	int			issuper = -1;	/* Make the user a superuser? */
 	int			inherit = -1;	/* Auto inherit privileges? */
 	int			createrole = -1;	/* Can this user create roles? */
@@ -501,6 +505,7 @@ AlterRole(AlterRoleStmt *stmt)
 	bool		validUntil_null;
 	bool		bypassrls = -1;
 	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordverifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -519,10 +524,10 @@ AlterRole(AlterRoleStmt *stmt)
 		DefElem    *defel = (DefElem *) lfirst(option);
 
 		if (strcmp(defel->defname, "password") == 0 ||
-			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+				 strcmp(defel->defname, "encryptedPassword") == 0 ||
+				 strcmp(defel->defname, "unencryptedPassword") == 0)
 		{
-			if (dpassword)
+			if (dpassword || dpasswordverifiers)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
@@ -532,6 +537,14 @@ AlterRole(AlterRoleStmt *stmt)
 			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
 				encrypt_password = false;
 		}
+		else if (strcmp(defel->defname, "passwordVerifiers") == 0)
+		{
+			if (dpassword || dpasswordverifiers)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dpasswordverifiers = defel;
+		}
 		else if (strcmp(defel->defname, "superuser") == 0)
 		{
 			if (dissuper)
@@ -620,6 +633,8 @@ AlterRole(AlterRoleStmt *stmt)
 
 	if (dpassword && dpassword->arg)
 		password = strVal(dpassword->arg);
+	if (dpasswordverifiers && dpasswordverifiers->arg)
+		passwordVerifiers = strVal(dpasswordverifiers->arg);
 	if (dissuper)
 		issuper = intVal(dissuper->arg);
 	if (dinherit)
@@ -718,7 +733,10 @@ AlterRole(AlterRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Call the password checking hook if there is one defined. Pass the
+	 * plaintext password to it if we have it, otherwise just the MD5 hash.
+	 *
+	 * XXX: what to do with other verifiers? Need to change the hook API...
 	 */
 	if (check_password_hook && password)
 		(*check_password_hook)(rolename ,
@@ -779,28 +797,14 @@ AlterRole(AlterRoleStmt *stmt)
 		new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
 	}
 
-	/* password */
-	if (password)
+	/* set or unset password */
+	if (dpassword)
 	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, rolename, strlen(rolename),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-	}
-
-	/* unset password */
-	if (dpassword && dpassword->arg == NULL)
-	{
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+		new_record_repl[Anum_pg_authid_rolverifiers - 1] = true;
+		new_record[Anum_pg_authid_rolverifiers - 1] =
+			buildPasswordVerifiers(rolename, password, encrypt_password,
+								   passwordVerifiers,
+								   &new_record_nulls[Anum_pg_authid_rolverifiers - 1]);
 	}
 
 	/* valid until */
@@ -1192,16 +1196,43 @@ RenameRole(const char *oldname, const char *newname)
 												   CStringGetDatum(newname));
 	repl_null[Anum_pg_authid_rolname - 1] = false;
 
-	datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
+	datum = heap_getattr(oldtuple, Anum_pg_authid_rolverifiers, dsc, &isnull);
 
-	if (!isnull && isMD5(TextDatumGetCString(datum)))
+	if (!isnull)
 	{
-		/* MD5 uses the username as salt, so just clear it on a rename */
-		repl_repl[Anum_pg_authid_rolpassword - 1] = true;
-		repl_null[Anum_pg_authid_rolpassword - 1] = true;
+		Datum	   *verifiers;
+		int			nverifiers;
+		bool		modified = false;
 
-		ereport(NOTICE,
-				(errmsg("MD5 password cleared because of role rename")));
+		deconstruct_array(DatumGetArrayTypeP(datum), TEXTOID, -1, false, 'i',
+						  &verifiers, NULL, &nverifiers);
+		for (i = 0; i < nverifiers;)
+		{
+			char *verifier = TextDatumGetCString(verifiers[i]);
+			if (strncmp(verifier, "md5:", 4) == 0)
+			{
+				/* MD5 uses the username as salt, so clear it on a rename */
+				nverifiers--;
+				memmove(&verifiers[i], &verifiers[i + 1], nverifiers - i);
+				modified = true;
+				ereport(NOTICE,
+						(errmsg("MD5 password cleared because of role rename")));
+			}
+			else
+				i++;
+		}
+		if (modified)
+		{
+			repl_repl[Anum_pg_authid_rolverifiers - 1] = true;
+			if (nverifiers == 0)
+				repl_null[Anum_pg_authid_rolverifiers - 1] = true;
+			else
+			{
+				repl_val[Anum_pg_authid_rolverifiers - 1] =
+					PointerGetDatum(construct_array(verifiers, nverifiers, TEXTOID, -1, false, 'i'));
+				repl_null[Anum_pg_authid_rolverifiers - 1] = false;
+			}
+		}
 	}
 
 	newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
@@ -1624,3 +1655,61 @@ DelRoleMems(const char *rolename, Oid roleid,
 	 */
 	heap_close(pg_authmem_rel, NoLock);
 }
+
+static Datum
+buildPasswordVerifiers(char *rolname, char *password, bool store_encrypted,
+					   char *verifiers, bool *isnull)
+{
+	Datum		datums[2];
+	int			nverifiers = 0;
+	Datum		result;
+	char	   *verifier = NULL;
+
+	/* If a verifier is given, use it as is */
+	if (verifiers)
+	{
+		result = OidInputFunctionCall(F_ARRAY_IN, verifiers, TEXTOID, -1);
+		*isnull = false;
+		return result;
+	}
+
+	/* If ENCRYPTED or UNENCRYPTED PASSWORD is given, store it */
+	if (password)
+	{
+		/*
+		 * Check if the password is actually already an MD5 hash.
+		 */
+		if (isMD5(password))
+		{
+			verifier = psprintf("md5:%s", password);
+			datums[nverifiers++] = CStringGetTextDatum(verifier);
+		}
+		else if (!store_encrypted)
+		{
+			verifier = psprintf("password:%s", password);
+			datums[nverifiers++] = CStringGetTextDatum(verifier);
+		}
+		else
+		{
+			char		encrypted_password[MD5_PASSWD_LEN + 1];
+
+			if (!pg_md5_encrypt(password, rolname, strlen(rolname),
+								encrypted_password))
+				elog(ERROR, "password encryption failed");
+			verifier = psprintf("md5:%s", encrypted_password);
+			datums[nverifiers++] = CStringGetTextDatum(verifier);
+		}
+	}
+
+	if (nverifiers == 0)
+	{
+		result = (Datum) 0;
+		*isnull = true;
+	}
+	else
+	{
+		result = PointerGetDatum(construct_array(datums, nverifiers, TEXTOID, -1, false, 'i'));
+		*isnull = false;
+	}
+	return result;
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 97be944..4c94d63 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -21,13 +21,93 @@
 #endif
 
 #include "catalog/pg_authid.h"
+#include "catalog/pg_type.h"
 #include "libpq/crypt.h"
 #include "libpq/md5.h"
 #include "miscadmin.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+/*
+ * Get verifier stored in pg_roleid tuple, for given authentication method.
+ */
+static char *
+get_role_verifier_from_tup(HeapTuple roleTup, const char *method)
+{
+	Datum		datum;
+	bool		isnull;
+	Datum	   *elems;
+	int			nelems;
+	char	   *verifier = NULL;
+	int			i;
+
+	datum = SysCacheGetAttr(AUTHNAME, roleTup,
+							Anum_pg_authid_rolverifiers, &isnull);
+	if (isnull)
+		return NULL;
+
+	/* Parse the stored value */
+	deconstruct_array(DatumGetArrayTypeP(datum), TEXTOID, -1, false, 'i',
+					  &elems, NULL, &nelems);
+	for (i = 0; i < nelems; i ++)
+	{
+		char		*s = TextDatumGetCString(elems[i]);
+		char	    *colon;
+
+		colon = strchr(s, ':');
+		if (colon == NULL)
+			continue; /* shouldn't happen, invalid verifier */
+
+		*colon = '\0';
+		verifier = colon + 1;
+
+		if (strcmp(s, method) == 0)
+		{
+			verifier = pstrdup(colon + 1);
+			pfree(s);
+			break;
+		}
+		pfree(s);
+	}
+
+	return verifier;
+}
+
+char *
+get_role_verifier(const char *role, const char *method, char **logdetail)
+{
+	char	   *verifier;
+	HeapTuple	roleTup;
+	Datum		datum;
+	bool		isnull;
+
+	/* Get role info from pg_authid */
+	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
+	if (!HeapTupleIsValid(roleTup))
+		return NULL;	/* no such user */
+
+	datum = SysCacheGetAttr(AUTHNAME, roleTup,
+							Anum_pg_authid_rolvaliduntil, &isnull);
+	if (!isnull)
+	{
+		TimestampTz vuntil = DatumGetTimestampTz(datum);
+		if (vuntil < GetCurrentTimestamp())
+		{
+			*logdetail = psprintf(_("User \"%s\" has an expired password."),
+								  role);
+			ReleaseSysCache(roleTup);
+			return NULL;
+		}
+	}
+
+	verifier = get_role_verifier_from_tup(roleTup, method);
+
+	ReleaseSysCache(roleTup);
+
+	return verifier;
+}
 
 /*
  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,9 +119,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				 char **logdetail)
 {
 	int			retval = STATUS_ERROR;
-	char	   *shadow_pass,
+	char	   *verifier,
 			   *crypt_pwd;
 	TimestampTz vuntil = 0;
+	bool		verifier_is_md5;
 	char	   *crypt_client_pass = client_pass;
 	HeapTuple	roleTup;
 	Datum		datum;
@@ -52,16 +133,22 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 	if (!HeapTupleIsValid(roleTup))
 		return STATUS_ERROR;	/* no such user */
 
-	datum = SysCacheGetAttr(AUTHNAME, roleTup,
-							Anum_pg_authid_rolpassword, &isnull);
-	if (isnull)
+	verifier_is_md5 = true;
+	verifier = get_role_verifier_from_tup(roleTup, "md5");
+	if (verifier == NULL)
 	{
-		ReleaseSysCache(roleTup);
-		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
-							  role);
-		return STATUS_ERROR;	/* user has no password */
+		/* we can also use a plaintext password, by creating the hash from it */
+		verifier_is_md5 = false;
+		verifier = get_role_verifier_from_tup(roleTup, "password");
+
+		if (verifier == NULL)
+		{
+			*logdetail = psprintf(_("User \"%s\" has no password assigned for authentication method \"%s\"."),
+								  role, "md5");
+			ReleaseSysCache(roleTup);
+			return STATUS_ERROR;
+		}
 	}
-	shadow_pass = TextDatumGetCString(datum);
 
 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
 							Anum_pg_authid_rolvaliduntil, &isnull);
@@ -70,7 +157,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 
 	ReleaseSysCache(roleTup);
 
-	if (*shadow_pass == '\0')
+	if (*verifier == '\0')
 		return STATUS_ERROR;	/* empty password */
 
 	CHECK_FOR_INTERRUPTS();
@@ -83,10 +170,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 	{
 		case uaMD5:
 			crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* stored password already encrypted, only do salt */
-				if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+				if (!pg_md5_encrypt(verifier + strlen("md5"),
 									port->md5Salt,
 									sizeof(port->md5Salt), crypt_pwd))
 				{
@@ -99,7 +186,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				/* stored password is plain, double-encrypt */
 				char	   *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
 
-				if (!pg_md5_encrypt(shadow_pass,
+				if (!pg_md5_encrypt(verifier,
 									port->user_name,
 									strlen(port->user_name),
 									crypt_pwd2))
@@ -121,7 +208,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 			}
 			break;
 		default:
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* Encrypt user-supplied password to match stored MD5 */
 				crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
@@ -134,7 +221,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 					return STATUS_ERROR;
 				}
 			}
-			crypt_pwd = shadow_pass;
+			crypt_pwd = verifier;
 			break;
 	}
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3aa9e42..d68e61d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -623,7 +623,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERIFIERS VERSION_P VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -916,6 +916,11 @@ AlterOptRoleElem:
 					$$ = makeDefElem("unencryptedPassword",
 									 (Node *)makeString($3));
 				}
+			| PASSWORD VERIFIERS Sconst
+				{
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)makeString($3));
+				}
 			| INHERIT
 				{
 					$$ = makeDefElem("inherit", (Node *)makeInteger(TRUE));
@@ -13504,6 +13509,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARYING
+			| VERIFIERS
 			| VERSION_P
 			| VIEW
 			| VIEWS
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 6a7a641..32d710c 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -660,7 +660,8 @@ dumpRoles(PGconn *conn)
 				i_rolcreatedb,
 				i_rolcanlogin,
 				i_rolconnlimit,
-				i_rolpassword,
+				i_rolverifiers = -1,
+				i_rolpassword = -1,
 				i_rolvaliduntil,
 				i_rolreplication,
 				i_rolbypassrls,
@@ -669,10 +670,21 @@ dumpRoles(PGconn *conn)
 	int			i;
 
 	/* note: rolconfig is dumped later */
+	/* FIXME: bump this when we branch off */
 	if (server_version >= 90500)
 		printfPQExpBuffer(buf,
 						  "SELECT oid, rolname, rolsuper, rolinherit, "
 						  "rolcreaterole, rolcreatedb, "
+						  "rolcanlogin, rolconnlimit, rolverifiers, "
+						  "rolvaliduntil, rolreplication, rolbypassrls, "
+			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
+						  "rolname = current_user AS is_current_user "
+						  "FROM pg_authid "
+						  "ORDER BY 2");
+	else if (server_version >= 90500)
+		printfPQExpBuffer(buf,
+						  "SELECT oid, rolname, rolsuper, rolinherit, "
+						  "rolcreaterole, rolcreatedb, "
 						  "rolcanlogin, rolconnlimit, rolpassword, "
 						  "rolvaliduntil, rolreplication, rolbypassrls, "
 			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
@@ -757,7 +769,11 @@ dumpRoles(PGconn *conn)
 	i_rolcreatedb = PQfnumber(res, "rolcreatedb");
 	i_rolcanlogin = PQfnumber(res, "rolcanlogin");
 	i_rolconnlimit = PQfnumber(res, "rolconnlimit");
-	i_rolpassword = PQfnumber(res, "rolpassword");
+	/* FIXME: version again */
+	if (server_version >= 90500)
+		i_rolverifiers = PQfnumber(res, "rolverifiers");
+	else
+		i_rolpassword = PQfnumber(res, "rolpassword");
 	i_rolvaliduntil = PQfnumber(res, "rolvaliduntil");
 	i_rolreplication = PQfnumber(res, "rolreplication");
 	i_rolbypassrls = PQfnumber(res, "rolbypassrls");
@@ -837,12 +853,18 @@ dumpRoles(PGconn *conn)
 			appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
 							  PQgetvalue(res, i, i_rolconnlimit));
 
-		if (!PQgetisnull(res, i, i_rolpassword))
+		if (i_rolpassword != -1 && !PQgetisnull(res, i, i_rolpassword))
 		{
 			appendPQExpBufferStr(buf, " PASSWORD ");
 			appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolpassword), conn);
 		}
 
+		if (i_rolverifiers != -1 && !PQgetisnull(res, i, i_rolverifiers))
+		{
+			appendPQExpBufferStr(buf, " PASSWORD VERIFIERS ");
+			appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolverifiers), conn);
+		}
+
 		if (!PQgetisnull(res, i, i_rolvaliduntil))
 			appendPQExpBuffer(buf, " VALID UNTIL '%s'",
 							  PQgetvalue(res, i, i_rolvaliduntil));
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index d5f19d6..498c18b 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,7 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MAC
 
 	/* remaining fields may be null; use heap_getattr to read them! */
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
-	text		rolpassword;	/* password, if any */
+	text		rolverifiers[];	/* password hashes, if any */
 	timestamptz rolvaliduntil;	/* password expiration time, if any */
 #endif
 } FormData_pg_authid;
@@ -85,7 +85,7 @@ typedef FormData_pg_authid *Form_pg_authid;
 #define Anum_pg_authid_rolreplication	7
 #define Anum_pg_authid_rolbypassrls		8
 #define Anum_pg_authid_rolconnlimit		9
-#define Anum_pg_authid_rolpassword		10
+#define Anum_pg_authid_rolverifiers		10
 #define Anum_pg_authid_rolvaliduntil	11
 
 /* ----------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 7c243ec..067112f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -407,6 +407,7 @@ PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("verifiers", VERIFIERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 1788270..052c4b7 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1608,7 +1608,7 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.rolsuper AS usesuper,
     pg_authid.rolreplication AS userepl,
     pg_authid.rolbypassrls AS usebypassrls,
-    pg_authid.rolpassword AS passwd,
+    pg_authid.rolverifiers AS verifiers,
     (pg_authid.rolvaliduntil)::abstime AS valuntil,
     s.setconfig AS useconfig
    FROM (pg_authid
-- 
2.1.4

