summaryrefslogtreecommitdiff
path: root/src/interfaces
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces')
-rw-r--r--src/interfaces/libpq/.gitignore5
-rw-r--r--src/interfaces/libpq/Makefile20
-rw-r--r--src/interfaces/libpq/fe-auth-scram.c640
-rw-r--r--src/interfaces/libpq/fe-auth.c112
-rw-r--r--src/interfaces/libpq/fe-auth.h8
-rw-r--r--src/interfaces/libpq/fe-connect.c52
-rw-r--r--src/interfaces/libpq/libpq-int.h7
7 files changed, 839 insertions, 5 deletions
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af71766..2224ada7313 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,4 +1,5 @@
/exports.list
+/base64.c
/chklocale.c
/crypt.c
/getaddrinfo.c
@@ -7,8 +8,12 @@
/inet_net_ntop.c
/noblock.c
/open.c
+/pg_strong_random.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha2.c
+/sha2_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4b1e552d167..792232db495 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,7 @@ LIBS := $(LIBS:-lpgport=)
# We can't use Makefile variables here because the MSVC build system scrapes
# OBJS from this file.
-OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
+OBJS= fe-auth.o fe-auth-scram.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
fe-protocol2.o fe-protocol3.o pqexpbuffer.o fe-secure.o \
libpq-events.o
# libpgport C files we always use
@@ -39,13 +39,22 @@ OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
thread.o
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
+
+ifeq ($(enable_strong_random), yes)
+OBJS += pg_strong_random.o
+else
+OBJS += erand48.o
+endif
+
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += base64.o ip.o md5.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha2_openssl.o
+else
+OBJS += sha2.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -93,7 +102,7 @@ backend_src = $(top_srcdir)/src/backend
# For some libpgport modules, this only happens if configure decides
# the module is needed (see filter hack in OBJS, above).
-chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
+chklocale.c crypt.c erand48.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
ip.c md5.c: % : $(top_srcdir)/src/common/%
@@ -102,6 +111,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+base64.c scram-common.c sha2.c sha2_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
distprep: libpq-dist.rc
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
new file mode 100644
index 00000000000..14331f8d618
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,640 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "common/base64.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/* These are needed for getpid(), in the fallback implementation */
+#ifndef HAVE_STRONG_RANDOM
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef enum
+{
+ FE_SCRAM_INIT,
+ FE_SCRAM_NONCE_SENT,
+ FE_SCRAM_PROOF_SENT,
+ FE_SCRAM_FINISHED
+} fe_scram_state_enum;
+
+typedef struct
+{
+ fe_scram_state_enum state;
+
+ /* These are supplied by the user */
+ const char *username;
+ const char *password;
+
+ /* We construct these */
+ char *client_nonce;
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* These come from the server-first message */
+ char *server_first_message;
+ char *salt;
+ int saltlen;
+ int iterations;
+ char *nonce;
+
+ /* These come from the server-final message */
+ char *server_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} fe_scram_state;
+
+static bool read_server_first_message(fe_scram_state *state, char *input,
+ PQExpBuffer errormessage);
+static bool read_server_final_message(fe_scram_state *state, char *input,
+ PQExpBuffer errormessage);
+static char *build_client_first_message(fe_scram_state *state,
+ PQExpBuffer errormessage);
+static char *build_client_final_message(fe_scram_state *state,
+ PQExpBuffer errormessage);
+static bool verify_server_proof(fe_scram_state *state);
+static void calculate_client_proof(fe_scram_state *state,
+ const char *client_final_message_without_proof,
+ uint8 *result);
+static bool pg_frontend_random(char *dst, int len);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+void *
+pg_fe_scram_init(const char *username, const char *password)
+{
+ fe_scram_state *state;
+
+ state = (fe_scram_state *) malloc(sizeof(fe_scram_state));
+ if (!state)
+ return NULL;
+ memset(state, 0, sizeof(fe_scram_state));
+ state->state = FE_SCRAM_INIT;
+ state->username = username;
+ state->password = password;
+
+ return state;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_nonce)
+ free(state->client_nonce);
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->nonce)
+ free(state->nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+void
+pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success, PQExpBuffer errorMessage)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ *done = false;
+ *success = false;
+ *output = NULL;
+ *outputlen = 0;
+
+ /*
+ * Check that the input length agrees with the string length of the input.
+ * We can ignore inputlen after this.
+ */
+ if (state->state != FE_SCRAM_INIT)
+ {
+ if (inputlen == 0)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (empty message)\n"));
+ goto error;
+ }
+ if (inputlen != strlen(input))
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (length mismatch)\n"));
+ goto error;
+ }
+ }
+
+ switch (state->state)
+ {
+ case FE_SCRAM_INIT:
+ /* Begin the SCRAM handshake, by sending client nonce */
+ *output = build_client_first_message(state, errorMessage);
+ if (*output == NULL)
+ goto error;
+
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = FE_SCRAM_NONCE_SENT;
+ break;
+
+ case FE_SCRAM_NONCE_SENT:
+ /* Receive salt and server nonce, send response. */
+ if (!read_server_first_message(state, input, errorMessage))
+ goto error;
+
+ *output = build_client_final_message(state, errorMessage);
+ if (*output == NULL)
+ goto error;
+
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = FE_SCRAM_PROOF_SENT;
+ break;
+
+ case FE_SCRAM_PROOF_SENT:
+ /* Receive server proof */
+ if (!read_server_final_message(state, input, errorMessage))
+ goto error;
+
+ /*
+ * Verify server proof, to make sure we're talking to the genuine
+ * server. XXX: A fake server could simply not require
+ * authentication, though. There is currently no option in libpq
+ * to reject a connection, if SCRAM authentication did not happen.
+ */
+ if (verify_server_proof(state))
+ *success = true;
+ else
+ {
+ *success = false;
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("invalid server proof\n"));
+ }
+ *done = true;
+ state->state = FE_SCRAM_FINISHED;
+ break;
+
+ default:
+ /* shouldn't happen */
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("invalid SCRAM exchange state\n"));
+ goto error;
+ }
+ return;
+
+error:
+ *done = true;
+ *success = false;
+ return;
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ */
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+ char *begin = *input;
+ char *end;
+
+ if (*begin != attr)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (%c expected)\n"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ if (*begin != '=')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (expected = in attr '%c')\n"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+ char raw_nonce[SCRAM_RAW_NONCE_LEN + 1];
+ char *buf;
+ char buflen;
+ int encoded_len;
+
+ /*
+ * Generate a "raw" nonce. This is converted to ASCII-printable form by
+ * base64-encoding it.
+ */
+ if (!pg_frontend_random(raw_nonce, SCRAM_RAW_NONCE_LEN))
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("failed to generate nonce\n"));
+ return NULL;
+ }
+
+ state->client_nonce = malloc(pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1);
+ if (state->client_nonce == NULL)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+ encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN, state->client_nonce);
+ state->client_nonce[encoded_len] = '\0';
+
+ /*
+ * Generate message. The username is left empty as the backend uses the
+ * value provided by the startup packet. Also, as this username is not
+ * prepared with SASLprep, the message parsing would fail if it includes
+ * '=' or ',' characters.
+ */
+ buflen = 8 + strlen(state->client_nonce) + 1;
+ buf = malloc(buflen);
+ if (buf == NULL)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+ snprintf(buf, buflen, "n,,n=,r=%s", state->client_nonce);
+
+ state->client_first_message_bare = strdup(buf + 3);
+ if (!state->client_first_message_bare)
+ {
+ free(buf);
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+
+ return buf;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+static char *
+build_client_final_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+ PQExpBufferData buf;
+ uint8 client_proof[SCRAM_KEY_LEN];
+ char *result;
+
+ initPQExpBuffer(&buf);
+
+ /*
+ * Construct client-final-message-without-proof. We need to remember it
+ * for verifying the server proof in the final step of authentication.
+ */
+ appendPQExpBuffer(&buf, "c=biws,r=%s", state->nonce);
+ if (PQExpBufferDataBroken(buf))
+ goto oom_error;
+
+ state->client_final_message_without_proof = strdup(buf.data);
+ if (state->client_final_message_without_proof == NULL)
+ goto oom_error;
+
+ /* Append proof to it, to form client-final-message. */
+ calculate_client_proof(state,
+ state->client_final_message_without_proof,
+ client_proof);
+
+ appendPQExpBuffer(&buf, ",p=");
+ if (!enlargePQExpBuffer(&buf, pg_b64_enc_len(SCRAM_KEY_LEN)))
+ goto oom_error;
+ buf.len += pg_b64_encode((char *) client_proof,
+ SCRAM_KEY_LEN,
+ buf.data + buf.len);
+ buf.data[buf.len] = '\0';
+
+ result = strdup(buf.data);
+ if (result == NULL)
+ goto oom_error;
+
+ termPQExpBuffer(&buf);
+ return result;
+
+oom_error:
+ termPQExpBuffer(&buf);
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("out of memory\n"));
+ return NULL;
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+static bool
+read_server_first_message(fe_scram_state *state, char *input,
+ PQExpBuffer errormessage)
+{
+ char *iterations_str;
+ char *endptr;
+ char *encoded_salt;
+ char *nonce;
+
+ state->server_first_message = strdup(input);
+ if (state->server_first_message == NULL)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ /* parse the message */
+ nonce = read_attr_value(&input, 'r', errormessage);
+ if (nonce == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+
+ /* Verify immediately that the server used our part of the nonce */
+ if (strncmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("invalid SCRAM response (nonce mismatch)\n"));
+ return false;
+ }
+
+ state->nonce = strdup(nonce);
+ if (state->nonce == NULL)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ encoded_salt = read_attr_value(&input, 's', errormessage);
+ if (encoded_salt == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+ state->salt = malloc(pg_b64_dec_len(strlen(encoded_salt)));
+ if (state->salt == NULL)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("out of memory\n"));
+ return false;
+ }
+ state->saltlen = pg_b64_decode(encoded_salt,
+ strlen(encoded_salt),
+ state->salt);
+
+ iterations_str = read_attr_value(&input, 'i', errormessage);
+ if (iterations_str == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+ state->iterations = strtol(iterations_str, &endptr, SCRAM_ITERATION_LEN);
+ if (*endptr != '\0' || state->iterations < 1)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed SCRAM message (invalid iteration count)\n"));
+ return false;
+ }
+
+ if (*input != '\0')
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed SCRAM message (garbage at end of server-first-message)\n"));
+
+ return true;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ /* Check for error result. */
+ if (*input == 'e')
+ {
+ char *errmsg = read_attr_value(&input, 'e', errormessage);
+
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("error received from server in SASL exchange: %s\n"),
+ errmsg);
+ return false;
+ }
+
+ /* Parse the message. */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ {
+ /* read_attr_value() has generated an error message */
+ return false;
+ }
+
+ if (*input != '\0')
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed SCRAM message (garbage at end of server-final-message)\n"));
+
+ server_proof_len = pg_b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed SCRAM message (invalid server proof)\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+static void
+calculate_client_proof(fe_scram_state *state,
+ const char *client_final_message_without_proof,
+ uint8 *result)
+{
+ uint8 StoredKey[SCRAM_KEY_LEN];
+ uint8 ClientKey[SCRAM_KEY_LEN];
+ uint8 ClientSignature[SCRAM_KEY_LEN];
+ int i;
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx,
+ state->client_first_message_bare,
+ strlen(state->client_first_message_bare));
+ scram_HMAC_update(&ctx, ",", 1);
+ scram_HMAC_update(&ctx,
+ state->server_first_message,
+ strlen(state->server_first_message));
+ scram_HMAC_update(&ctx, ",", 1);
+ scram_HMAC_update(&ctx,
+ client_final_message_without_proof,
+ strlen(client_final_message_without_proof));
+ scram_HMAC_final(ClientSignature, &ctx);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx,
+ state->client_first_message_bare,
+ strlen(state->client_first_message_bare));
+ scram_HMAC_update(&ctx, ",", 1);
+ scram_HMAC_update(&ctx,
+ state->server_first_message,
+ strlen(state->server_first_message));
+ scram_HMAC_update(&ctx, ",", 1);
+ scram_HMAC_update(&ctx,
+ state->client_final_message_without_proof,
+ strlen(state->client_final_message_without_proof));
+ scram_HMAC_final(ServerSignature, &ctx);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Random number generator.
+ */
+static bool
+pg_frontend_random(char *dst, int len)
+{
+#ifdef HAVE_STRONG_RANDOM
+ return pg_strong_random(dst, len);
+#else
+ int i;
+ char *end = dst + len;
+
+ static unsigned short seed[3];
+ static int mypid = 0;
+
+ pglock_thread();
+
+ if (mypid != getpid())
+ {
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+
+ seed[0] = now.tv_sec ^ getpid();
+ seed[1] = (unsigned short) (now.tv_usec);
+ seed[2] = (unsigned short) (now.tv_usec >> 16);
+ }
+
+ for (i = 0; dst < end; i++)
+ {
+ uint32 r;
+ int j;
+
+ /*
+ * pg_jrand48 returns a 32-bit integer. Fill the next 4 bytes from
+ * it.
+ */
+ r = (uint32) pg_jrand48(seed);
+
+ for (j = 0; j < 4 && dst < end; j++)
+ {
+ *(dst++) = (char) (r & 0xFF);
+ r >>= 8;
+ }
+ }
+
+ pgunlock_thread();
+
+ return true;
+#endif
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index b47a16e3d01..c69260b5226 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -40,6 +40,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
+#include "libpq/scram.h"
#include "fe-auth.h"
@@ -433,6 +434,87 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL authentication exchange.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 0)
+ {
+ char *password = conn->connhost[conn->whichhost].password;
+
+ if (password == NULL)
+ password = conn->pgpass;
+ conn->password_needed = true;
+ if (password == NULL || password == '\0')
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ PQnoPasswordSupplied);
+ return STATUS_ERROR;
+ }
+
+ conn->sasl_state = pg_fe_scram_init(conn->pguser, password);
+ if (!conn->sasl_state)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return STATUS_ERROR;
+ }
+
+ return STATUS_OK;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SASL authentication mechanism %s not supported\n"),
+ (char *) conn->auth_req_inbuf);
+ return STATUS_ERROR;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+static int
+pg_SASL_exchange(PGconn *conn)
+{
+ char *output;
+ int outputlen;
+ bool done;
+ bool success;
+ int res;
+
+ pg_fe_scram_exchange(conn->sasl_state,
+ conn->auth_req_inbuf, conn->auth_req_inlen,
+ &output, &outputlen,
+ &done, &success, &conn->errorMessage);
+ if (outputlen != 0)
+ {
+ /*
+ * Send the SASL response to the server. We don't care if it's the
+ * first or subsequent packet, just send the same kind of password
+ * packet.
+ */
+ res = pqPacketSend(conn, 'p', output, outputlen);
+ free(output);
+
+ if (res != STATUS_OK)
+ return STATUS_ERROR;
+ }
+
+ if (done && !success)
+ return STATUS_ERROR;
+
+ return STATUS_OK;
+}
+
+/*
* Respond to AUTH_REQ_SCM_CREDS challenge.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -707,6 +789,36 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
break;
}
+ case AUTH_REQ_SASL:
+
+ /*
+ * The request contains the name (as assigned by IANA) of the
+ * authentication mechanism.
+ */
+ if (pg_SASL_init(conn, conn->auth_req_inbuf) != STATUS_OK)
+ {
+ /* pg_SASL_init already set the error message */
+ return STATUS_ERROR;
+ }
+ /* fall through */
+
+ case AUTH_REQ_SASL_CONT:
+ if (conn->sasl_state == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ "fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n");
+ return STATUS_ERROR;
+ }
+ if (pg_SASL_exchange(conn) != STATUS_OK)
+ {
+ /* Use error message, if set already */
+ if (conn->errorMessage.len == 0)
+ printfPQExpBuffer(&conn->errorMessage,
+ "fe_sendauth: error in SASL authentication\n");
+ return STATUS_ERROR;
+ }
+ break;
+
case AUTH_REQ_SCM_CREDS:
if (pg_local_sendauth(conn) != STATUS_OK)
return STATUS_ERROR;
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 0aa6a287683..204790cea46 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -18,7 +18,15 @@
#include "libpq-int.h"
+/* Prototypes for functions in fe-auth.c */
extern int pg_fe_sendauth(AuthRequest areq, PGconn *conn);
extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
+/* Prototypes for functions in fe-auth-scram.c */
+extern void *pg_fe_scram_init(const char *username, const char *password);
+extern void pg_fe_scram_free(void *opaq);
+extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success, PQExpBuffer errorMessage);
+
#endif /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 685f355ab36..c1814f5fe43 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2720,6 +2720,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ msgLength > 4)
+ {
+ int llen = msgLength - 4;
+
+ /*
+ * We can be called repeatedly for the same buffer. Avoid
+ * re-allocating the buffer in this case - just re-use the
+ * old buffer.
+ */
+ if (llen != conn->auth_req_inlen)
+ {
+ if (conn->auth_req_inbuf)
+ {
+ free(conn->auth_req_inbuf);
+ conn->auth_req_inbuf = NULL;
+ }
+
+ conn->auth_req_inlen = llen;
+ conn->auth_req_inbuf = malloc(llen + 1);
+ if (!conn->auth_req_inbuf)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory allocating SASL buffer (%d)"),
+ llen);
+ goto error_return;
+ }
+ }
+
+ if (pqGetnchar(conn->auth_req_inbuf, llen, conn))
+ {
+ /* We'll come back when there is more data. */
+ return PGRES_POLLING_READING;
+ }
+
+ /*
+ * For safety and convenience, always ensure the in-buffer
+ * is NULL-terminated.
+ */
+ conn->auth_req_inbuf[llen] = '\0';
+ }
/*
* OK, we successfully read the message; mark data consumed
@@ -3506,6 +3549,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, this
+ * needs to call the right 'free' function.
+ */
+ pg_fe_scram_free(conn->sasl_state);
+ conn->sasl_state = NULL;
+ }
}
/*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 24242da221e..360956d6eb3 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -453,7 +453,12 @@ struct pg_conn
PGresult *result; /* result being constructed */
PGresult *next_result; /* next result (used in single-row mode) */
- /* Assorted state for SSL, GSS, etc */
+ /* Buffer to hold incoming authentication request data */
+ char *auth_req_inbuf;
+ int auth_req_inlen;
+
+ /* Assorted state for SASL, SSL, GSS, etc */
+ void *sasl_state;
#ifdef USE_SSL
bool allow_ssl_try; /* Allowed to try SSL negotiation */