From d19217ea67fbe27a4c5f5d693f0e09ddc83aa69a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 7 Apr 2017 10:39:27 +0900
Subject: [PATCH 3/4] Extend PQencryptPassword with a hashing method

This extra argument can use the following values when hashing the
password:
- scram, for SCRAM-SHA-256 hashing.
- md5, for MD5 hashing.
- plain, for cleartext.
---
 doc/src/sgml/libpq.sgml         |  6 ++++-
 src/bin/psql/command.c          |  2 +-
 src/bin/scripts/createuser.c    |  3 ++-
 src/interfaces/libpq/fe-auth.c  | 54 ++++++++++++++++++++++++++++++++---------
 src/interfaces/libpq/libpq-fe.h |  3 ++-
 5 files changed, 52 insertions(+), 16 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 4bc5bf3192..fc1aa4b5e5 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -5887,7 +5887,8 @@ void PQconninfoFree(PQconninfoOption *connOptions);
      <para>
       Prepares the encrypted form of a <productname>PostgreSQL</> password.
 <synopsis>
-char * PQencryptPassword(const char *passwd, const char *user);
+char * PQencryptPassword(const char *passwd, const char *user,
+                         const char *method);
 </synopsis>
       This function is intended to be used by client applications that
       wish to send commands like <literal>ALTER USER joe PASSWORD
@@ -5901,6 +5902,9 @@ char * PQencryptPassword(const char *passwd, const char *user);
       memory.  The caller can assume the string doesn't contain any
       special characters that would require escaping.  Use
       <function>PQfreemem</> to free the result when done with it.
+      The encryption method of the password can be specified as
+      <literal>md5</> for hashing with MD5, <literal>scram</> for
+      hashing with SCRAM-SHA-256 and <literal>plain</> for cleartext.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 859ded71f6..ccea7a5fd0 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1878,7 +1878,7 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
 			else
 				user = PQuser(pset.db);
 
-			encrypted_password = PQencryptPassword(pw1, user);
+			encrypted_password = PQencryptPassword(pw1, user, "md5");
 
 			if (!encrypted_password)
 			{
diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c
index 3d74797a8f..5af263e34a 100644
--- a/src/bin/scripts/createuser.c
+++ b/src/bin/scripts/createuser.c
@@ -275,7 +275,8 @@ main(int argc, char *argv[])
 			char	   *encrypted_password;
 
 			encrypted_password = PQencryptPassword(newpassword,
-												   newuser);
+												   newuser,
+												   "md5");
 			if (!encrypted_password)
 			{
 				fprintf(stderr, _("Password encryption failed.\n"));
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index d81ee4f944..cdd46a216b 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -39,6 +39,7 @@
 #endif
 
 #include "common/md5.h"
+#include "common/scram-common.h"
 #include "libpq-fe.h"
 #include "libpq/scram.h"
 #include "fe-auth.h"
@@ -1087,27 +1088,56 @@ pg_fe_getauthname(PQExpBuffer errorMessage)
  * be dependent on low-level details like whether the encryption is MD5
  * or something else.
  *
- * Arguments are the cleartext password, and the SQL name of the user it
- * is for.
+ * Arguments are the cleartext password, the SQL name of the user it
+ * is for, and the name of password hashing method:
+ * - "scram", to hash password using SCRAM-SHA-256.
+ * - "md5", to hash password using MD5.
+ * - "plain", to get a cleartext value of password.
  *
- * Return value is a malloc'd string, or NULL if out-of-memory.  The client
- * may assume the string doesn't contain any special characters that would
- * require escaping.
+ * Return value is a malloc'd string, or NULL if out-of-memory or in
+ * the event of an error.  The client may assume the string doesn't
+ * contain any special characters that would require escaping.
  */
 char *
-PQencryptPassword(const char *passwd, const char *user)
+PQencryptPassword(const char *passwd, const char *user, const char *method)
 {
 	char	   *crypt_pwd;
 
-	crypt_pwd = malloc(MD5_PASSWD_LEN + 1);
-	if (!crypt_pwd)
-		return NULL;
+	if (strcmp(method, "md5") == 0)
+	{
+		crypt_pwd = malloc(MD5_PASSWD_LEN + 1);
+		if (!crypt_pwd)
+			return NULL;
 
-	if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd))
+		if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd))
+		{
+			free(crypt_pwd);
+			return NULL;
+		}
+	}
+	else if (strcmp(method, "scram") == 0)
 	{
-		free(crypt_pwd);
-		return NULL;
+		char		salt[SCRAM_SALT_LEN];
+
+		crypt_pwd = malloc(SCRAM_VERIFIER_LEN + 1);
+		if (!crypt_pwd)
+			return NULL;
+
+		if (!pg_frontend_random(salt, SCRAM_SALT_LEN))
+			return NULL;
+
+		if (!scram_build_verifier(user, passwd, salt, 0, crypt_pwd))
+		{
+			free(crypt_pwd);
+			return NULL;
+		}
 	}
+	else if (strcmp(method, "plain") == 0)
+	{
+		crypt_pwd = strdup(passwd);
+	}
+	else
+		return NULL;
 
 	return crypt_pwd;
 }
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 635af5b50e..c312dd0152 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -596,7 +596,8 @@ extern int	PQenv2encoding(void);
 
 /* === in fe-auth.c === */
 
-extern char *PQencryptPassword(const char *passwd, const char *user);
+extern char *PQencryptPassword(const char *passwd, const char *user,
+							   const char *method);
 
 /* === in encnames.c === */
 
-- 
2.12.2

