From 397831e1039ecc335e1aae54ebea8483ce2e6b72 Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Sat, 26 Jul 2003 13:50:02 +0000 Subject: [PATCH] At long last I put together a patch to support 4 client SSL negotiation modes (and replace the requiressl boolean). The four options were first spelled out by Magnus Hagander on 2000-08-23 in email to pgsql-hackers, archived here: http://archives.postgresql.org/pgsql-hackers/2000-08/msg00639.php My original less-flexible patch and the ensuing thread are archived at: http://dbforums.com/t623845.html Attached is a new patch, including documentation. To sum up, there's a new client parameter "sslmode" and environment variable "PGSSLMODE", with these options: sslmode description ------- ----------- disable Unencrypted non-SSL only allow Negotiate, prefer non-SSL prefer Negotiate, prefer SSL (default) require Require SSL The only change to the server is a new pg_hba.conf line type, "hostnossl", for specifying connections that are not allowed to use SSL (for example, to prevent servers on a local network from accidentally using SSL and wasting cycles). Thus the 3 pg_hba.conf line types are: pg_hba.conf line types ---------------------- host applies to either SSL or regular connections hostssl applies only to SSL connections hostnossl applies only to regular connections These client and server options, the postgresql.conf ssl = false option, and finally the possibility of compiling with no SSL support at all, make quite a range of combinations to test. I threw together a test script to try many of them out. It's in a separate tarball with its config files, a patch to psql so it'll announce SSL connections even in absence of a tty, and the test output. The test is especially informative when run on the same tty the postmaster was started on, so the FATAL: errors during negotiation are interleaved with the psql client output. I saw Tom write that new submissions for 7.4 have to be in before midnight local time, and since I'm on the east coast in the US, this just makes it in before the bell. :) Jon Jensen --- doc/src/sgml/client-auth.sgml | 25 +++- doc/src/sgml/libpq.sgml | 63 +++++++++- src/backend/libpq/auth.c | 12 +- src/backend/libpq/hba.c | 16 ++- src/interfaces/libpq/fe-connect.c | 200 +++++++++++++++++++++++++++--- src/interfaces/libpq/libpq-int.h | 6 +- 6 files changed, 286 insertions(+), 36 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index cad51caca9..e6180c762e 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1,5 +1,5 @@ @@ -83,13 +83,15 @@ $Header: /cvsroot/pgsql/doc/src/sgml/client-auth.sgml,v 1.52 2003/06/25 01:20:50 - A record may have one of the five formats + A record may have one of the seven formats local database user authentication-method authentication-option host database user IP-address IP-mask authentication-method authentication-option hostssl database user IP-address IP-mask authentication-method authentication-option +hostnossl database user IP-address IP-mask authentication-method authentication-option host database user IP-address/CIDR-mask authentication-method authentication-option hostssl database user IP-address/CIDR-mask authentication-method authentication-option +hostnossl database user IP-address/CIDR-mask authentication-method authentication-option The meaning of the fields is as follows: @@ -136,6 +138,17 @@ hostssl database user < + + hostnossl + + + This record is similar to hostssl but with the + opposite logic: it matches only regular connection attempts not + using SSL. + + + + database @@ -196,8 +209,8 @@ hostssl database user < - These fields only apply to host and - hostssl records. + These fields only apply to host, + hostssl, and hostnossl records. @@ -224,8 +237,8 @@ hostssl database user < - This field only applies to host and - hostssl records. + This field only applies to host, + hostssl, and hostnossl records. diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index fa2db652b2..1ebbd31443 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,5 +1,5 @@ @@ -206,14 +206,44 @@ PGconn *PQconnectdb(const char *conninfo); + + sslmode + + + This option determines whether or with what priority an SSL + connection will be negotiated with the server. There are four + modes: disable will attempt only an unencrypted + SSL connection; allow will negotiate, + trying first a non-SSL connection, then if that fails, + trying an SSL connection; prefer + (the default) will negotiate, trying first an SSL connection, + then if that fails, trying a regular non-SSL connection; + require will try only an SSL connection. + + + If PostgreSQL is compiled without SSL support, + using option require will cause an error, and options + allow and prefer will be tolerated but + libpq will be unable to negotiate an SSL + connection. + + + + requiressl - If set to 1, an SSL connection to the server is required. + This option is deprecated in favor of the sslmode + setting. + + + If set to 1, an SSL connection to the server is required + (this is equivalent to sslmode require). libpq will then refuse to connect if the server does not accept an SSL connection. - If set to 0 (default), libpq will negotiate the connection type with server. + If set to 0 (default), libpq will negotiate the connection + type with the server (equivalent to sslmode prefer). This option is only available if PostgreSQL is compiled with SSL support. @@ -3140,6 +3170,27 @@ the PostgreSQL server. + + PGSSLMODE + +PGSSLMODE determines whether and with what priority an +SSL connection will be negotiated with the server. There are +four modes: disable will attempt only an unencrypted +SSL connection; allow will negotiate, +trying first a non-SSL connection, then if that fails, +trying an SSL connection; prefer +(the default) will negotiate, trying first an SSL +connection, then if that fails, trying a regular non-SSL +connection; require will try only an SSL +connection. If PostgreSQL is compiled without SSL support, +using option require will cause an error, and options +allow and prefer will be tolerated but +libpq will be unable to negotiate an SSL +connection. + + + + PGREQUIRESSL @@ -3147,8 +3198,10 @@ the PostgreSQL server. made over SSL. If set to 1, libpq will refuse to connect if the server does not accept -an SSL connection. -This option is only available if +an SSL connection (equivalent to sslmode +prefer). +This option is deprecated in favor of the sslmode +setting, and is only available if PostgreSQL is compiled with SSL support. diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index a24f097846..fd8d54a012 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.105 2003/07/23 23:30:40 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.106 2003/07/26 13:50:02 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -439,10 +439,16 @@ ClientAuthentication(Port *port) NULL, 0, NI_NUMERICHOST); +#ifdef USE_SSL +#define EREPORT_SSL_STATUS (port->ssl ? "on" : "off") +#else +#define EREPORT_SSL_STATUS "off" +#endif + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), - errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"", - hostinfo, port->user_name, port->database_name))); + errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", SSL \"%s\"", + hostinfo, port->user_name, port->database_name, EREPORT_SSL_STATUS))); break; } diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 0d98e729a4..b233ee235d 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.107 2003/07/23 23:30:40 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.108 2003/07/26 13:50:02 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -595,10 +595,12 @@ parse_hba(List *line, hbaPort *port, bool *found_p, bool *error_p) if (port->raddr.addr.ss_family != AF_UNIX) return; } - else if (strcmp(token, "host") == 0 || strcmp(token, "hostssl") == 0) + else if (strcmp(token, "host") == 0 + || strcmp(token, "hostssl") == 0 + || strcmp(token, "hostnossl") == 0) { - if (strcmp(token, "hostssl") == 0) + if (token[4] == 's') /* "hostssl" */ { #ifdef USE_SSL /* Record does not match if we are not on an SSL connection */ @@ -614,6 +616,14 @@ parse_hba(List *line, hbaPort *port, bool *found_p, bool *error_p) goto hba_syntax; #endif } +#ifdef USE_SSL + else if (token[4] == 'n') /* "hostnossl" */ + { + /* Record does not match if we are on an SSL connection */ + if (port->ssl) + return; + } +#endif /* Get the database. */ line = lnext(line); diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 0518cd21b4..6688e57038 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.253 2003/07/23 23:30:41 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.254 2003/07/26 13:50:02 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -60,6 +60,11 @@ long ioctlsocket_ret; #define DefaultOption "" #define DefaultAuthtype "" #define DefaultPassword "" +#ifdef USE_SSL +#define DefaultSSLMode "prefer" +#else +#define DefaultSSLMode "disable" +#endif /* ---------- @@ -131,10 +136,22 @@ static const PQconninfoOption PQconninfoOptions[] = { "Backend-Debug-Options", "D", 40}, #ifdef USE_SSL + /* + * "requiressl" is deprecated, its purpose having been taken over + * by "sslmode". It remains for backwards compatibility. + */ {"requiressl", "PGREQUIRESSL", "0", NULL, - "Require-SSL", "", 1}, + "Require-SSL", "D", 1}, #endif + /* + * "sslmode" option is allowed even without client SSL support + * because the client can still handle SSL modes "disable" and + * "allow". + */ + {"sslmode", "PGSSLMODE", DefaultSSLMode, NULL, + "SSL-Mode", "", 8}, /* sizeof("disable") == 8 */ + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} @@ -340,10 +357,17 @@ connectOptions1(PGconn *conn, const char *conninfo) conn->pgpass = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "connect_timeout"); conn->connect_timeout = tmp ? strdup(tmp) : NULL; + tmp = conninfo_getval(connOptions, "sslmode"); + conn->sslmode = tmp ? strdup(tmp) : NULL; #ifdef USE_SSL tmp = conninfo_getval(connOptions, "requiressl"); if (tmp && tmp[0] == '1') - conn->require_ssl = true; + { + /* here warn that the requiressl option is deprecated? */ + if (conn->sslmode) + free(conn->sslmode); + conn->sslmode = "require"; + } #endif /* @@ -412,6 +436,46 @@ connectOptions2(PGconn *conn) } #endif + /* + * validate sslmode option + */ + if (conn->sslmode) + { + if (strcmp(conn->sslmode, "disable") != 0 + && strcmp(conn->sslmode, "allow") != 0 + && strcmp(conn->sslmode, "prefer") != 0 + && strcmp(conn->sslmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unknown sslmode \"%s\" requested\n"), + conn->sslmode); + return false; + } + +#ifndef USE_SSL + switch (conn->sslmode[0]) { + case 'a': /* "allow" */ + case 'p': /* "prefer" */ + /* + * warn user that an SSL connection will never be + * negotiated since SSL was not compiled in? + */ + break; + + case 'r': /* "require" */ + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("sslmode \"%s\" invalid when SSL " + "support is not compiled in\n"), + conn->sslmode); + return false; + } +#endif + } + else + conn->sslmode = DefaultSSLMode; + return true; } @@ -878,6 +942,14 @@ connectDBStart(PGconn *conn) goto connect_errReturn; } +#ifdef USE_SSL + /* setup values based on SSL mode */ + if (conn->sslmode[0] == 'd') /* "disable" */ + conn->allow_ssl_try = false; + else if (conn->sslmode[0] == 'a') /* "allow" */ + conn->wait_ssl_try = true; +#endif + /* * Set up to try to connect, with protocol 3.0 as the first attempt. */ @@ -1278,9 +1350,8 @@ retry_connect: { /* Don't bother requesting SSL over a Unix socket */ conn->allow_ssl_try = false; - conn->require_ssl = false; } - if (conn->allow_ssl_try && conn->ssl == NULL) + if (conn->allow_ssl_try && !conn->wait_ssl_try && conn->ssl == NULL) { ProtocolVersion pv; @@ -1384,13 +1455,22 @@ retry_ssl_read: } else if (SSLok == 'N') { - if (conn->require_ssl) - { - /* Require SSL, but server does not want it */ - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("server does not support SSL, but SSL was required\n")); - goto error_return; + switch (conn->sslmode[0]) { + case 'r': /* "require" */ + /* Require SSL, but server does not want it */ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("server does not support SSL, but SSL was required\n")); + goto error_return; + case 'a': /* "allow" */ + /* + * normal startup already failed, + * so SSL failure means the end + */ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("server does not support SSL, and previous non-SSL attempt failed\n")); + goto error_return; } + /* Otherwise, proceed with normal startup */ conn->allow_ssl_try = false; conn->status = CONNECTION_MADE; @@ -1401,13 +1481,22 @@ retry_ssl_read: /* Received error - probably protocol mismatch */ if (conn->Pfdebug) fprintf(conn->Pfdebug, "Postmaster reports error, attempting fallback to pre-7.0.\n"); - if (conn->require_ssl) - { - /* Require SSL, but server is too old */ - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("server does not support SSL, but SSL was required\n")); - goto error_return; + switch (conn->sslmode[0]) { + case 'r': /* "require" */ + /* Require SSL, but server is too old */ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("server does not support SSL, but SSL was required\n")); + goto error_return; + case 'a': /* "allow" */ + /* + * normal startup already failed, + * so SSL failure means the end + */ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("server does not support SSL, and previous non-SSL attempt failed\n")); + goto error_return; } + /* Otherwise, try again without SSL */ conn->allow_ssl_try = false; /* Assume it ain't gonna handle protocol 3, either */ @@ -1594,6 +1683,45 @@ retry_ssl_read: } /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; + +#ifdef USE_SSL + /* + * if sslmode is "allow" and we haven't tried an + * SSL connection already, then retry with an SSL connection + */ + if (conn->wait_ssl_try + && conn->ssl == NULL + && conn->allow_ssl_try) + { + conn->wait_ssl_try = false; + /* Must drop the old connection */ + closesocket(conn->sock); + conn->sock = -1; + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* + * if sslmode is "prefer" and we're in an SSL + * connection and we haven't already tried a non-SSL + * for "allow", then do a non-SSL retry + */ + if (!conn->wait_ssl_try + && conn->ssl + && conn->allow_ssl_try + && conn->sslmode[0] == 'p') /* "prefer" */ + { + conn->allow_ssl_try = false; + /* Must drop the old connection */ + pqsecure_close(conn); + closesocket(conn->sock); + conn->sock = -1; + free(conn->ssl); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + goto error_return; } @@ -1645,6 +1773,44 @@ retry_ssl_read: if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass, conn->errorMessage.data) != STATUS_OK) { +#ifdef USE_SSL + /* + * if sslmode is "allow" and we haven't tried an + * SSL connection already, then retry with an SSL connection + */ + if (conn->wait_ssl_try + && conn->ssl == NULL + && conn->allow_ssl_try) + { + conn->wait_ssl_try = false; + /* Must drop the old connection */ + closesocket(conn->sock); + conn->sock = -1; + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* + * if sslmode is "prefer" and we're in an SSL + * connection and we haven't already tried a non-SSL + * for "allow", then do a non-SSL retry + */ + if (!conn->wait_ssl_try + && conn->ssl + && conn->allow_ssl_try + && conn->sslmode[0] == 'p') /* "prefer" */ + { + conn->allow_ssl_try = false; + /* Must drop the old connection */ + pqsecure_close(conn); + closesocket(conn->sock); + conn->sock = -1; + free(conn->ssl); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + conn->errorMessage.len = strlen(conn->errorMessage.data); goto error_return; } diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index d97db7c88b..d05681d37f 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-int.h,v 1.76 2003/06/23 19:20:25 tgl Exp $ + * $Id: libpq-int.h,v 1.77 2003/07/26 13:50:02 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -316,9 +316,11 @@ struct pg_conn PGresult *result; /* result being constructed */ PGresAttValue *curTuple; /* tuple currently being read */ + char *sslmode; /* SSL mode option string */ #ifdef USE_SSL bool allow_ssl_try; /* Allowed to try SSL negotiation */ - bool require_ssl; /* Require SSL to make connection */ + bool wait_ssl_try; /* Delay SSL negotiation until after + attempting normal connection */ SSL *ssl; /* SSL status, if have SSL connection */ X509 *peer; /* X509 cert of server */ char peer_dn[256 + 1]; /* peer distinguished name */ -- 2.40.0