aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/libpq/fe-connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces/libpq/fe-connect.c')
-rw-r--r--src/interfaces/libpq/fe-connect.c236
1 files changed, 231 insertions, 5 deletions
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e3bf6a7449f..68cf4224570 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
#else
#define DefaultSSLMode "disable"
#endif
+#ifdef ENABLE_GSS
+#include "fe-gssapi-common.h"
+#define DefaultGSSMode "prefer"
+#else
+#define DefaultGSSMode "disable"
+#endif
/* ----------
* Definition of the conninfo parameters and their fallback resources.
@@ -298,6 +304,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ /*
+ * Expose gssencmode similarly to sslmode - we can still handle "disable"
+ * and "prefer".
+ */
+ {"gssencmode", "PGGSSMODE", DefaultGSSMode, NULL,
+ "GSS-Mode", "", 7, /* sizeof("disable") == 7 */
+ offsetof(struct pg_conn, gssencmode)},
+
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1227,6 +1241,39 @@ connectOptions2(PGconn *conn)
}
/*
+ * validate gssencmode option
+ */
+ if (conn->gssencmode)
+ {
+ if (strcmp(conn->gssencmode, "disable") != 0 &&
+ strcmp(conn->gssencmode, "prefer") != 0 &&
+ strcmp(conn->gssencmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid gssencmode value: \"%s\"\n"),
+ conn->gssencmode);
+ return false;
+ }
+#ifndef ENABLE_GSS
+ if (strcmp(conn->gssencmode, "require") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(
+ &conn->errorMessage,
+ libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->gssencmode = strdup(DefaultGSSMode);
+ if (!conn->gssencmode)
+ goto oom_error;
+ }
+
+ /*
* Resolve special "auto" client_encoding from the locale
*/
if (conn->client_encoding_initial &&
@@ -1827,6 +1874,11 @@ connectDBStart(PGconn *conn)
*/
resetPQExpBuffer(&conn->errorMessage);
+#ifdef ENABLE_GSS
+ if (conn->gssencmode[0] == 'd') /* "disable" */
+ conn->try_gss = false;
+#endif
+
/*
* Set up to try to connect to the first host. (Setting whichhost = -1 is
* a bit of a cheat, but PQconnectPoll will advance it to 0 before
@@ -2099,6 +2151,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
+ case CONNECTION_GSS_STARTUP:
break;
default:
@@ -2640,17 +2693,57 @@ keep_going: /* We will come back to here until there is
}
#endif /* HAVE_UNIX_SOCKETS */
+ if (IS_AF_UNIX(conn->raddr.addr.ss_family))
+ {
+ /* Don't request SSL or GSSAPI over Unix sockets */
#ifdef USE_SSL
+ conn->allow_ssl_try = false;
+#endif
+#ifdef ENABLE_GSS
+ conn->try_gss = false;
+#endif
+ }
+
+#ifdef ENABLE_GSS
/*
- * If SSL is enabled and we haven't already got it running,
- * request it instead of sending the startup message.
+ * If GSSAPI is enabled and we have a ccache, try to set it up
+ * before sending startup messages. If it's already
+ * operating, don't try SSL and instead just build the startup
+ * packet.
*/
- if (IS_AF_UNIX(conn->raddr.addr.ss_family))
+ if (conn->try_gss && !conn->gctx)
+ conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+ if (conn->try_gss && !conn->gctx)
{
- /* Don't bother requesting SSL over a Unix socket */
- conn->allow_ssl_try = false;
+ ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE);
+
+ if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
+ SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+ goto error_return;
+ }
+
+ /* Ok, wait for response */
+ conn->status = CONNECTION_GSS_STARTUP;
+ return PGRES_POLLING_READING;
}
+ else if (!conn->gctx && conn->gssencmode[0] == 'r')
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache, no server support, or using a local socket)\n"));
+ goto error_return;
+ }
+#endif
+
+#ifdef USE_SSL
+
+ /*
+ * If SSL is enabled and we haven't already got it running,
+ * request it instead of sending the startup message.
+ */
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@@ -2844,6 +2937,98 @@ keep_going: /* We will come back to here until there is
#endif /* USE_SSL */
}
+ case CONNECTION_GSS_STARTUP:
+ {
+#ifdef ENABLE_GSS
+ PostgresPollingStatusType pollres;
+
+ /*
+ * If we haven't yet, get the postmaster's response to our
+ * negotiation packet
+ */
+ if (conn->try_gss && !conn->gctx)
+ {
+ char gss_ok;
+ int rdresult = pqReadData(conn);
+
+ if (rdresult < 0)
+ /* pqReadData fills in error message */
+ goto error_return;
+ else if (rdresult == 0)
+ /* caller failed to wait for data */
+ return PGRES_POLLING_READING;
+ if (pqGetc(&gss_ok, conn) < 0)
+ /* shouldn't happen... */
+ return PGRES_POLLING_READING;
+
+ if (gss_ok == 'E')
+ {
+ /*
+ * Server failure of some sort. Assume it's a
+ * protocol version support failure, and let's see if
+ * we can't recover (if it's not, we'll get a better
+ * error message on retry). Server gets fussy if we
+ * don't hang up the socket, though.
+ */
+ conn->try_gss = false;
+ pqDropConnection(conn, true);
+ conn->status = CONNECTION_NEEDED;
+ goto keep_going;
+ }
+
+ /* mark byte consumed */
+ conn->inStart = conn->inCursor;
+
+ if (gss_ok == 'N')
+ {
+ /* Server doesn't want GSSAPI; fall back if we can */
+ if (conn->gssencmode[0] == 'r')
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
+ goto error_return;
+ }
+
+ conn->try_gss = false;
+ conn->status = CONNECTION_MADE;
+ return PGRES_POLLING_WRITING;
+ }
+ else if (gss_ok != 'G')
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
+ gss_ok);
+ goto error_return;
+ }
+ }
+
+ /* Begin or continue GSSAPI negotiation */
+ pollres = pqsecure_open_gss(conn);
+ if (pollres == PGRES_POLLING_OK)
+ {
+ /* All set for startup packet */
+ conn->status = CONNECTION_MADE;
+ return PGRES_POLLING_WRITING;
+ }
+ else if (pollres == PGRES_POLLING_FAILED &&
+ conn->gssencmode[0] == 'p')
+ {
+ /*
+ * We failed, but we can retry on "prefer". Have to drop
+ * the current connection to do so, though.
+ */
+ conn->try_gss = false;
+ pqDropConnection(conn, true);
+ conn->status = CONNECTION_NEEDED;
+ goto keep_going;
+ }
+ return pollres;
+#else /* !ENABLE_GSS */
+ /* unreachable */
+ goto error_return;
+#endif /* ENABLE_GSS */
+ }
+
/*
* Handle authentication exchange: wait for postmaster messages
* and respond as necessary.
@@ -2997,6 +3182,26 @@ keep_going: /* We will come back to here until there is
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
+#ifdef ENABLE_GSS
+
+ /*
+ * If gssencmode is "prefer" and we're using GSSAPI, retry
+ * without it.
+ */
+ if (conn->gssenc && conn->gssencmode[0] == 'p')
+ {
+ OM_uint32 minor;
+
+ /* postmaster expects us to drop the connection */
+ conn->try_gss = false;
+ conn->gssenc = false;
+ gss_delete_sec_context(&minor, &conn->gctx, NULL);
+ pqDropConnection(conn, true);
+ conn->status = CONNECTION_NEEDED;
+ goto keep_going;
+ }
+#endif
+
#ifdef USE_SSL
/*
@@ -3564,6 +3769,9 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
+#ifdef ENABLE_GSS
+ conn->try_gss = true;
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3695,10 +3903,28 @@ freePGconn(PGconn *conn)
free(conn->requirepeer);
if (conn->connip)
free(conn->connip);
+ if (conn->gssencmode)
+ free(conn->gssencmode);
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
if (conn->krbsrvname)
free(conn->krbsrvname);
#endif
+#ifdef ENABLE_GSS
+ if (conn->gcred != GSS_C_NO_CREDENTIAL)
+ {
+ OM_uint32 minor;
+
+ gss_release_cred(&minor, &conn->gcred);
+ conn->gcred = GSS_C_NO_CREDENTIAL;
+ }
+ if (conn->gctx)
+ {
+ OM_uint32 minor;
+
+ gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER);
+ conn->gctx = NULL;
+ }
+#endif
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
if (conn->gsslib)
free(conn->gsslib);