X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Finterfaces%2Flibpq%2Ffe-secure.c;h=5d1747821bf29006b7d2a23cc11b739450402340;hb=2c69fa0c388ccff1048749be06c7c522f23d4659;hp=6313dc146f00510c1ec03c4bbbc8f6455f05ba9e;hpb=3f0fa93cfc2442cb4952fa824525b4d7b4346db7;p=postgresql diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index 6313dc146f..5d1747821b 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -6,90 +6,29 @@ * message integrity and endpoint authentication. * * - * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.47 2004/08/17 16:54:47 momjian Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.110 2008/12/02 10:39:30 mha Exp $ * * NOTES - * The client *requires* a valid server certificate. Since - * SSH tunnels provide anonymous confidentiality, the presumption - * is that sites that want endpoint authentication will use the - * direct SSL support, while sites that are comfortable with - * anonymous connections will use SSH tunnels. - * - * This code verifies the server certificate, to detect simple - * "man-in-the-middle" and "impersonation" attacks. The - * server certificate, or better yet the CA certificate used - * to sign the server certificate, should be present in the - * "$HOME/.postgresql/root.crt" file. If this file isn't - * readable, or the server certificate can't be validated, - * pqsecure_open_client() will return an error code. - * - * Additionally, the server certificate's "common name" must - * resolve to the other end of the socket. This makes it - * substantially harder to pull off a "man-in-the-middle" or - * "impersonation" attack even if the server's private key - * has been stolen. This check limits acceptable network - * layers to Unix sockets (weird, but legal), TCPv4 and TCPv6. - * - * Unfortunately neither the current front- or back-end handle - * failure gracefully, resulting in the backend hiccupping. - * This points out problems in each (the frontend shouldn't even - * try to do SSL if pqsecure_initialize() fails, and the backend - * shouldn't crash/recover if an SSH negotiation fails. The - * backend definitely needs to be fixed, to prevent a "denial - * of service" attack, but I don't know enough about how the - * backend works (especially that pre-SSL negotiation) to identify - * a fix. - * - * ... - * - * Unlike the server's static private key, the client's - * static private key ($HOME/.postgresql/postgresql.key) - * should normally be stored encrypted. However we still - * support EPH since it's useful for other reasons. - * - * ... - * - * Client certificates are supported, if the server requests - * or requires them. Client certificates can be used for - * authentication, to prevent sessions from being hijacked, - * or to allow "road warriors" to access the database while - * keeping it closed to everyone else. - * - * The user's certificate and private key are located in - * $HOME/.postgresql/postgresql.crt - * and - * $HOME/.postgresql/postgresql.key - * respectively. - * - * ... * * We don't provide informational callbacks here (like * info_cb() in be-secure.c), since there's mechanism to * display that information to the client. * - * OS DEPENDENCIES - * The code currently assumes a POSIX password entry. How should - * Windows and Mac users be handled? - * *------------------------------------------------------------------------- */ #include "postgres_fe.h" -#include #include #include -#include #include -#include #include "libpq-fe.h" -#include "libpq-int.h" #include "fe-auth.h" #include "pqsignal.h" @@ -105,35 +44,49 @@ #endif #include #endif +#include #ifdef ENABLE_THREAD_SAFETY +#ifdef WIN32 +#include "pthread-win32.h" +#else #include #endif - -#ifndef HAVE_STRDUP -#include "strdup.h" #endif -#ifndef WIN32 -#include -#endif -#include - #ifdef USE_SSL + #include -#include -#endif /* USE_SSL */ +#include +#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) +#include +#endif +#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) && !defined(OPENSSL_NO_ENGINE) +#include +#endif -#ifdef USE_SSL -static int verify_cb(int ok, X509_STORE_CTX *ctx); +#ifndef WIN32 +#define USER_CERT_FILE ".postgresql/postgresql.crt" +#define USER_KEY_FILE ".postgresql/postgresql.key" +#define ROOT_CERT_FILE ".postgresql/root.crt" +#define ROOT_CRL_FILE ".postgresql/root.crl" +#else +/* On Windows, the "home" directory is already PostgreSQL-specific */ +#define USER_CERT_FILE "postgresql.crt" +#define USER_KEY_FILE "postgresql.key" +#define ROOT_CERT_FILE "root.crt" +#define ROOT_CRL_FILE "root.crl" +#endif -#ifdef NOT_USED -static int verify_peer(PGconn *); +#ifndef HAVE_ERR_SET_MARK +/* These don't exist in OpenSSL before 0.9.8 */ +#define ERR_set_mark() ((void) 0) +#define ERR_pop_to_mark() ((void) 0) #endif -static DH *load_dh_file(int keylength); -static DH *load_dh_buffer(const char *, size_t); -static DH *tmp_dh_cb(SSL *s, int is_export, int keylength); + +static bool verify_peer_name_matches_certificate(PGconn *); +static int verify_cb(int ok, X509_STORE_CTX *ctx); static int client_cert_cb(SSL *, X509 **, EVP_PKEY **); static int init_ssl_system(PGconn *conn); static int initialize_SSL(PGconn *); @@ -145,76 +98,71 @@ static void SSLerrfree(char *buf); #endif #ifdef USE_SSL -bool pq_initssllib = true; +static bool pq_initssllib = true; static SSL_CTX *SSL_context = NULL; #endif +/* + * Macros to handle disabling and then restoring the state of SIGPIPE handling. + * Note that DISABLE_SIGPIPE() must appear at the start of a block. + */ + +#ifndef WIN32 #ifdef ENABLE_THREAD_SAFETY -static void sigpipe_handler_ignore_send(int signo); -pthread_key_t pq_thread_in_send = 0; /* initializer needed on Darwin */ -static pqsigfunc pq_pipe_handler; -#endif + +#define DISABLE_SIGPIPE(failaction) \ + sigset_t osigmask; \ + bool sigpipe_pending; \ + bool got_epipe = false; \ +\ + if (pq_block_sigpipe(&osigmask, &sigpipe_pending) < 0) \ + failaction + +#define REMEMBER_EPIPE(cond) \ + do { \ + if (cond) \ + got_epipe = true; \ + } while (0) + +#define RESTORE_SIGPIPE() \ + pq_reset_sigpipe(&osigmask, sigpipe_pending, got_epipe) + +#else /* !ENABLE_THREAD_SAFETY */ + +#define DISABLE_SIGPIPE(failaction) \ + pqsigfunc oldsighandler = pqsignal(SIGPIPE, SIG_IGN) + +#define REMEMBER_EPIPE(cond) + +#define RESTORE_SIGPIPE() \ + pqsignal(SIGPIPE, oldsighandler) + +#endif /* ENABLE_THREAD_SAFETY */ +#else /* WIN32 */ + +#define DISABLE_SIGPIPE(failaction) +#define REMEMBER_EPIPE(cond) +#define RESTORE_SIGPIPE() + +#endif /* WIN32 */ /* ------------------------------------------------------------ */ -/* Hardcoded values */ +/* Procedures common to all secure sessions */ /* ------------------------------------------------------------ */ + /* - * Hardcoded DH parameters, used in empheral DH keying. - * As discussed above, EDH protects the confidentiality of - * sessions even if the static private key is compromised, - * so we are *highly* motivated to ensure that we can use - * EDH even if the user... or an attacker... deletes the - * $HOME/.postgresql/dh*.pem files. - * - * It's not critical that users have EPH keys, but it doesn't - * hurt and if it's missing someone will demand it, so.... + * Exported function to allow application to tell us it's already + * initialized OpenSSL. */ +void +PQinitSSL(int do_init) +{ #ifdef USE_SSL - -static const char file_dh512[] = -"-----BEGIN DH PARAMETERS-----\n\ -MEYCQQD1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMIPWak\n\ -XUGfnHy9iUsiGSa6q6Jew1XpKgVfAgEC\n\ ------END DH PARAMETERS-----\n"; - -static const char file_dh1024[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIGHAoGBAPSI/VhOSdvNILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsY\n\ -jY67VYy4XTjTNP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6\n\ -ypUM2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpL3jHAgEC\n\ ------END DH PARAMETERS-----\n"; - -static const char file_dh2048[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n\ -89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n\ -T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n\ -zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n\ -Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n\ -CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\ ------END DH PARAMETERS-----\n"; - -static const char file_dh4096[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIICCAKCAgEA+hRyUsFN4VpJ1O8JLcCo/VWr19k3BCgJ4uk+d+KhehjdRqNDNyOQ\n\ -l/MOyQNQfWXPeGKmOmIig6Ev/nm6Nf9Z2B1h3R4hExf+zTiHnvVPeRBhjdQi81rt\n\ -Xeoh6TNrSBIKIHfUJWBh3va0TxxjQIs6IZOLeVNRLMqzeylWqMf49HsIXqbcokUS\n\ -Vt1BkvLdW48j8PPv5DsKRN3tloTxqDJGo9tKvj1Fuk74A+Xda1kNhB7KFlqMyN98\n\ -VETEJ6c7KpfOo30mnK30wqw3S8OtaIR/maYX72tGOno2ehFDkq3pnPtEbD2CScxc\n\ -alJC+EL7RPk5c/tgeTvCngvc1KZn92Y//EI7G9tPZtylj2b56sHtMftIoYJ9+ODM\n\ -sccD5Piz/rejE3Ome8EOOceUSCYAhXn8b3qvxVI1ddd1pED6FHRhFvLrZxFvBEM9\n\ -ERRMp5QqOaHJkM+Dxv8Cj6MqrCbfC4u+ZErxodzuusgDgvZiLF22uxMZbobFWyte\n\ -OvOzKGtwcTqO/1wV5gKkzu1ZVswVUQd5Gg8lJicwqRWyyNRczDDoG9jVDxmogKTH\n\ -AaqLulO7R8Ifa1SwF2DteSGVtgWEN8gDpN3RBmmPTDngyF2DHb5qmpnznwtFKdTL\n\ -KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n\ ------END DH PARAMETERS-----\n"; + pq_initssllib = do_init; #endif - -/* ------------------------------------------------------------ */ -/* Procedures common to all secure sessions */ -/* ------------------------------------------------------------ */ +} /* * Initialize global context @@ -256,14 +204,21 @@ pqsecure_open_client(PGconn *conn) !SSL_set_app_data(conn->ssl, conn) || !SSL_set_fd(conn->ssl, conn->sock)) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(); + printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not establish SSL connection: %s\n"), + libpq_gettext("could not establish SSL connection: %s\n"), err); SSLerrfree(err); close_SSL(conn); return PGRES_POLLING_FAILED; } + + /* + * Initialize errorMessage to empty. This allows open_client_SSL() to + * detect whether client_cert_cb() has stored a message. + */ + resetPQExpBuffer(&conn->errorMessage); } /* Begin or continue the actual handshake */ return open_client_SSL(conn); @@ -296,9 +251,15 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) #ifdef USE_SSL if (conn->ssl) { + int err; + + /* SSL_read can write to the socket, so we need to disable SIGPIPE */ + DISABLE_SIGPIPE(return -1); + rloop: n = SSL_read(conn->ssl, ptr, len); - switch (SSL_get_error(conn->ssl, n)) + err = SSL_get_error(conn->ssl, n); + switch (err) { case SSL_ERROR_NONE: break; @@ -308,11 +269,10 @@ rloop: case SSL_ERROR_WANT_WRITE: /* - * Returning 0 here would cause caller to wait for - * read-ready, which is not correct since what SSL wants - * is wait for write-ready. The former could get us stuck - * in an infinite wait, so don't risk it; busy-loop - * instead. + * Returning 0 here would cause caller to wait for read-ready, + * which is not correct since what SSL wants is wait for + * write-ready. The former could get us stuck in an infinite + * wait, so don't risk it; busy-loop instead. */ goto rloop; case SSL_ERROR_SYSCALL: @@ -320,13 +280,16 @@ rloop: char sebuf[256]; if (n == -1) + { + REMEMBER_EPIPE(SOCK_ERRNO == EPIPE); printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL SYSCALL error: %s\n"), - SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + libpq_gettext("SSL SYSCALL error: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + } else { printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL SYSCALL error: EOF detected\n")); + libpq_gettext("SSL SYSCALL error: EOF detected\n")); SOCK_ERRNO_SET(ECONNRESET); n = -1; @@ -335,9 +298,10 @@ rloop: } case SSL_ERROR_SSL: { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(); + printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL error: %s\n"), err); + libpq_gettext("SSL error: %s\n"), err); SSLerrfree(err); } /* fall through */ @@ -347,10 +311,13 @@ rloop: break; default: printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("unrecognized SSL error code\n")); + libpq_gettext("unrecognized SSL error code: %d\n"), + err); n = -1; break; } + + RESTORE_SIGPIPE(); } else #endif @@ -367,19 +334,16 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) { ssize_t n; -#ifdef ENABLE_THREAD_SAFETY - pthread_setspecific(pq_thread_in_send, "t"); -#else -#ifndef WIN32 - pqsigfunc oldsighandler = pqsignal(SIGPIPE, SIG_IGN); -#endif -#endif + DISABLE_SIGPIPE(return -1); #ifdef USE_SSL if (conn->ssl) { + int err; + n = SSL_write(conn->ssl, ptr, len); - switch (SSL_get_error(conn->ssl, n)) + err = SSL_get_error(conn->ssl, n); + switch (err) { case SSL_ERROR_NONE: break; @@ -387,8 +351,8 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) /* * Returning 0 here causes caller to wait for write-ready, - * which is not really the right thing, but it's the best - * we can do. + * which is not really the right thing, but it's the best we + * can do. */ n = 0; break; @@ -400,13 +364,16 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) char sebuf[256]; if (n == -1) + { + REMEMBER_EPIPE(SOCK_ERRNO == EPIPE); printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL SYSCALL error: %s\n"), - SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + libpq_gettext("SSL SYSCALL error: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + } else { printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL SYSCALL error: EOF detected\n")); + libpq_gettext("SSL SYSCALL error: EOF detected\n")); SOCK_ERRNO_SET(ECONNRESET); n = -1; } @@ -414,9 +381,10 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) } case SSL_ERROR_SSL: { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(); + printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL error: %s\n"), err); + libpq_gettext("SSL error: %s\n"), err); SSLerrfree(err); } /* fall through */ @@ -426,22 +394,20 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) break; default: printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("unrecognized SSL error code\n")); + libpq_gettext("unrecognized SSL error code: %d\n"), + err); n = -1; break; } } else #endif + { n = send(conn->sock, ptr, len, 0); + REMEMBER_EPIPE(n < 0 && SOCK_ERRNO == EPIPE); + } -#ifdef ENABLE_THREAD_SAFETY - pthread_setspecific(pq_thread_in_send, "f"); -#else -#ifndef WIN32 - pqsignal(SIGPIPE, oldsighandler); -#endif -#endif + RESTORE_SIGPIPE(); return n; } @@ -450,6 +416,7 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) /* SSL specific code */ /* ------------------------------------------------------------ */ #ifdef USE_SSL + /* * Certificate verification callback * @@ -467,256 +434,91 @@ verify_cb(int ok, X509_STORE_CTX *ctx) return ok; } -#ifdef NOT_USED + /* - * Verify that common name resolves to peer. + * 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. + * + * This is roughly in line with RFC2818, but contrary to what most browsers + * appear to be implementing (point 3 being the difference) + * + * Matching is always cone case-insensitive, since DNS is case insensitive. */ static int -verify_peer(PGconn *conn) +wildcard_certificate_match(const char *pattern, const char *string) { - struct hostent *h = NULL; - struct sockaddr addr; - struct sockaddr_in *sin; - socklen_t len; - char **s; - unsigned long l; - - /* get the address on the other side of the socket */ - len = sizeof(addr); - if (getpeername(conn->sock, &addr, &len) == -1) - { - char sebuf[256]; + int lenpat = strlen(pattern); + int lenstr = strlen(string); - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("error querying socket: %s\n"), - SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); - return -1; - } - - /* weird, but legal case */ - if (addr.sa_family == AF_UNIX) + /* If we don't start with a wildcard, it's not a match (rule 1 & 2) */ + if (lenpat < 3 || + pattern[0] != '*' || + pattern[1] != '.') return 0; - { - struct hostent hpstr; - char buf[BUFSIZ]; - int herrno = 0; - - /* - * Currently, pqGethostbyname() is used only on platforms that - * don't have getaddrinfo(). If you enable this function, - * you should convert the pqGethostbyname() function call to - * use getaddrinfo(). - */ - pqGethostbyname(conn->peer_cn, &hpstr, buf, sizeof(buf), - &h, &herrno); - } - - /* what do we know about the peer's common name? */ - if (h == NULL) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not get information about host (%s): %s\n"), - conn->peer_cn, hstrerror(h_errno)); - return -1; - } - - /* does the address match? */ - switch (addr.sa_family) - { - case AF_INET: - sin = (struct sockaddr_in *) & addr; - for (s = h->h_addr_list; *s != NULL; s++) - { - if (!memcmp(&sin->sin_addr.s_addr, *s, h->h_length)) - return 0; - } - break; - - default: - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("unsupported protocol\n")); - return -1; - } + if (lenpat > lenstr) + /* If pattern is longer than the string, we can never match */ + return 0; - /* - * the prior test should be definitive, but in practice it sometimes - * fails. So we also check the aliases. - */ - for (s = h->h_aliases; *s != NULL; s++) - { - if (pg_strcasecmp(conn->peer_cn, *s) == 0) - 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 */ + return 0; - /* generate protocol-aware error message */ - switch (addr.sa_family) - { - case AF_INET: - sin = (struct sockaddr_in *) & addr; - l = ntohl(sin->sin_addr.s_addr); - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext( - "server common name \"%s\" does not resolve to %ld.%ld.%ld.%ld\n"), - conn->peer_cn, (l >> 24) % 0x100, (l >> 16) % 0x100, - (l >> 8) % 0x100, l % 0x100); - break; - default: - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext( - "server common name \"%s\" does not resolve to peer address\n"), - conn->peer_cn); - } + 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; - return -1; + /* String ended with pattern, and didn't have a dot before, so we match */ + return 1; } -#endif + /* - * Load precomputed DH parameters. - * - * To prevent "downgrade" attacks, we perform a number of checks - * to verify that the DBA-generated DH parameters file contains - * what we expect it to contain. + * Verify that common name resolves to peer. */ -static DH * -load_dh_file(int keylength) +static bool +verify_peer_name_matches_certificate(PGconn *conn) { -#ifdef WIN32 - return NULL; -#else - char pwdbuf[BUFSIZ]; - struct passwd pwdstr; - struct passwd *pwd = NULL; - FILE *fp; - char fnbuf[2048]; - DH *dh = NULL; - int codes; - - if (pqGetpwuid(getuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd) == 0) - return NULL; - - /* attempt to open file. It's not an error if it doesn't exist. */ - snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/dh%d.pem", - pwd->pw_dir, keylength); - - if ((fp = fopen(fnbuf, "r")) == NULL) - return NULL; - -/* flock(fileno(fp), LOCK_SH); */ - dh = PEM_read_DHparams(fp, NULL, NULL, NULL); -/* flock(fileno(fp), LOCK_UN); */ - fclose(fp); - - /* is the prime the correct size? */ - if (dh != NULL && 8 * DH_size(dh) < keylength) - dh = NULL; + /* + * 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) + return true; - /* make sure the DH parameters are usable */ - if (dh != NULL) + if (conn->pghostaddr) { - if (DH_check(dh, &codes)) - return NULL; - if (codes & DH_CHECK_P_NOT_PRIME) - return NULL; - if ((codes & DH_NOT_SUITABLE_GENERATOR) && - (codes & DH_CHECK_P_NOT_SAFE_PRIME)) - return NULL; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("verified SSL connections are only supported when connecting to a hostname")); + return false; } - - return dh; -#endif -} - -/* - * Load hardcoded DH parameters. - * - * To prevent problems if the DH parameters files don't even - * exist, we can load DH parameters hardcoded into this file. - */ -static DH * -load_dh_buffer(const char *buffer, size_t len) -{ - BIO *bio; - DH *dh = NULL; - - bio = BIO_new_mem_buf((char *) buffer, len); - if (bio == NULL) - return NULL; - dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); - BIO_free(bio); - - return dh; -} - -/* - * Generate an empheral DH key. Because this can take a long - * time to compute, we can use precomputed parameters of the - * common key sizes. - * - * Since few sites will bother to precompute these parameter - * files, we also provide a fallback to the parameters provided - * by the OpenSSL project. - * - * These values can be static (once loaded or computed) since - * the OpenSSL library can efficiently generate random keys from - * the information provided. - */ -static DH * -tmp_dh_cb(SSL *s, int is_export, int keylength) -{ - DH *r = NULL; - static DH *dh = NULL; - static DH *dh512 = NULL; - static DH *dh1024 = NULL; - static DH *dh2048 = NULL; - static DH *dh4096 = NULL; - - switch (keylength) + else { - case 512: - if (dh512 == NULL) - dh512 = load_dh_file(keylength); - if (dh512 == NULL) - dh512 = load_dh_buffer(file_dh512, sizeof file_dh512); - r = dh512; - break; - - case 1024: - if (dh1024 == NULL) - dh1024 = load_dh_file(keylength); - if (dh1024 == NULL) - dh1024 = load_dh_buffer(file_dh1024, sizeof file_dh1024); - r = dh1024; - break; - - case 2048: - if (dh2048 == NULL) - dh2048 = load_dh_file(keylength); - if (dh2048 == NULL) - dh2048 = load_dh_buffer(file_dh2048, sizeof file_dh2048); - r = dh2048; - break; - - case 4096: - if (dh4096 == NULL) - dh4096 = load_dh_file(keylength); - if (dh4096 == NULL) - dh4096 = load_dh_buffer(file_dh4096, sizeof file_dh4096); - r = dh4096; - break; - - default: - if (dh == NULL) - dh = load_dh_file(keylength); - r = dh; + /* + * Connect by hostname. + * + * XXX: Should support alternate names here + */ + if (pg_strcasecmp(conn->peer_cn, conn->pghost) == 0) + /* Exact name match */ + return true; + else if (wildcard_certificate_match(conn->peer_cn, conn->pghost)) + /* Matched wildcard certificate */ + return true; + else + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("server common name '%s' does not match hostname '%s'"), + conn->peer_cn, conn->pghost); + return false; + } } - - /* this may take a long time, but it may be necessary... */ - if (r == NULL || 8 * DH_size(r) < keylength) - r = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL); - - return r; } /* @@ -724,120 +526,211 @@ tmp_dh_cb(SSL *s, int is_export, int keylength) * This callback is only called when the server wants a * client cert. * - * Returns 1 on success, 0 on no data, -1 on error. + * Since BIO functions can set OpenSSL error codes, we must + * reset the OpenSSL error stack on *every* exit from this + * function once we've started using BIO. + * + * Must return 1 on success, 0 on no data or error. */ static int client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) { -#ifdef WIN32 - return 0; -#else - char pwdbuf[BUFSIZ]; - struct passwd pwdstr; - struct passwd *pwd = NULL; - struct stat buf, - buf2; - char fnbuf[2048]; + char homedir[MAXPGPATH]; + struct stat buf; + +#ifndef WIN32 + struct stat buf2; FILE *fp; +#endif + char fnbuf[MAXPGPATH]; + BIO *bio; PGconn *conn = (PGconn *) SSL_get_app_data(ssl); - int (*cb) () = NULL; /* how to read user password */ char sebuf[256]; - - if (pqGetpwuid(getuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd) == 0) + if (!pqGetHomeDirectory(homedir, sizeof(homedir))) { printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not get user information\n")); - return -1; + libpq_gettext("could not get user information\n")); + return 0; } /* read the user certificate */ - snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.crt", - pwd->pw_dir); - if (stat(fnbuf, &buf) == -1) - return 0; - if ((fp = fopen(fnbuf, "r")) == NULL) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not open certificate (%s): %s\n"), - fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); - return -1; - } - if (PEM_read_X509(fp, x509, NULL, NULL) == NULL) + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE); + + /* + * OpenSSL <= 0.9.8 lacks error stack handling, which means it's likely to + * report wrong error messages if access to the cert file fails. Do our + * own check for the readability of the file to catch the majority of such + * problems before OpenSSL gets involved. + */ +#ifndef HAVE_ERR_SET_MARK { - char *err = SSLerrmessage(); - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not read certificate (%s): %s\n"), - fnbuf, err); - SSLerrfree(err); - fclose(fp); - return -1; + FILE *fp2; + + if ((fp2 = fopen(fnbuf, "r")) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not open certificate file \"%s\": %s\n"), + fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); + return 0; + } + fclose(fp2); } - fclose(fp); +#endif - /* read the user key */ - snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.key", - pwd->pw_dir); - if (stat(fnbuf, &buf) == -1) + /* save OpenSSL error stack */ + ERR_set_mark(); + + if ((bio = BIO_new_file(fnbuf, "r")) == NULL) { printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("certificate present, but not private key (%s)\n"), - fnbuf); - X509_free(*x509); + libpq_gettext("could not open certificate file \"%s\": %s\n"), + fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); + ERR_pop_to_mark(); return 0; } - if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) || - buf.st_uid != getuid()) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("private key (%s) has wrong permissions\n"), fnbuf); - X509_free(*x509); - return -1; - } - if ((fp = fopen(fnbuf, "r")) == NULL) + + if (PEM_read_bio_X509(bio, x509, NULL, NULL) == NULL) { + char *err = SSLerrmessage(); + printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not open private key file (%s): %s\n"), - fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); - X509_free(*x509); - return -1; + libpq_gettext("could not read certificate file \"%s\": %s\n"), + fnbuf, err); + SSLerrfree(err); + BIO_free(bio); + ERR_pop_to_mark(); + return 0; } - if (fstat(fileno(fp), &buf2) == -1 || - buf.st_dev != buf2.st_dev || buf.st_ino != buf2.st_ino) + + BIO_free(bio); + +#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) && !defined(OPENSSL_NO_ENGINE) + if (getenv("PGSSLKEY")) { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("private key (%s) changed during execution\n"), fnbuf); - X509_free(*x509); - return -1; + /* read the user key from engine */ + char *engine_env = getenv("PGSSLKEY"); + char *engine_colon = strchr(engine_env, ':'); + char *engine_str; + ENGINE *engine_ptr; + + if (!engine_colon) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid value of PGSSLKEY environment variable\n")); + ERR_pop_to_mark(); + return 0; + } + + engine_str = malloc(engine_colon - engine_env + 1); + strlcpy(engine_str, engine_env, engine_colon - engine_env + 1); + engine_ptr = ENGINE_by_id(engine_str); + if (engine_ptr == 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; + } + + *pkey = ENGINE_load_private_key(engine_ptr, engine_colon + 1, + NULL, NULL); + if (*pkey == NULL) + { + char *err = SSLerrmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"), + engine_colon + 1, engine_str, err); + SSLerrfree(err); + free(engine_str); + ERR_pop_to_mark(); + return 0; + } + free(engine_str); } - if (PEM_read_PrivateKey(fp, pkey, cb, NULL) == NULL) + else +#endif /* use PGSSLKEY */ { - char *err = SSLerrmessage(); - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not read private key (%s): %s\n"), - fnbuf, err); - SSLerrfree(err); - X509_free(*x509); - fclose(fp); - return -1; + /* read the user key from file */ + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE); + if (stat(fnbuf, &buf) != 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate present, but not private key file \"%s\"\n"), + fnbuf); + ERR_pop_to_mark(); + return 0; + } +#ifndef WIN32 + 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"), + fnbuf); + ERR_pop_to_mark(); + return 0; + } +#endif + + if ((bio = BIO_new_file(fnbuf, "r")) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not open private key file \"%s\": %s\n"), + fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); + ERR_pop_to_mark(); + return 0; + } +#ifndef WIN32 + BIO_get_fp(bio, &fp); + if (fstat(fileno(fp), &buf2) == -1 || + buf.st_dev != buf2.st_dev || buf.st_ino != buf2.st_ino) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("private key file \"%s\" changed during execution\n"), fnbuf); + ERR_pop_to_mark(); + return 0; + } +#endif + + if (PEM_read_bio_PrivateKey(bio, pkey, NULL, NULL) == NULL) + { + char *err = SSLerrmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read private key file \"%s\": %s\n"), + fnbuf, err); + SSLerrfree(err); + + BIO_free(bio); + ERR_pop_to_mark(); + return 0; + } + + BIO_free(bio); } - fclose(fp); /* verify that the cert and key go together */ if (!X509_check_private_key(*x509, *pkey)) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(); + printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("certificate/private key mismatch (%s): %s\n"), + libpq_gettext("certificate does not match private key file \"%s\": %s\n"), fnbuf, err); SSLerrfree(err); - X509_free(*x509); - EVP_PKEY_free(*pkey); - return -1; + ERR_pop_to_mark(); + return 0; } + ERR_pop_to_mark(); + return 1; -#endif } #ifdef ENABLE_THREAD_SAFETY @@ -845,69 +738,98 @@ client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) static unsigned long pq_threadidcallback(void) { - return (unsigned long)pthread_self(); + /* + * This is not standards-compliant. pthread_self() returns pthread_t, and + * shouldn't be cast to unsigned long, but CRYPTO_set_id_callback requires + * it, so we have to do it. + */ + return (unsigned long) pthread_self(); } static pthread_mutex_t *pq_lockarray; + static void pq_lockingcallback(int mode, int n, const char *file, int line) { - if (mode & CRYPTO_LOCK) { - pthread_mutex_lock(&pq_lockarray[n]); - } else { - pthread_mutex_unlock(&pq_lockarray[n]); + if (mode & CRYPTO_LOCK) + { + if (pthread_mutex_lock(&pq_lockarray[n])) + PGTHREAD_ERROR("failed to lock mutex"); + } + else + { + if (pthread_mutex_unlock(&pq_lockarray[n])) + PGTHREAD_ERROR("failed to unlock mutex"); } } +#endif /* ENABLE_THREAD_SAFETY */ -#endif /* ENABLE_THREAD_SAFETY */ - +/* + * Also see similar code in fe-connect.c, default_threadlock() + */ static int init_ssl_system(PGconn *conn) { #ifdef ENABLE_THREAD_SAFETY #ifndef WIN32 - static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; + static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; #else static pthread_mutex_t init_mutex = NULL; static long mutex_initlock = 0; - if (init_mutex == NULL) { - while(InterlockedExchange(&mutex_initlock, 1) == 1) - /* loop, another thread own the lock */ ; + if (init_mutex == NULL) + { + while (InterlockedExchange(&mutex_initlock, 1) == 1) + /* loop, another thread own the lock */ ; if (init_mutex == NULL) - pthread_mutex_init(&init_mutex, NULL); - InterlockedExchange(&mutex_initlock,0); + { + if (pthread_mutex_init(&init_mutex, NULL)) + return -1; + } + InterlockedExchange(&mutex_initlock, 0); } #endif - pthread_mutex_lock(&init_mutex); - - if (pq_initssllib && pq_lockarray == NULL) { - int i; + if (pthread_mutex_lock(&init_mutex)) + return -1; + + if (pq_initssllib && pq_lockarray == NULL) + { + int i; + CRYPTO_set_id_callback(pq_threadidcallback); - pq_lockarray = malloc(sizeof(pthread_mutex_t)*CRYPTO_num_locks()); - if (!pq_lockarray) { + pq_lockarray = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks()); + if (!pq_lockarray) + { pthread_mutex_unlock(&init_mutex); return -1; } - for (i=0;i= 0x00907000L + OPENSSL_config(NULL); +#endif SSL_library_init(); SSL_load_error_strings(); } SSL_context = SSL_CTX_new(TLSv1_method()); if (!SSL_context) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(); + printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not create SSL context: %s\n"), + libpq_gettext("could not create SSL context: %s\n"), err); SSLerrfree(err); #ifdef ENABLE_THREAD_SAFETY @@ -921,63 +843,91 @@ init_ssl_system(PGconn *conn) #endif return 0; } + /* * Initialize global SSL context. */ static int initialize_SSL(PGconn *conn) { -#ifndef WIN32 struct stat buf; - char pwdbuf[BUFSIZ]; - struct passwd pwdstr; - struct passwd *pwd = NULL; - char fnbuf[2048]; -#endif + char homedir[MAXPGPATH]; + char fnbuf[MAXPGPATH]; - if(init_ssl_system(conn)) + if (init_ssl_system(conn)) return -1; -#ifndef WIN32 - if (pqGetpwuid(getuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd) == 0) + /* + * 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. + */ + + /* Set up to verify server cert, if root.crt is present */ + if (pqGetHomeDirectory(homedir, sizeof(homedir))) { - snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/root.crt", - pwd->pw_dir); - if (stat(fnbuf, &buf) == -1) + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE); + if (stat(fnbuf, &buf) == 0) { - return 0; -#ifdef NOT_USED - char sebuf[256]; + X509_STORE *cvstore; - /* CLIENT CERTIFICATES NOT REQUIRED bjm 2002-09-26 */ - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not read root certificate list (%s): %s\n"), - fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); - return -1; + if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL)) + { + char *err = SSLerrmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read root certificate file \"%s\": %s\n"), + fnbuf, err); + SSLerrfree(err); + return -1; + } + + if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL) + { + /* setting the flags to check against the complete CRL chain */ + if (X509_STORE_load_locations(cvstore, ROOT_CRL_FILE, NULL) != 0) +/* 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); + /* if not found, silently ignore; we do not require CRL */ +#else + { + char *err = SSLerrmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"), + fnbuf); + SSLerrfree(err); + return -1; + } #endif + } + + SSL_CTX_set_verify(SSL_context, SSL_VERIFY_PEER, verify_cb); } - if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, 0)) + else + { + if (strcmp(conn->sslverify, "none") != 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("root certificate file (%s) not found"), fnbuf); + return -1; + } + } + } + else + { + if (strcmp(conn->sslverify, "none") != 0) { - char *err = SSLerrmessage(); printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not read root certificate list (%s): %s\n"), - fnbuf, err); - SSLerrfree(err); + libpq_gettext("cannot find home directory to locate root certificate file")); return -1; } } - SSL_CTX_set_verify(SSL_context, - SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb); - SSL_CTX_set_verify_depth(SSL_context, 1); - - /* set up empheral DH keys */ - SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb); - SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE); - /* set up mechanism to provide client certificate, if available */ SSL_CTX_set_client_cert_cb(SSL_context, client_cert_cb); -#endif return 0; } @@ -1006,7 +956,9 @@ open_client_SSL(PGconn *conn) r = SSL_connect(conn->ssl); if (r <= 0) { - switch (SSL_get_error(conn->ssl, r)) + int err = SSL_get_error(conn->ssl, r); + + switch (err) { case SSL_ERROR_WANT_READ: return PGRES_POLLING_READING; @@ -1020,59 +972,59 @@ open_client_SSL(PGconn *conn) if (r == -1) printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL SYSCALL error: %s\n"), - SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + libpq_gettext("SSL SYSCALL error: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); else printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL SYSCALL error: EOF detected\n")); + libpq_gettext("SSL SYSCALL error: EOF detected\n")); close_SSL(conn); return PGRES_POLLING_FAILED; } case SSL_ERROR_SSL: { - char *err = SSLerrmessage(); - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SSL error: %s\n"), err); - SSLerrfree(err); + /* + * If there are problems with the local certificate files, + * these will be detected by client_cert_cb() which is + * called from SSL_connect(). We want to return that + * error message and not the rather unhelpful error that + * OpenSSL itself returns. So check to see if an error + * message was already stored. + */ + if (conn->errorMessage.len == 0) + { + char *err = SSLerrmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), + err); + SSLerrfree(err); + } close_SSL(conn); return PGRES_POLLING_FAILED; } default: printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("unrecognized SSL error code\n")); + libpq_gettext("unrecognized SSL error code: %d\n"), + err); close_SSL(conn); return PGRES_POLLING_FAILED; } } - /* check the certificate chain of the server */ - -#ifdef NOT_USED - /* CLIENT CERTIFICATES NOT REQUIRED bjm 2002-09-26 */ - /* - * this eliminates simple man-in-the-middle attacks and simple - * impersonations + * We already checked the server certificate in initialize_SSL() + * using SSL_CTX_set_verify() if root.crt exists. */ - r = SSL_get_verify_result(conn->ssl); - if (r != X509_V_OK) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("certificate could not be validated: %s\n"), - X509_verify_cert_error_string(r)); - close_SSL(conn); - return PGRES_POLLING_FAILED; - } -#endif /* pull out server distinguished and common names */ conn->peer = SSL_get_peer_certificate(conn->ssl); if (conn->peer == NULL) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(); + printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("certificate could not be obtained: %s\n"), + libpq_gettext("certificate could not be obtained: %s\n"), err); SSLerrfree(err); close_SSL(conn); @@ -1087,22 +1039,11 @@ open_client_SSL(PGconn *conn) NID_commonName, conn->peer_cn, SM_USER); conn->peer_cn[SM_USER] = '\0'; - /* verify that the common name resolves to peer */ - -#ifdef NOT_USED - /* CLIENT CERTIFICATES NOT REQUIRED bjm 2002-09-26 */ - - /* - * this is necessary to eliminate man-in-the-middle attacks and - * impersonations where the attacker somehow learned the server's - * private key - */ - if (verify_peer(conn) == -1) + if (!verify_peer_name_matches_certificate(conn)) { close_SSL(conn); return PGRES_POLLING_FAILED; } -#endif /* SSL handshake is complete */ return PGRES_POLLING_OK; @@ -1116,9 +1057,13 @@ close_SSL(PGconn *conn) { if (conn->ssl) { + DISABLE_SIGPIPE((void) 0); SSL_shutdown(conn->ssl); SSL_free(conn->ssl); conn->ssl = NULL; + /* We have to assume we got EPIPE */ + REMEMBER_EPIPE(true); + RESTORE_SIGPIPE(); } if (conn->peer) @@ -1135,31 +1080,33 @@ close_SSL(PGconn *conn) * return NULL if it doesn't recognize the error code. We don't * want to return NULL ever. */ -static char ssl_nomem[] = "Out of memory allocating error description"; -#define SSL_ERR_LEN 128 +static char ssl_nomem[] = "out of memory allocating error description"; + +#define SSL_ERR_LEN 128 static char * SSLerrmessage(void) { unsigned long errcode; const char *errreason; - char *errbuf; + char *errbuf; errbuf = malloc(SSL_ERR_LEN); if (!errbuf) return ssl_nomem; errcode = ERR_get_error(); - if (errcode == 0) { - strcpy(errbuf, "No SSL error reported"); + if (errcode == 0) + { + snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("no SSL error reported")); return errbuf; } errreason = ERR_reason_error_string(errcode); - if (errreason != NULL) { - strncpy(errbuf, errreason, SSL_ERR_LEN-1); - errbuf[SSL_ERR_LEN-1] = '\0'; + if (errreason != NULL) + { + strlcpy(errbuf, errreason, SSL_ERR_LEN); return errbuf; } - snprintf(errbuf, SSL_ERR_LEN, "SSL error code %lu", errcode); + snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("SSL error code %lu"), errcode); return errbuf; } @@ -1169,77 +1116,109 @@ SSLerrfree(char *buf) if (buf != ssl_nomem) free(buf); } + /* - * Return pointer to SSL object. + * Return pointer to OpenSSL object. */ -SSL * +void * PQgetssl(PGconn *conn) { if (!conn) return NULL; return conn->ssl; } +#else /* !USE_SSL */ +void * +PQgetssl(PGconn *conn) +{ + return NULL; +} #endif /* USE_SSL */ -#ifdef ENABLE_THREAD_SAFETY -#ifndef WIN32 -/* - * Check SIGPIPE handler and perhaps install our own. - */ -void -pq_check_sigpipe_handler(void) -{ - pthread_key_create(&pq_thread_in_send, NULL); - /* - * Find current pipe handler and chain on to it. - */ - pq_pipe_handler = pqsignalinquire(SIGPIPE); - pqsignal(SIGPIPE, sigpipe_handler_ignore_send); -} +#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32) /* - * Threaded SIGPIPE signal handler + * Block SIGPIPE for this thread. This prevents send()/write() from exiting + * the application. */ -void -sigpipe_handler_ignore_send(int signo) +int +pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending) { - /* - * If we have gotten a SIGPIPE outside send(), chain or - * exit if we are at the end of the chain. - * Synchronous signals are delivered to the thread that - * caused the signal. - */ - if (!PQinSend()) + sigset_t sigpipe_sigset; + sigset_t sigset; + + sigemptyset(&sigpipe_sigset); + sigaddset(&sigpipe_sigset, SIGPIPE); + + /* Block SIGPIPE and save previous mask for later reset */ + SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset)); + if (SOCK_ERRNO) + return -1; + + /* We can have a pending SIGPIPE only if it was blocked before */ + if (sigismember(osigset, SIGPIPE)) { - if (pq_pipe_handler == SIG_DFL) /* not set by application */ - exit(128 + SIGPIPE); /* typical return value for SIG_DFL */ + /* Is there a pending SIGPIPE? */ + if (sigpending(&sigset) != 0) + return -1; + + if (sigismember(&sigset, SIGPIPE)) + *sigpipe_pending = true; else - (*pq_pipe_handler)(signo); /* call original handler */ + *sigpipe_pending = false; } + else + *sigpipe_pending = false; + + return 0; } -#endif -#endif - + /* - * Indicates whether the current thread is in send() - * For use by SIGPIPE signal handlers; they should - * ignore SIGPIPE when libpq is in send(). This means - * that the backend has died unexpectedly. + * Discard any pending SIGPIPE and reset the signal mask. + * + * Note: we are effectively assuming here that the C library doesn't queue + * up multiple SIGPIPE events. If it did, then we'd accidentally leave + * ours in the queue when an event was already pending and we got another. + * As long as it doesn't queue multiple events, we're OK because the caller + * can't tell the difference. + * + * The caller should say got_epipe = FALSE if it is certain that it + * didn't get an EPIPE error; in that case we'll skip the clear operation + * and things are definitely OK, queuing or no. If it got one or might have + * gotten one, pass got_epipe = TRUE. + * + * We do not want this to change errno, since if it did that could lose + * the error code from a preceding send(). We essentially assume that if + * we were able to do pq_block_sigpipe(), this can't fail. */ -pqbool -PQinSend(void) +void +pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe) { -#ifdef ENABLE_THREAD_SAFETY - return (pthread_getspecific(pq_thread_in_send) /* has it been set? */ && - *(char *)pthread_getspecific(pq_thread_in_send) == 't') ? true : false; -#else - /* - * No threading: our code ignores SIGPIPE around send(). - * Therefore, we can't be in send() if we are checking - * from a SIGPIPE signal handler. - */ - return false; -#endif + int save_errno = SOCK_ERRNO; + int signo; + sigset_t sigset; + + /* Clear SIGPIPE only if none was pending */ + if (got_epipe && !sigpipe_pending) + { + if (sigpending(&sigset) == 0 && + sigismember(&sigset, SIGPIPE)) + { + sigset_t sigpipe_sigset; + + sigemptyset(&sigpipe_sigset); + sigaddset(&sigpipe_sigset, SIGPIPE); + + sigwait(&sigpipe_sigset, &signo); + } + } + + /* Restore saved block mask */ + pthread_sigmask(SIG_SETMASK, osigset, NULL); + + SOCK_ERRNO_SET(save_errno); } + +#endif /* ENABLE_THREAD_SAFETY && !WIN32 */