*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.118 2009/01/19 17:17:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.129 2009/12/09 06:37:06 mha Exp $
*
* NOTES
*
#include "libpq-fe.h"
#include "fe-auth.h"
#include "pqsignal.h"
+#include "libpq-int.h"
#ifdef WIN32
#include "win32.h"
#if (SSLEAY_VERSION_NUMBER >= 0x00907000L)
#include <openssl/conf.h>
#endif
-#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) && !defined(OPENSSL_NO_ENGINE)
+#ifdef USE_SSL_ENGINE
#include <openssl/engine.h>
#endif
static char *SSLerrmessage(void);
static void SSLerrfree(char *buf);
-static bool pq_initssllib = true;
+static bool pq_init_ssl_lib = true;
+static bool pq_init_crypto_lib = true;
static SSL_CTX *SSL_context = NULL;
#ifdef ENABLE_THREAD_SAFETY
-static int ssl_open_connections = 0;
+static long ssl_open_connections = 0;
#ifndef WIN32
static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t ssl_config_mutex = NULL;
static long win32_ssl_create_mutex = 0;
#endif
-
-#endif /* ENABLE_THREAD_SAFETY */
-
-#endif /* SSL */
+#endif /* ENABLE_THREAD_SAFETY */
+#endif /* SSL */
/*
* Macros to handle disabling and then restoring the state of SIGPIPE handling.
- * Note that DISABLE_SIGPIPE() must appear at the start of a block.
+ * On Windows, these are all no-ops since there's no SIGPIPEs.
*/
#ifndef WIN32
+
+#define SIGPIPE_MASKED(conn) ((conn)->sigpipe_so || (conn)->sigpipe_flag)
+
#ifdef ENABLE_THREAD_SAFETY
-#define DISABLE_SIGPIPE(failaction) \
- sigset_t osigmask; \
- bool sigpipe_pending; \
- bool got_epipe = false; \
-\
- if (pq_block_sigpipe(&osigmask, &sigpipe_pending) < 0) \
- failaction
+struct sigpipe_info
+{
+ sigset_t oldsigmask;
+ bool sigpipe_pending;
+ bool got_epipe;
+};
+
+#define DECLARE_SIGPIPE_INFO(spinfo) struct sigpipe_info spinfo
+
+#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
+ do { \
+ (spinfo).got_epipe = false; \
+ if (!SIGPIPE_MASKED(conn)) \
+ { \
+ if (pq_block_sigpipe(&(spinfo).oldsigmask, \
+ &(spinfo).sigpipe_pending) < 0) \
+ failaction; \
+ } \
+ } while (0)
-#define REMEMBER_EPIPE(cond) \
+#define REMEMBER_EPIPE(spinfo, cond) \
do { \
if (cond) \
- got_epipe = true; \
+ (spinfo).got_epipe = true; \
+ } while (0)
+
+#define RESTORE_SIGPIPE(conn, spinfo) \
+ do { \
+ if (!SIGPIPE_MASKED(conn)) \
+ pq_reset_sigpipe(&(spinfo).oldsigmask, (spinfo).sigpipe_pending, \
+ (spinfo).got_epipe); \
} while (0)
-#define RESTORE_SIGPIPE() \
- pq_reset_sigpipe(&osigmask, sigpipe_pending, got_epipe)
+#else /* !ENABLE_THREAD_SAFETY */
-#else /* !ENABLE_THREAD_SAFETY */
+#define DECLARE_SIGPIPE_INFO(spinfo) pqsigfunc spinfo = NULL
-#define DISABLE_SIGPIPE(failaction) \
- pqsigfunc oldsighandler = pqsignal(SIGPIPE, SIG_IGN)
+#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
+ do { \
+ if (!SIGPIPE_MASKED(conn)) \
+ spinfo = pqsignal(SIGPIPE, SIG_IGN); \
+ } while (0)
-#define REMEMBER_EPIPE(cond)
+#define REMEMBER_EPIPE(spinfo, cond)
-#define RESTORE_SIGPIPE() \
- pqsignal(SIGPIPE, oldsighandler)
+#define RESTORE_SIGPIPE(conn, spinfo) \
+ do { \
+ if (!SIGPIPE_MASKED(conn)) \
+ pqsignal(SIGPIPE, spinfo); \
+ } while (0)
#endif /* ENABLE_THREAD_SAFETY */
+
#else /* WIN32 */
-#define DISABLE_SIGPIPE(failaction)
-#define REMEMBER_EPIPE(cond)
-#define RESTORE_SIGPIPE()
+#define DECLARE_SIGPIPE_INFO(spinfo)
+#define DISABLE_SIGPIPE(conn, spinfo, failaction)
+#define REMEMBER_EPIPE(spinfo, cond)
+#define RESTORE_SIGPIPE(conn, spinfo)
#endif /* WIN32 */
*/
void
PQinitSSL(int do_init)
+{
+ PQinitOpenSSL(do_init, do_init);
+}
+
+/*
+ * Exported function to allow application to tell us it's already
+ * initialized OpenSSL and/or libcrypto.
+ */
+void
+PQinitOpenSSL(int do_ssl, int do_crypto)
{
#ifdef USE_SSL
- pq_initssllib = do_init;
+#ifdef ENABLE_THREAD_SAFETY
+
+ /*
+ * Disallow changing the flags while we have open connections, else we'd
+ * get completely confused.
+ */
+ if (ssl_open_connections != 0)
+ return;
+#endif
+
+ pq_init_ssl_lib = do_ssl;
+ pq_init_crypto_lib = do_crypto;
#endif
}
/* First time through? */
if (conn->ssl == NULL)
{
+ /* We cannot use MSG_NOSIGNAL to block SIGPIPE when using SSL */
+ conn->sigpipe_flag = false;
+
if (!(conn->ssl = SSL_new(SSL_context)) ||
!SSL_set_app_data(conn->ssl, conn) ||
!SSL_set_fd(conn->ssl, conn->sock))
if (conn->ssl)
{
int err;
+ DECLARE_SIGPIPE_INFO(spinfo);
/* SSL_read can write to the socket, so we need to disable SIGPIPE */
- DISABLE_SIGPIPE(return -1);
+ DISABLE_SIGPIPE(conn, spinfo, return -1);
rloop:
n = SSL_read(conn->ssl, ptr, len);
if (n == -1)
{
- REMEMBER_EPIPE(SOCK_ERRNO == EPIPE);
+ REMEMBER_EPIPE(spinfo, SOCK_ERRNO == EPIPE);
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
break;
}
- RESTORE_SIGPIPE();
+ RESTORE_SIGPIPE(conn, spinfo);
}
else
#endif
pqsecure_write(PGconn *conn, const void *ptr, size_t len)
{
ssize_t n;
-
- DISABLE_SIGPIPE(return -1);
+ DECLARE_SIGPIPE_INFO(spinfo);
#ifdef USE_SSL
if (conn->ssl)
{
int err;
+ DISABLE_SIGPIPE(conn, spinfo, return -1);
+
n = SSL_write(conn->ssl, ptr, len);
err = SSL_get_error(conn->ssl, n);
switch (err)
if (n == -1)
{
- REMEMBER_EPIPE(SOCK_ERRNO == EPIPE);
+ REMEMBER_EPIPE(spinfo, SOCK_ERRNO == EPIPE);
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
else
#endif
{
- n = send(conn->sock, ptr, len, 0);
- REMEMBER_EPIPE(n < 0 && SOCK_ERRNO == EPIPE);
+ int flags = 0;
+
+#ifdef MSG_NOSIGNAL
+ if (conn->sigpipe_flag)
+ flags |= MSG_NOSIGNAL;
+
+retry_masked:
+
+#endif /* MSG_NOSIGNAL */
+
+ DISABLE_SIGPIPE(conn, spinfo, return -1);
+
+ n = send(conn->sock, ptr, len, flags);
+
+ if (n < 0)
+ {
+ /*
+ * If we see an EINVAL, it may be because MSG_NOSIGNAL isn't
+ * available on this machine. So, clear sigpipe_flag so we don't
+ * try the flag again, and retry the send().
+ */
+#ifdef MSG_NOSIGNAL
+ if (flags != 0 && SOCK_ERRNO == EINVAL)
+ {
+ conn->sigpipe_flag = false;
+ flags = 0;
+ goto retry_masked;
+ }
+#endif /* MSG_NOSIGNAL */
+
+ REMEMBER_EPIPE(spinfo, SOCK_ERRNO == EPIPE);
+ }
}
- RESTORE_SIGPIPE();
+ RESTORE_SIGPIPE(conn, spinfo);
return n;
}
* Check if a wildcard certificate matches the server hostname.
*
* The rule for this is:
- * 1. We only match the '*' character as wildcard
- * 2. We match only wildcards at the start of the string
- * 3. The '*' character does *not* match '.', meaning that we match only
- * a single pathname component.
- * 4. We don't support more than one '*' in a single pattern.
+ * 1. We only match the '*' character as wildcard
+ * 2. We match only wildcards at the start of the string
+ * 3. The '*' character does *not* match '.', meaning that we match only
+ * a single pathname component.
+ * 4. We don't support more than one '*' in a single pattern.
*
* This is roughly in line with RFC2818, but contrary to what most browsers
* appear to be implementing (point 3 being the difference)
static int
wildcard_certificate_match(const char *pattern, const char *string)
{
- int lenpat = strlen(pattern);
- int lenstr = strlen(string);
+ int lenpat = strlen(pattern);
+ int lenstr = strlen(string);
/* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
if (lenpat < 3 ||
/* If pattern is longer than the string, we can never match */
return 0;
- if (pg_strcasecmp(pattern+1, string+lenstr-lenpat+1) != 0)
- /* If string does not end in pattern (minus the wildcard), we don't match */
+ if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
+
+ /*
+ * If string does not end in pattern (minus the wildcard), we don't
+ * match
+ */
return 0;
- if (strchr(string, '.') < string+lenstr-lenpat)
- /* If there is a dot left of where the pattern started to match, we don't match (rule 3) */
+ if (strchr(string, '.') < string + lenstr - lenpat)
+
+ /*
+ * If there is a dot left of where the pattern started to match, we
+ * don't match (rule 3)
+ */
return 0;
/* String ended with pattern, and didn't have a dot before, so we match */
verify_peer_name_matches_certificate(PGconn *conn)
{
/*
- * If told not to verify the peer name, don't do it. Return
- * 0 indicating that the verification was successful.
+ * If told not to verify the peer name, don't do it. Return 0 indicating
+ * that the verification was successful.
*/
- if(strcmp(conn->sslverify, "cn") != 0)
+ if (strcmp(conn->sslmode, "verify-full") != 0)
return true;
if (conn->pghostaddr)
BIO_free(bio);
/*
- * Read the SSL key. If a key is specified, treat it as an engine:key combination
- * if there is colon present - we don't support files with colon in the name. The
- * exception is if the second character is a colon, in which case it can be a Windows
- * filename with drive specification.
+ * Read the SSL key. If a key is specified, treat it as an engine:key
+ * combination if there is colon present - we don't support files with
+ * colon in the name. The exception is if the second character is a colon,
+ * in which case it can be a Windows filename with drive specification.
*/
if (conn->sslkey && strlen(conn->sslkey) > 0)
{
-#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) && !defined(OPENSSL_NO_ENGINE)
+#ifdef USE_SSL_ENGINE
if (strchr(conn->sslkey, ':')
#ifdef WIN32
&& conn->sslkey[1] != ':'
#endif
- )
+ )
{
/* Colon, but not in second character, treat as engine:key */
- ENGINE *engine_ptr;
char *engine_str = strdup(conn->sslkey);
char *engine_colon = strchr(engine_str, ':');
- *engine_colon = '\0'; /* engine_str now has engine name */
- engine_colon++; /* engine_colon now has key name */
+ *engine_colon = '\0'; /* engine_str now has engine name */
+ engine_colon++; /* engine_colon now has key name */
+
+ conn->engine = ENGINE_by_id(engine_str);
+ if (conn->engine == NULL)
+ {
+ char *err = SSLerrmessage();
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load SSL engine \"%s\": %s\n"),
+ engine_str, err);
+ SSLerrfree(err);
+ free(engine_str);
+ ERR_pop_to_mark();
+ return 0;
+ }
- engine_ptr = ENGINE_by_id(engine_str);
- if (engine_ptr == NULL)
+ if (ENGINE_init(conn->engine) == 0)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not load SSL engine \"%s\": %s\n"),
+ libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
engine_str, err);
SSLerrfree(err);
+ ENGINE_free(conn->engine);
+ conn->engine = NULL;
free(engine_str);
ERR_pop_to_mark();
return 0;
}
- *pkey = ENGINE_load_private_key(engine_ptr, engine_colon,
+ *pkey = ENGINE_load_private_key(conn->engine, engine_colon,
NULL, NULL);
if (*pkey == NULL)
{
libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
engine_colon, engine_str, err);
SSLerrfree(err);
+ ENGINE_finish(conn->engine);
+ ENGINE_free(conn->engine);
+ conn->engine = NULL;
free(engine_str);
ERR_pop_to_mark();
return 0;
}
free(engine_str);
- fnbuf[0] = '\0'; /* indicate we're not going to load from a file */
+ fnbuf[0] = '\0'; /* indicate we're not going to load from a
+ * file */
}
else
-#endif /* support for SSL engines */
+#endif /* support for SSL engines */
{
/* PGSSLKEY is not an engine, treat it as a filename */
strncpy(fnbuf, conn->sslkey, sizeof(fnbuf));
if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
{
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
fnbuf);
ERR_pop_to_mark();
return 0;
}
/* verify that the cert and key go together */
- if (!X509_check_private_key(*x509, *pkey))
+ if (X509_check_private_key(*x509, *pkey) != 1)
{
char *err = SSLerrmessage();
/*
* Initialize SSL system. In threadsafe mode, this includes setting
- * up OpenSSL callback functions to do thread locking.
+ * up libcrypto callback functions to do thread locking.
*
- * If the caller has told us (through PQinitSSL) that he's taking care
- * of SSL, we expect that callbacks are already set, and won't try to
+ * If the caller has told us (through PQinitOpenSSL) that he's taking care
+ * of libcrypto, we expect that callbacks are already set, and won't try to
* override it.
*
* The conn parameter is only used to be able to pass back an error
if (pthread_mutex_lock(&ssl_config_mutex))
return -1;
- if (pq_initssllib)
+ if (pq_init_crypto_lib)
{
/*
- * If necessary, set up an array to hold locks for OpenSSL. OpenSSL will
- * tell us how big to make this array.
+ * If necessary, set up an array to hold locks for libcrypto.
+ * libcrypto will tell us how big to make this array.
*/
if (pq_lockarray == NULL)
{
- int i;
+ int i;
pq_lockarray = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks());
if (!pq_lockarray)
if (ssl_open_connections++ == 0)
{
- /* These are only required for threaded SSL applications */
+ /* These are only required for threaded libcrypto applications */
CRYPTO_set_id_callback(pq_threadidcallback);
CRYPTO_set_locking_callback(pq_lockingcallback);
}
}
-#endif /* ENABLE_THREAD_SAFETY */
+#endif /* ENABLE_THREAD_SAFETY */
if (!SSL_context)
{
- if (pq_initssllib)
+ if (pq_init_ssl_lib)
{
#if SSLEAY_VERSION_NUMBER >= 0x00907000L
OPENSSL_config(NULL);
/*
* This function is needed because if the libpq library is unloaded
* from the application, the callback functions will no longer exist when
- * SSL used by other parts of the system. For this reason,
- * we unregister the SSL callback functions when the last libpq
- * connection is closed.
+ * libcrypto is used by other parts of the system. For this reason,
+ * we unregister the callback functions when the last libpq
+ * connection is closed. (The same would apply for OpenSSL callbacks
+ * if we had any.)
*
* Callbacks are only set when we're compiled in threadsafe mode, so
* we only need to remove them in this case.
if (pthread_mutex_lock(&ssl_config_mutex))
return;
- if (pq_initssllib)
- {
- if (ssl_open_connections > 0)
- --ssl_open_connections;
+ if (pq_init_crypto_lib && ssl_open_connections > 0)
+ --ssl_open_connections;
- if (ssl_open_connections == 0)
- {
- /* No connections left, unregister all callbacks */
- CRYPTO_set_locking_callback(NULL);
- CRYPTO_set_id_callback(NULL);
+ if (pq_init_crypto_lib && ssl_open_connections == 0)
+ {
+ /* No connections left, unregister libcrypto callbacks */
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_id_callback(NULL);
- /*
- * We don't free the lock array. If we get another connection
- * from the same caller, we will just re-use it with the existing
- * mutexes.
- *
- * This means we leak a little memory on repeated load/unload
- * of the library.
- */
- }
+ /*
+ * We don't free the lock array. If we get another connection in this
+ * process, we will just re-use it with the existing mutexes.
+ *
+ * This means we leak a little memory on repeated load/unload of the
+ * library.
+ */
}
pthread_mutex_unlock(&ssl_config_mutex);
return -1;
/*
- * If sslverify is set to anything other than "none", perform certificate
- * verification. If set to "cn" we will also do further verifications after
- * the connection has been completed.
+ * If sslmode is set to one of the verify options, perform certificate
+ * verification. If set to "verify-full" we will also do further
+ * verification after the connection has been completed.
*
- * If we are going to look for either root certificate or CRL in the home directory,
- * we need pqGetHomeDirectory() to succeed. In other cases, we don't need to
- * get the home directory explicitly.
+ * If we are going to look for either root certificate or CRL in the home
+ * directory, we need pqGetHomeDirectory() to succeed. In other cases, we
+ * don't need to get the home directory explicitly.
*/
if (!conn->sslrootcert || !conn->sslcrl)
{
if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
{
- if (strcmp(conn->sslverify, "none") != 0)
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or
+ * "verify-full" */
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not get home directory to locate root certificate file"));
homedir[0] = '\0';
}
-
-
if (conn->sslrootcert)
strncpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
else
{
X509_STORE *cvstore;
- if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL))
+ if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1)
{
char *err = SSLerrmessage();
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
/* setting the flags to check against the complete CRL chain */
- if (X509_STORE_load_locations(cvstore, fnbuf, NULL) != 0)
+ if (X509_STORE_load_locations(cvstore, fnbuf, NULL) == 1)
/* OpenSSL 0.96 does not support X509_V_FLAG_CRL_CHECK */
#ifdef X509_V_FLAG_CRL_CHECK
X509_STORE_set_flags(cvstore,
- X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
+ X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
/* if not found, silently ignore; we do not require CRL */
#else
{
else
{
/* stat() failed; assume cert file doesn't exist */
- if (strcmp(conn->sslverify, "none") != 0)
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
{
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("root certificate file \"%s\" does not exist"), fnbuf);
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
return -1;
}
}
}
/*
- * We already checked the server certificate in initialize_SSL()
- * using SSL_CTX_set_verify() if root.crt exists.
+ * We already checked the server certificate in initialize_SSL() using
+ * SSL_CTX_set_verify() if root.crt exists.
*/
/* pull out server distinguished and common names */
conn->peer_dn, sizeof(conn->peer_dn));
conn->peer_dn[sizeof(conn->peer_dn) - 1] = '\0';
- X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
+ r = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
NID_commonName, conn->peer_cn, SM_USER);
- conn->peer_cn[SM_USER] = '\0';
+ conn->peer_cn[SM_USER] = '\0'; /* buffer is SM_USER+1 chars! */
+ if (r == -1)
+ {
+ /* Unable to get the CN, set it to blank so it can't be used */
+ conn->peer_cn[0] = '\0';
+ }
+ else
+ {
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (r != strlen(conn->peer_cn))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL certificate's common name contains embedded null\n"));
+ close_SSL(conn);
+ return PGRES_POLLING_FAILED;
+ }
+ }
if (!verify_peer_name_matches_certificate(conn))
{
{
if (conn->ssl)
{
- DISABLE_SIGPIPE((void) 0);
+ DECLARE_SIGPIPE_INFO(spinfo);
+
+ DISABLE_SIGPIPE(conn, spinfo, (void) 0);
SSL_shutdown(conn->ssl);
SSL_free(conn->ssl);
conn->ssl = NULL;
pqsecure_destroy();
/* We have to assume we got EPIPE */
- REMEMBER_EPIPE(true);
- RESTORE_SIGPIPE();
+ REMEMBER_EPIPE(spinfo, true);
+ RESTORE_SIGPIPE(conn, spinfo);
}
if (conn->peer)
X509_free(conn->peer);
conn->peer = NULL;
}
+
+#ifdef USE_SSL_ENGINE
+ if (conn->engine)
+ {
+ ENGINE_finish(conn->engine);
+ ENGINE_free(conn->engine);
+ conn->engine = NULL;
+ }
+#endif
}
/*