From 2873e223b78eafb2f6168154d4f95161343c57c7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 10 Oct 2017 21:49:37 +0900
Subject: [PATCH 3/4] Add connection parameters "saslname" and
 "saslchannelbinding"

Those parameters can be used to respectively enforce the value of the
SASL mechanism name and the channel binding name sent to server during
a SASL message exchange.

A set of tests dedicated to SASL and channel binding is added as well
to the SSL test suite, which is handy to check the validity of a patch.
---
 doc/src/sgml/libpq.sgml           | 24 ++++++++++++++++++++++++
 src/backend/libpq/auth-scram.c    |  1 +
 src/interfaces/libpq/fe-auth.c    |  9 ++++++++-
 src/interfaces/libpq/fe-connect.c | 13 +++++++++++++
 src/interfaces/libpq/libpq-int.h  |  2 ++
 src/test/ssl/t/002_sasl.pl        | 18 +++++++++++++++---
 6 files changed, 63 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 694921f212..432589b815 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1222,6 +1222,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-saslname" xreflabel="saslname">
+      <term><literal>saslname</literal></term>
+      <listitem>
+       <para>
+        Controls the name of the SASL mechanism name sent to server when doing
+        a message exchange for a SASL authentication. The list of SASL
+        mechanisms supported by server are listed in
+        <xref linkend="sasl-authentication">.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="libpq-saslchannelbinding" xreflabel="saslchannelbinding">
+      <term><literal>saslchannelbinding</literal></term>
+      <listitem>
+       <para>
+        Controls the name of the channel binding name sent to server when doing
+        a message exchange for a SASL authentication. The list of channel
+        binding names supported by server are listed in
+        <xref linkend="sasl-authentication">.
+       </para>
+      </listitem>
+     </varlistentry>
+     
      <varlistentry id="libpq-connect-sslmode" xreflabel="sslmode">
       <term><literal>sslmode</literal></term>
       <listitem>
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index b326c8a39e..b90be7f1ae 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -1097,6 +1097,7 @@ read_client_final_message(scram_state *state, char *input)
 	 * client has to provide channel binding value if needed.
 	 */
 	channel_binding = read_attr_value(&p, 'c');
+
 #ifdef USE_SSL
 	if (state->ssl_in_use &&
 		state->channel_binding)
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index fa24c88522..97de9a0a30 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -569,6 +569,13 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 		}
 	}
 
+	/*
+	 * If user has asked for a specific mechanism name, enforce the chosen
+	 * name to it.
+	 */
+	if (conn->saslname && strlen(conn->saslname) > 0)
+		selected_mechanism = conn->saslname;
+
 	if (!selected_mechanism)
 	{
 		printfPQExpBuffer(&conn->errorMessage,
@@ -617,7 +624,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 										conn->ssl_in_use,
 										channel_binding_advertised,
 										selected_mechanism,
-										NULL,	/* default channel binding */
+										conn->saslchannelbinding,
 										tls_finished,
 										tls_finished_len);
 	if (!conn->sasl_state)
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 2c175a2a24..b092fa6d70 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -262,6 +262,15 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"TCP-Keepalives-Count", "", 10, /* strlen(INT32_MAX) == 10 */
 	offsetof(struct pg_conn, keepalives_count)},
 
+	/* Set of options proper to SASL */
+	{"saslname", NULL, NULL, NULL,
+		"SASL-Name", "", 21,	/* maximum name size per IANA == 21 */
+	offsetof(struct pg_conn, saslname)},
+
+	{"saslchannelbinding", NULL, NULL, NULL,
+		"SASL-Channel", "", 22,	/* sizeof("tls-unique-for-telnet") == 22 */
+	offsetof(struct pg_conn, saslchannelbinding)},
+
 	/*
 	 * ssl options are allowed even without client SSL support because the
 	 * client can still handle SSL modes "disable" and "allow". Other
@@ -3469,6 +3478,10 @@ freePGconn(PGconn *conn)
 		free(conn->keepalives_interval);
 	if (conn->keepalives_count)
 		free(conn->keepalives_count);
+	if (conn->saslname)
+		free(conn->saslname);
+	if (conn->saslchannelbinding)
+		free(conn->saslchannelbinding);
 	if (conn->sslmode)
 		free(conn->sslmode);
 	if (conn->sslcert)
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 0eb8b60c95..6d500aa5db 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -349,6 +349,8 @@ struct pg_conn
 										 * retransmits */
 	char	   *keepalives_count;	/* maximum number of TCP keepalive
 									 * retransmits */
+	char	   *saslname;			/* SASL mechanism name */
+	char	   *saslchannelbinding;	/* channel binding used in SASL */
 	char	   *sslmode;		/* SSL mode (require,prefer,allow,disable) */
 	char	   *sslcompression; /* SSL compression (0 or 1) */
 	char	   *sslkey;			/* client key filename */
diff --git a/src/test/ssl/t/002_sasl.pl b/src/test/ssl/t/002_sasl.pl
index d43a970b55..e9226903ca 100644
--- a/src/test/ssl/t/002_sasl.pl
+++ b/src/test/ssl/t/002_sasl.pl
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 1;
+use Test::More tests => 6;
 use ServerSetup;
 use File::Copy;
 
@@ -37,5 +37,17 @@ $common_connstr =
 "user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR";
 
 # Tests with default channel binding and SASL mechanism names.
-# tls-unique is used here with channel binding.
-test_connect_ok($common_connstr, "");
+# tls-unique is used here
+test_connect_ok($common_connstr, "saslname=SCRAM-SHA-256-PLUS");
+test_connect_fails($common_connstr, "saslname=not-exists");
+# Having a client willing to not use channel binding should work, and
+# so should this series.
+test_connect_ok($common_connstr, "saslname=SCRAM-SHA-256");
+test_connect_ok($common_connstr,
+		"saslname=SCRAM-SHA-256 saslchannelbinding=tls-unique");
+
+# Channel bindings
+test_connect_ok($common_connstr,
+		"saslname=SCRAM-SHA-256-PLUS saslchannelbinding=tls-unique");
+test_connect_fails($common_connstr,
+		"saslname=SCRAM-SHA-256-PLUS saslchannelbinding=not-exists");
-- 
2.15.0

