Index: configure Index: configure.in =================================================================== RCS file: /projects/cvsroot/pgsql/configure.in,v retrieving revision 1.464 diff -c -r1.464 configure.in *** configure.in 29 Apr 2006 20:47:29 -0000 1.464 --- configure.in 4 May 2006 13:35:49 -0000 *************** *** 489,498 **** # AC_MSG_CHECKING([whether to build with OpenSSL support]) PGAC_ARG_BOOL(with, openssl, no, [ --with-openssl build with OpenSSL support], ! [AC_DEFINE([USE_SSL], 1, [Define to build with (Open)SSL support. (--with-openssl)])]) AC_MSG_RESULT([$with_openssl]) AC_SUBST(with_openssl) # # Prefer libedit --- 489,515 ---- # AC_MSG_CHECKING([whether to build with OpenSSL support]) PGAC_ARG_BOOL(with, openssl, no, [ --with-openssl build with OpenSSL support], ! [AC_DEFINE([USE_SSL_OPENSSL], 1, [Define to build with (Open)SSL support. (--with-openssl)])]) AC_MSG_RESULT([$with_openssl]) AC_SUBST(with_openssl) + # + # GnuTLS + # + AC_MSG_CHECKING([whether to build with GnuTLS support]) + PGAC_ARG_BOOL(with, gnutls, no, [ --with-gnutls build with GnuTLS support], + [AC_DEFINE([USE_SSL_GNUTLS], 1, [Define to build with GnuTLS (SSL) support. (--with-gnutls)])]) + AC_MSG_RESULT([$with_gnutls]) + AC_SUBST(with_gnutls) + + if test "$with_openssl" = yes -o "$with_gnutls" = yes ; then + AC_DEFINE([USE_SSL], 1, [Define to build with SSL support (select either OpenSSL or GnuTLS)]) + fi + + if test "$with_openssl" = yes -a "$with_gnutls" = yes ; then + AC_MSG_ERROR([ + *** Cannot select both OpenSSL and GnuTLS.]) + fi # # Prefer libedit *************** *** 692,697 **** --- 709,720 ---- fi fi + if test "$with_gnutls" = yes ; then + AC_CHECK_LIB(gnutls, gnutls_init, [], [AC_MSG_ERROR([library 'gnutls' is required for GnuTLS])]) + # Technically we only need this with --enable-thread-safety + AC_CHECK_LIB(gcrypt, gcry_control, [], [AC_MSG_ERROR([library 'gcrypt' is required for GnuTLS])]) + fi + if test "$with_pam" = yes ; then AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])]) fi *************** *** 774,779 **** --- 797,807 ---- AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file is required for OpenSSL])]) fi + if test "$with_gnutls" = yes ; then + AC_CHECK_HEADER(gnutls/gnutls.h, [], [AC_MSG_ERROR([header file is required for GnuTLS])]) + AC_CHECK_HEADER(gcrypt.h, [], [AC_MSG_ERROR([header file is required for GnuTLS])]) + fi + if test "$with_pam" = yes ; then AC_CHECK_HEADERS(security/pam_appl.h, [], [AC_CHECK_HEADERS(pam/pam_appl.h, [], Index: src/Makefile.global.in =================================================================== RCS file: /projects/cvsroot/pgsql/src/Makefile.global.in,v retrieving revision 1.222 diff -c -r1.222 Makefile.global.in *** src/Makefile.global.in 19 Apr 2006 16:32:08 -0000 1.222 --- src/Makefile.global.in 4 May 2006 13:35:52 -0000 *************** *** 150,155 **** --- 150,156 ---- with_python = @with_python@ with_tcl = @with_tcl@ with_openssl = @with_openssl@ + with_gnutls = @with_gnutls@ with_zlib = @with_zlib@ enable_shared = @enable_shared@ enable_rpath = @enable_rpath@ Index: src/backend/libpq/Makefile =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/libpq/Makefile,v retrieving revision 1.37 diff -c -r1.37 Makefile *** src/backend/libpq/Makefile 29 Nov 2003 19:51:49 -0000 1.37 --- src/backend/libpq/Makefile 4 May 2006 13:35:52 -0000 *************** *** 17,22 **** --- 17,29 ---- OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o md5.o pqcomm.o \ pqformat.o pqsignal.o + ifeq ($(with_openssl),yes) + OBJS += be-secure-openssl.o + endif + + ifeq ($(with_gnutls),yes) + OBJS += be-secure-gnutls.o + endif all: SUBSYS.o Index: src/backend/libpq/be-secure.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/libpq/be-secure.c,v retrieving revision 1.66 diff -c -r1.66 be-secure.c *** src/backend/libpq/be-secure.c 27 Apr 2006 15:35:15 -0000 1.66 --- src/backend/libpq/be-secure.c 4 May 2006 13:35:52 -0000 *************** *** 89,99 **** #include #endif - #ifdef USE_SSL - #include - #include - #endif - #include "libpq/libpq.h" #include "miscadmin.h" #include "tcop/tcopprot.h" --- 89,94 ---- *************** *** 106,131 **** #define SERVER_CERT_FILE "server.crt" #define SERVER_PRIVATE_KEY_FILE "server.key" - 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 int verify_cb(int, X509_STORE_CTX *); - static void info_cb(const SSL *ssl, int type, int args); - static void initialize_SSL(void); - static void destroy_SSL(void); - static int open_server_SSL(Port *); - static void close_SSL(Port *); - static const char *SSLerrmessage(void); - #endif - - #ifdef USE_SSL - /* - * How much data can be sent across a secure connection - * (total in both directions) before we require renegotiation. - */ - #define RENEGOTIATION_LIMIT (512 * 1024 * 1024) - - static SSL_CTX *SSL_context = NULL; #endif /* ------------------------------------------------------------ */ --- 101,106 ---- *************** *** 206,212 **** secure_initialize(void) { #ifdef USE_SSL ! initialize_SSL(); #endif return 0; --- 181,187 ---- secure_initialize(void) { #ifdef USE_SSL ! pgtls_initialize(); #endif return 0; *************** *** 219,225 **** secure_destroy(void) { #ifdef USE_SSL ! destroy_SSL(); #endif } --- 194,200 ---- secure_destroy(void) { #ifdef USE_SSL ! pgtls_destroy(); #endif } *************** *** 232,238 **** int r = 0; #ifdef USE_SSL ! r = open_server_SSL(port); #endif return r; --- 207,213 ---- int r = 0; #ifdef USE_SSL ! r = pgtls_open_server(port); #endif return r; *************** *** 246,252 **** { #ifdef USE_SSL if (port->ssl) ! close_SSL(port); #endif } --- 221,227 ---- { #ifdef USE_SSL if (port->ssl) ! pgtls_close(port); #endif } *************** *** 260,316 **** #ifdef USE_SSL if (port->ssl) ! { ! int err; ! ! rloop: ! n = SSL_read(port->ssl, ptr, len); ! err = SSL_get_error(port->ssl, n); ! switch (err) ! { ! case SSL_ERROR_NONE: ! port->count += n; ! break; ! case SSL_ERROR_WANT_READ: ! case SSL_ERROR_WANT_WRITE: ! #ifdef WIN32 ! pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl), ! (err == SSL_ERROR_WANT_READ) ? ! FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE); ! #endif ! goto rloop; ! case SSL_ERROR_SYSCALL: ! if (n == -1) ! ereport(COMMERROR, ! (errcode_for_socket_access(), ! errmsg("SSL SYSCALL error: %m"))); ! else ! { ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("SSL SYSCALL error: EOF detected"))); ! errno = ECONNRESET; ! n = -1; ! } ! break; ! case SSL_ERROR_SSL: ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("SSL error: %s", SSLerrmessage()))); ! /* fall through */ ! case SSL_ERROR_ZERO_RETURN: ! errno = ECONNRESET; ! n = -1; ! break; ! default: ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("unrecognized SSL error code: %d", ! err))); ! n = -1; ! break; ! } ! } else #endif { --- 235,241 ---- #ifdef USE_SSL if (port->ssl) ! n = pgtls_read(port, ptr, len); else #endif { *************** *** 334,415 **** #ifdef USE_SSL if (port->ssl) ! { ! int err; ! ! if (port->count > RENEGOTIATION_LIMIT) ! { ! SSL_set_session_id_context(port->ssl, (void *) &SSL_context, ! sizeof(SSL_context)); ! if (SSL_renegotiate(port->ssl) <= 0) ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("SSL renegotiation failure"))); ! if (SSL_do_handshake(port->ssl) <= 0) ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("SSL renegotiation failure"))); ! if (port->ssl->state != SSL_ST_OK) ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("SSL failed to send renegotiation request"))); ! port->ssl->state |= SSL_ST_ACCEPT; ! SSL_do_handshake(port->ssl); ! if (port->ssl->state != SSL_ST_OK) ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("SSL renegotiation failure"))); ! port->count = 0; ! } ! ! wloop: ! n = SSL_write(port->ssl, ptr, len); ! err = SSL_get_error(port->ssl, n); ! switch (err) ! { ! case SSL_ERROR_NONE: ! port->count += n; ! break; ! case SSL_ERROR_WANT_READ: ! case SSL_ERROR_WANT_WRITE: ! #ifdef WIN32 ! pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl), ! (err == SSL_ERROR_WANT_READ) ? ! FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE); ! #endif ! goto wloop; ! case SSL_ERROR_SYSCALL: ! if (n == -1) ! ereport(COMMERROR, ! (errcode_for_socket_access(), ! errmsg("SSL SYSCALL error: %m"))); ! else ! { ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("SSL SYSCALL error: EOF detected"))); ! errno = ECONNRESET; ! n = -1; ! } ! break; ! case SSL_ERROR_SSL: ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("SSL error: %s", SSLerrmessage()))); ! /* fall through */ ! case SSL_ERROR_ZERO_RETURN: ! errno = ECONNRESET; ! n = -1; ! break; ! default: ! ereport(COMMERROR, ! (errcode(ERRCODE_PROTOCOL_VIOLATION), ! errmsg("unrecognized SSL error code: %d", ! err))); ! n = -1; ! break; ! } ! } else #endif n = send(port->sock, ptr, len, 0); --- 259,265 ---- #ifdef USE_SSL if (port->ssl) ! n = pgtls_write(port, ptr, len); else #endif n = send(port->sock, ptr, len, 0); *************** *** 417,990 **** return n; } - /* ------------------------------------------------------------ */ - /* SSL specific code */ - /* ------------------------------------------------------------ */ - #ifdef USE_SSL - - /* - * Private substitute BIO: this wraps the SSL library's standard socket BIO - * so that we can enable and disable interrupts just while calling recv(). - * We cannot have interrupts occurring while the bulk of openssl runs, - * because it uses malloc() and possibly other non-reentrant libc facilities. - * - * As of openssl 0.9.7, we can use the reasonably clean method of interposing - * a wrapper around the standard socket BIO's sock_read() method. This relies - * on the fact that sock_read() doesn't call anything non-reentrant, in fact - * not much of anything at all except recv(). If this ever changes we'd - * probably need to duplicate the code of sock_read() in order to push the - * interrupt enable/disable down yet another level. - */ - - static bool my_bio_initialized = false; - static BIO_METHOD my_bio_methods; - static int (*std_sock_read) (BIO *h, char *buf, int size); - - static int - my_sock_read(BIO *h, char *buf, int size) - { - int res; - - prepare_for_client_read(); - - res = std_sock_read(h, buf, size); - - client_read_ended(); - - return res; - } - - static BIO_METHOD * - my_BIO_s_socket(void) - { - if (!my_bio_initialized) - { - memcpy(&my_bio_methods, BIO_s_socket(), sizeof(BIO_METHOD)); - std_sock_read = my_bio_methods.bread; - my_bio_methods.bread = my_sock_read; - my_bio_initialized = true; - } - return &my_bio_methods; - } - - /* This should exactly match openssl's SSL_set_fd except for using my BIO */ - static int - my_SSL_set_fd(SSL *s, int fd) - { - int ret = 0; - BIO *bio = NULL; - - bio = BIO_new(my_BIO_s_socket()); - - if (bio == NULL) - { - SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB); - goto err; - } - BIO_set_fd(bio, fd, BIO_NOCLOSE); - SSL_set_bio(s, bio, bio); - ret = 1; - err: - return ret; - } - - /* - * 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. - */ - static DH * - load_dh_file(int keylength) - { - FILE *fp; - char fnbuf[MAXPGPATH]; - DH *dh = NULL; - int codes; - - /* attempt to open file. It's not an error if it doesn't exist. */ - snprintf(fnbuf, sizeof(fnbuf), "dh%d.pem", 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) - { - elog(LOG, "DH errors (%s): %d bits expected, %d bits found", - fnbuf, keylength, 8 * DH_size(dh)); - dh = NULL; - } - - /* make sure the DH parameters are usable */ - if (dh != NULL) - { - if (DH_check(dh, &codes)) - { - elog(LOG, "DH_check error (%s): %s", fnbuf, SSLerrmessage()); - return NULL; - } - if (codes & DH_CHECK_P_NOT_PRIME) - { - elog(LOG, "DH error (%s): p is not prime", fnbuf); - return NULL; - } - if ((codes & DH_NOT_SUITABLE_GENERATOR) && - (codes & DH_CHECK_P_NOT_SAFE_PRIME)) - { - elog(LOG, - "DH error (%s): neither suitable generator or safe prime", - fnbuf); - return NULL; - } - } - - return dh; - } - - /* - * 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); - if (dh == NULL) - ereport(DEBUG2, - (errmsg_internal("DH load buffer: %s", - SSLerrmessage()))); - 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) - { - 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; - } - - /* this may take a long time, but it may be necessary... */ - if (r == NULL || 8 * DH_size(r) < keylength) - { - ereport(DEBUG2, - (errmsg_internal("DH: generating parameters (%d bits)....", - keylength))); - r = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL); - } - - return r; - } - - /* - * Certificate verification callback - * - * This callback allows us to log intermediate problems during - * verification, but for now we'll see if the final error message - * contains enough information. - * - * This callback also allows us to override the default acceptance - * criteria (e.g., accepting self-signed or expired certs), but - * for now we accept the default checks. - */ - static int - verify_cb(int ok, X509_STORE_CTX *ctx) - { - return ok; - } - - /* - * This callback is used to copy SSL information messages - * into the PostgreSQL log. - */ - static void - info_cb(const SSL *ssl, int type, int args) - { - switch (type) - { - case SSL_CB_HANDSHAKE_START: - ereport(DEBUG4, - (errmsg_internal("SSL: handshake start"))); - break; - case SSL_CB_HANDSHAKE_DONE: - ereport(DEBUG4, - (errmsg_internal("SSL: handshake done"))); - break; - case SSL_CB_ACCEPT_LOOP: - ereport(DEBUG4, - (errmsg_internal("SSL: accept loop"))); - break; - case SSL_CB_ACCEPT_EXIT: - ereport(DEBUG4, - (errmsg_internal("SSL: accept exit (%d)", args))); - break; - case SSL_CB_CONNECT_LOOP: - ereport(DEBUG4, - (errmsg_internal("SSL: connect loop"))); - break; - case SSL_CB_CONNECT_EXIT: - ereport(DEBUG4, - (errmsg_internal("SSL: connect exit (%d)", args))); - break; - case SSL_CB_READ_ALERT: - ereport(DEBUG4, - (errmsg_internal("SSL: read alert (0x%04x)", args))); - break; - case SSL_CB_WRITE_ALERT: - ereport(DEBUG4, - (errmsg_internal("SSL: write alert (0x%04x)", args))); - break; - } - } - - /* - * Initialize global SSL context. - */ - static void - initialize_SSL(void) - { - struct stat buf; - - if (!SSL_context) - { - SSL_library_init(); - SSL_load_error_strings(); - SSL_context = SSL_CTX_new(SSLv23_method()); - if (!SSL_context) - ereport(FATAL, - (errmsg("could not create SSL context: %s", - SSLerrmessage()))); - - /* - * Load and verify certificate and private key - */ - if (!SSL_CTX_use_certificate_file(SSL_context, - SERVER_CERT_FILE, - SSL_FILETYPE_PEM)) - ereport(FATAL, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("could not load server certificate file \"%s\": %s", - SERVER_CERT_FILE, SSLerrmessage()))); - - if (stat(SERVER_PRIVATE_KEY_FILE, &buf) == -1) - ereport(FATAL, - (errcode_for_file_access(), - errmsg("could not access private key file \"%s\": %m", - SERVER_PRIVATE_KEY_FILE))); - - /* - * Require no public access to key file. - * - * XXX temporarily suppress check when on Windows, because there may - * not be proper support for Unix-y file permissions. Need to think - * of a reasonable check to apply on Windows. (See also the data - * directory permission check in postmaster.c) - */ - #if !defined(WIN32) && !defined(__CYGWIN__) - if (!S_ISREG(buf.st_mode) || (buf.st_mode & (S_IRWXG | S_IRWXO)) || - buf.st_uid != geteuid()) - ereport(FATAL, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("unsafe permissions on private key file \"%s\"", - SERVER_PRIVATE_KEY_FILE), - errdetail("File must be owned by the database user and must have no permissions for \"group\" or \"other\"."))); - #endif - - if (!SSL_CTX_use_PrivateKey_file(SSL_context, - SERVER_PRIVATE_KEY_FILE, - SSL_FILETYPE_PEM)) - ereport(FATAL, - (errmsg("could not load private key file \"%s\": %s", - SERVER_PRIVATE_KEY_FILE, SSLerrmessage()))); - - if (!SSL_CTX_check_private_key(SSL_context)) - ereport(FATAL, - (errmsg("check of private key failed: %s", - SSLerrmessage()))); - } - - /* 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 | SSL_OP_NO_SSLv2); - - /* setup the allowed cipher list */ - if (SSL_CTX_set_cipher_list(SSL_context, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH") != 1) - elog(FATAL, "could not set the cipher list (no valid ciphers available)"); - - /* - * Require and check client certificates only if we have a root.crt file. - */ - if (!SSL_CTX_load_verify_locations(SSL_context, ROOT_CERT_FILE, NULL)) - { - /* Not fatal - we do not require client certificates */ - ereport(LOG, - (errmsg("could not load root certificate file \"%s\": %s", - ROOT_CERT_FILE, SSLerrmessage()), - errdetail("Will not verify client certificates."))); - } - else - { - /* - * Check the Certificate Revocation List (CRL) if file exists. - * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html - */ - X509_STORE *cvstore = SSL_CTX_get_cert_store(SSL_context); - - if (cvstore) - { - if (X509_STORE_load_locations(cvstore, ROOT_CRL_FILE, NULL) != 0) - /* setting the flags to check against the complete CRL chain */ - X509_STORE_set_flags(cvstore, - X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); - else - { - /* Not fatal - we do not require CRL */ - ereport(LOG, - (errmsg("SSL Certificate Revocation List (CRL) file \"%s\" not found, skipping: %s", - ROOT_CRL_FILE, SSLerrmessage()), - errdetail("Will not check certificates against CRL."))); - } - } - - SSL_CTX_set_verify(SSL_context, - (SSL_VERIFY_PEER | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT | - SSL_VERIFY_CLIENT_ONCE), - verify_cb); - } - } - - /* - * Destroy global SSL context. - */ - static void - destroy_SSL(void) - { - if (SSL_context) - { - SSL_CTX_free(SSL_context); - SSL_context = NULL; - } - } - - /* - * Attempt to negotiate SSL connection. - */ - static int - open_server_SSL(Port *port) - { - int r; - int err; - - Assert(!port->ssl); - Assert(!port->peer); - - if (!(port->ssl = SSL_new(SSL_context))) - { - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("could not initialize SSL connection: %s", - SSLerrmessage()))); - close_SSL(port); - return -1; - } - if (!my_SSL_set_fd(port->ssl, port->sock)) - { - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("could not set SSL socket: %s", - SSLerrmessage()))); - close_SSL(port); - return -1; - } - - aloop: - r = SSL_accept(port->ssl); - if (r <= 0) - { - err = SSL_get_error(port->ssl, r); - switch (err) - { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - #ifdef WIN32 - pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl), - (err == SSL_ERROR_WANT_READ) ? - FD_READ | FD_CLOSE | FD_ACCEPT : FD_WRITE | FD_CLOSE); - #endif - goto aloop; - case SSL_ERROR_SYSCALL: - if (r < 0) - ereport(COMMERROR, - (errcode_for_socket_access(), - errmsg("could not accept SSL connection: %m"))); - else - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("could not accept SSL connection: EOF detected"))); - break; - case SSL_ERROR_SSL: - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("could not accept SSL connection: %s", - SSLerrmessage()))); - break; - case SSL_ERROR_ZERO_RETURN: - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("could not accept SSL connection: EOF detected"))); - break; - default: - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("unrecognized SSL error code: %d", - err))); - break; - } - close_SSL(port); - return -1; - } - - port->count = 0; - - /* get client certificate, if available. */ - port->peer = SSL_get_peer_certificate(port->ssl); - if (port->peer == NULL) - { - strncpy(port->peer_dn, "(anonymous)", sizeof(port->peer_dn)); - strncpy(port->peer_cn, "(anonymous)", sizeof(port->peer_cn)); - } - else - { - X509_NAME_oneline(X509_get_subject_name(port->peer), - port->peer_dn, sizeof(port->peer_dn)); - port->peer_dn[sizeof(port->peer_dn) - 1] = '\0'; - X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer), - NID_commonName, port->peer_cn, sizeof(port->peer_cn)); - port->peer_cn[sizeof(port->peer_cn) - 1] = '\0'; - } - ereport(DEBUG2, - (errmsg("SSL connection from \"%s\"", port->peer_cn))); - - /* set up debugging/info callback */ - SSL_CTX_set_info_callback(SSL_context, info_cb); - - return 0; - } - - /* - * Close SSL connection. - */ - static void - close_SSL(Port *port) - { - if (port->ssl) - { - SSL_shutdown(port->ssl); - SSL_free(port->ssl); - port->ssl = NULL; - } - - if (port->peer) - { - X509_free(port->peer); - port->peer = NULL; - } - } - - /* - * Obtain reason string for last SSL error - * - * Some caution is needed here since ERR_reason_error_string will - * return NULL if it doesn't recognize the error code. We don't - * want to return NULL ever. - */ - static const char * - SSLerrmessage(void) - { - unsigned long errcode; - const char *errreason; - static char errbuf[32]; - - errcode = ERR_get_error(); - if (errcode == 0) - return "No SSL error reported"; - errreason = ERR_reason_error_string(errcode); - if (errreason != NULL) - return errreason; - snprintf(errbuf, sizeof(errbuf), "SSL error code %lu", errcode); - return errbuf; - } - - #endif /* USE_SSL */ --- 267,269 ---- Index: src/bin/psql/Makefile =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/Makefile,v retrieving revision 1.57 diff -c -r1.57 Makefile *** src/bin/psql/Makefile 5 Mar 2006 15:58:51 -0000 1.57 --- src/bin/psql/Makefile 4 May 2006 13:35:52 -0000 *************** *** 27,32 **** --- 27,34 ---- FLEXFLAGS = -Cfe + # Remove libs not needed for psql as they may conflict with libpq + LIBS := $(filter-out -lz -lgnutls -lgcrypt -lssl -lcrypto, $(LIBS)) all: submake-libpq submake-libpgport submake-backend psql Index: src/bin/psql/command.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/command.c,v retrieving revision 1.166 diff -c -r1.166 command.c *** src/bin/psql/command.c 2 Apr 2006 20:08:22 -0000 1.166 --- src/bin/psql/command.c 4 May 2006 13:35:52 -0000 *************** *** 800,805 **** --- 800,818 ---- } free(opt0); } + + /* \ssl -- Display SSL connection info */ + else if (strcmp(cmd, "ssl") == 0 ) + { + PGresult *res = PQgettlsinfo(pset.db); + if( !res ) + printf(gettext("No SSL available")); + else + { + printQuery( res, &pset.popt, stdout, NULL ); + PQclear(res); + } + } /* \t -- turn off headers and row count */ else if (strcmp(cmd, "t") == 0) Index: src/bin/psql/help.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/help.c,v retrieving revision 1.110 diff -c -r1.110 help.c *** src/bin/psql/help.c 5 Mar 2006 15:58:51 -0000 1.110 --- src/bin/psql/help.c 4 May 2006 13:35:52 -0000 *************** *** 199,204 **** --- 199,205 ---- #ifdef USE_READLINE fprintf(output, _(" \\s [FILE] display history or save it to file\n")); #endif + fprintf(output, _(" \\ssl display SSL information\n")); fprintf(output, _(" \\w FILE write query buffer to file\n")); fprintf(output, "\n"); Index: src/bin/psql/startup.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/startup.c,v retrieving revision 1.132 diff -c -r1.132 startup.c *** src/bin/psql/startup.c 27 Apr 2006 02:58:08 -0000 1.132 --- src/bin/psql/startup.c 4 May 2006 13:35:52 -0000 *************** *** 8,16 **** #include "postgres_fe.h" #include - #ifdef USE_SSL - #include - #endif #ifndef WIN32 #include --- 8,13 ---- *************** *** 89,97 **** static void process_psqlrc_file(char *filename); static void showVersion(void); - #ifdef USE_SSL static void printSSLInfo(void); - #endif #ifdef WIN32 static void --- 86,92 ---- *************** *** 358,366 **** pset.progname, client_ver / 10000, (client_ver / 100) % 100); - #ifdef USE_SSL printSSLInfo(); - #endif #ifdef WIN32 checkWin32Codepage(); #endif --- 353,359 ---- *************** *** 721,748 **** } /* * printSSLInfo * * Prints information about the current SSL connection, if SSL is in use */ - #ifdef USE_SSL static void printSSLInfo(void) { ! int sslbits = -1; ! SSL *ssl; ! ! ssl = PQgetssl(pset.db); ! if (!ssl) ! return; /* no SSL */ ! ! SSL_get_cipher_bits(ssl, &sslbits); ! printf(_("SSL connection (cipher: %s, bits: %i)\n\n"), ! SSL_get_cipher(ssl), sslbits); } - #endif --- 714,755 ---- } + /* getSSLinfostring + * + * Gets a string from the returned SSL info + */ + static const char * + getSSLinfostring( PGresult *sslinfo, const char *key ) + { + int i; + for( i=0; i #endif ! #ifdef USE_SSL #include #include #endif #ifdef HAVE_NETINET_TCP_H #include --- 21,35 ---- #ifdef HAVE_SYS_TIME_H #include #endif ! #ifdef USE_SSL_OPENSSL #include #include + typedef SSL *pg_sslcontext; + typedef X509 *pg_sslcert; + #elif USE_SSL_GNUTLS + #include + typedef gnutls_session pg_sslcontext; + typedef const gnutls_datum *pg_sslcert; #endif #ifdef HAVE_NETINET_TCP_H #include *************** *** 100,107 **** * SSL structures */ #ifdef USE_SSL ! SSL *ssl; ! X509 *peer; char peer_dn[128 + 1]; char peer_cn[SM_USER + 1]; unsigned long count; --- 106,113 ---- * SSL structures */ #ifdef USE_SSL ! pg_sslcontext ssl; ! pg_sslcert peer; char peer_dn[128 + 1]; char peer_cn[SM_USER + 1]; unsigned long count; Index: src/include/libpq/libpq.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/libpq/libpq.h,v retrieving revision 1.66 diff -c -r1.66 libpq.h *** src/include/libpq/libpq.h 5 Mar 2006 15:58:56 -0000 1.66 --- src/include/libpq/libpq.h 4 May 2006 13:35:53 -0000 *************** *** 74,77 **** --- 74,95 ---- extern ssize_t secure_read(Port *port, void *ptr, size_t len); extern ssize_t secure_write(Port *port, void *ptr, size_t len); + #ifdef USE_SSL + /* + * prototypes for functions that implement the TLS layer + */ + #define ROOT_CERT_FILE "root.crt" + #define ROOT_CRL_FILE "root.crl" + #define SERVER_CERT_FILE "server.crt" + #define SERVER_PRIVATE_KEY_FILE "server.key" + + extern void pgtls_initialize(void); + extern void pgtls_destroy(void); + extern int pgtls_open_server(Port *); + extern void pgtls_close(Port *); + extern ssize_t pgtls_read(Port *port, void *ptr, size_t len); + extern ssize_t pgtls_write(Port *port, void *ptr, size_t len); + + #endif + #endif /* LIBPQ_H */ Index: src/interfaces/libpq/Makefile =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/Makefile,v retrieving revision 1.144 diff -c -r1.144 Makefile *** src/interfaces/libpq/Makefile 28 Apr 2006 02:53:20 -0000 1.144 --- src/interfaces/libpq/Makefile 4 May 2006 13:35:53 -0000 *************** *** 36,41 **** --- 36,49 ---- md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \ $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o, $(LIBOBJS)) + ifeq ($(with_openssl),yes) + OBJS += fe-secure-openssl.o + endif + + ifeq ($(with_gnutls),yes) + OBJS += fe-secure-gnutls.o + endif + ifeq ($(PORTNAME), cygwin) override shlib = cyg$(NAME)$(DLSUFFIX) endif *************** *** 57,71 **** # shared library link. (The order in which you list them here doesn't # matter.) ifneq ($(PORTNAME), win32) ! SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl, $(LIBS)) $(PTHREAD_LIBS) else ! SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS)) endif ifeq ($(PORTNAME), win32) SHLIB_LINK += -lshfolder -lwsock32 -lws2_32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32, $(LIBS)) endif - all: def-files $(srcdir)/libpq.rc all-lib # Shared library stuff --- 65,78 ---- # shared library link. (The order in which you list them here doesn't # matter.) ifneq ($(PORTNAME), win32) ! SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl -lgnutls -lgcrypt, $(LIBS)) $(PTHREAD_LIBS) else ! SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl -lgnutls -lgcrypt $(PTHREAD_LIBS), $(LIBS)) endif ifeq ($(PORTNAME), win32) SHLIB_LINK += -lshfolder -lwsock32 -lws2_32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32, $(LIBS)) endif all: def-files $(srcdir)/libpq.rc all-lib # Shared library stuff Index: src/interfaces/libpq/exports.txt =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v retrieving revision 1.7 diff -c -r1.7 exports.txt *** src/interfaces/libpq/exports.txt 26 Dec 2005 14:58:05 -0000 1.7 --- src/interfaces/libpq/exports.txt 4 May 2006 13:35:53 -0000 *************** *** 126,128 **** --- 126,130 ---- PQinitSSL 124 PQregisterThreadLock 125 PQencryptPassword 126 + PQgettlsinfo 127 + PQsetPassthrough 128 Index: src/interfaces/libpq/fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.182 diff -c -r1.182 fe-exec.c *** src/interfaces/libpq/fe-exec.c 14 Mar 2006 22:48:23 -0000 1.182 --- src/interfaces/libpq/fe-exec.c 4 May 2006 13:35:53 -0000 *************** *** 863,868 **** --- 863,875 ---- /* clear the error string */ resetPQExpBuffer(&conn->errorMessage); + /* Don't try to send if the user has taken the connection */ + if (conn->status == CONNECTION_PASSTHROUGH) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("connection is in passthrough mode\n")); + return false; + } /* Don't try to send if we know there's no live connection. */ if (conn->status != CONNECTION_OK) { *************** *** 1091,1096 **** --- 1098,1110 ---- if (!conn) return 0; + /* Not possible in passthrough mode */ + if (conn->status == CONNECTION_PASSTHROUGH) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("connection is in passthrough mode\n")); + return false; + } /* * for non-blocking connections try to flush the send-queue, otherwise we * may never get a response for something that may not have already been *************** *** 1788,1793 **** --- 1802,1843 ---- return pqEndcopy2(conn); } + /* + * PQsetPassthrough + * Once a connection has been established to the server, using + * this function it is possible to take connection and + * communicate to the server directly. If true is returned, the + * state variable points to a filled in structure. The + * functions read and write will send and receive data from the + * server. The function PQsetnonblocking has effect. If true + * these functions will return appropriate states. + * + * It is no possible to return to normal query state from here. + * The connection state will be CONNECTION_PASSTHROUGH. The + * only way out is to close the connection. + * + * RETURNS + * true if passthrough has been enabled + * + * *state points to a newly allocated structure with the + * necessary information. This pointer must be freed using + * PQfreemem when no longer needed. + */ + int + PQsetPassthrough(PGconn *conn, PQpassthrough **state ) + { + if( !state ) + return false; + + if(!PQsendQueryStart(conn)) + return false; + + *state = (PQpassthrough *)malloc(sizeof(PQpassthrough)); + (*state)->len = sizeof(PQpassthrough); + pqsecure_pt_setup(*state); + conn->status = CONNECTION_PASSTHROUGH; + return true; + } /* ---------------- * PQfn - Send a function call to the POSTGRES backend. Index: src/interfaces/libpq/fe-misc.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v retrieving revision 1.125 diff -c -r1.125 fe-misc.c *** src/interfaces/libpq/fe-misc.c 5 Mar 2006 15:59:09 -0000 1.125 --- src/interfaces/libpq/fe-misc.c 4 May 2006 13:35:53 -0000 *************** *** 969,975 **** #ifdef USE_SSL /* Check for SSL library buffering read bytes */ ! if (forRead && conn->ssl && SSL_pending(conn->ssl) > 0) { /* short-circuit the select */ return 1; --- 969,975 ---- #ifdef USE_SSL /* Check for SSL library buffering read bytes */ ! if (forRead && conn->ssl && pqsecure_pending(conn) > 0) { /* short-circuit the select */ return 1; Index: src/interfaces/libpq/fe-secure.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v retrieving revision 1.79 diff -c -r1.79 fe-secure.c *** src/interfaces/libpq/fe-secure.c 27 Apr 2006 14:02:36 -0000 1.79 --- src/interfaces/libpq/fe-secure.c 4 May 2006 13:35:53 -0000 *************** *** 114,156 **** #include "strdup.h" #endif ! #ifdef USE_SSL ! #include ! #endif /* USE_SSL */ ! ! ! #ifdef USE_SSL ! ! #ifndef WIN32 ! #define USER_CERT_FILE ".postgresql/postgresql.crt" ! #define USER_KEY_FILE ".postgresql/postgresql.key" ! #define ROOT_CERT_FILE ".postgresql/root.crt" ! #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" ! #endif ! ! #ifdef NOT_USED ! static int verify_peer(PGconn *); ! #endif ! 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 *); ! static void destroy_SSL(void); ! static PostgresPollingStatusType open_client_SSL(PGconn *); ! static void close_SSL(PGconn *); ! static char *SSLerrmessage(void); ! static void SSLerrfree(char *buf); ! #endif - #ifdef USE_SSL - static bool pq_initssllib = true; ! static SSL_CTX *SSL_context = NULL; ! #endif /* ------------------------------------------------------------ */ /* Procedures common to all secure sessions */ --- 114,123 ---- #include "strdup.h" #endif ! #include "libpq-tls.h" ! char pqTLS_ssl_nomem[] = "Out of memory allocating error description"; /* ------------------------------------------------------------ */ /* Procedures common to all secure sessions */ *************** *** 158,175 **** /* - * Exported function to allow application to tell us it's already - * initialized OpenSSL. - */ - void - PQinitSSL(int do_init) - { - #ifdef USE_SSL - pq_initssllib = do_init; - #endif - } - - /* * Initialize global context */ int --- 125,130 ---- *************** *** 178,184 **** int r = 0; #ifdef USE_SSL ! r = initialize_SSL(conn); #endif return r; --- 133,139 ---- int r = 0; #ifdef USE_SSL ! r = pqTLS_initialize(conn); #endif return r; *************** *** 191,197 **** pqsecure_destroy(void) { #ifdef USE_SSL ! destroy_SSL(); #endif } --- 146,152 ---- pqsecure_destroy(void) { #ifdef USE_SSL ! pqTLS_destroy(); #endif } *************** *** 205,232 **** /* First time through? */ if (conn->ssl == NULL) { ! if (!(conn->ssl = SSL_new(SSL_context)) || ! !SSL_set_app_data(conn->ssl, conn) || ! !SSL_set_fd(conn->ssl, conn->sock)) ! { ! char *err = SSLerrmessage(); ! ! printfPQExpBuffer(&conn->errorMessage, ! 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); #else /* shouldn't get here */ return PGRES_POLLING_FAILED; --- 160,171 ---- /* First time through? */ if (conn->ssl == NULL) { ! PostgresPollingStatusType status = pqTLS_initconn(conn); ! if( status != PGRES_POLLING_OK ) ! return status; } /* Begin or continue the actual handshake */ ! return pqTLS_open_client(conn); #else /* shouldn't get here */ return PGRES_POLLING_FAILED; *************** *** 241,247 **** { #ifdef USE_SSL if (conn->ssl) ! close_SSL(conn); #endif } --- 180,186 ---- { #ifdef USE_SSL if (conn->ssl) ! pqTLS_close(conn); #endif } *************** *** 255,321 **** #ifdef USE_SSL if (conn->ssl) ! { ! int err; ! ! rloop: ! n = SSL_read(conn->ssl, ptr, len); ! err = SSL_get_error(conn->ssl, n); ! switch (err) ! { ! case SSL_ERROR_NONE: ! break; ! case SSL_ERROR_WANT_READ: ! n = 0; ! break; ! 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. ! */ ! goto rloop; ! case SSL_ERROR_SYSCALL: ! { ! char sebuf[256]; ! ! if (n == -1) ! printfPQExpBuffer(&conn->errorMessage, ! 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")); ! ! SOCK_ERRNO_SET(ECONNRESET); ! n = -1; ! } ! break; ! } ! case SSL_ERROR_SSL: ! { ! char *err = SSLerrmessage(); ! ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("SSL error: %s\n"), err); ! SSLerrfree(err); ! } ! /* fall through */ ! case SSL_ERROR_ZERO_RETURN: ! SOCK_ERRNO_SET(ECONNRESET); ! n = -1; ! break; ! default: ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("unrecognized SSL error code: %d\n"), ! err); ! n = -1; ! break; ! } ! } else #endif n = recv(conn->sock, ptr, len, 0); --- 194,200 ---- #ifdef USE_SSL if (conn->ssl) ! n = pqTLS_read( conn, ptr, len ); else #endif n = recv(conn->sock, ptr, len, 0); *************** *** 331,341 **** { ssize_t n; #ifndef WIN32 #ifdef ENABLE_THREAD_SAFETY sigset_t osigmask; bool sigpipe_pending; - bool got_epipe = false; if (pq_block_sigpipe(&osigmask, &sigpipe_pending) < 0) --- 210,220 ---- { ssize_t n; + bool got_epipe = false; #ifndef WIN32 #ifdef ENABLE_THREAD_SAFETY sigset_t osigmask; bool sigpipe_pending; if (pq_block_sigpipe(&osigmask, &sigpipe_pending) < 0) *************** *** 347,417 **** #ifdef USE_SSL if (conn->ssl) ! { ! int err; ! ! n = SSL_write(conn->ssl, ptr, len); ! err = SSL_get_error(conn->ssl, n); ! switch (err) ! { ! case SSL_ERROR_NONE: ! break; ! case SSL_ERROR_WANT_READ: ! ! /* ! * 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. ! */ ! n = 0; ! break; ! case SSL_ERROR_WANT_WRITE: ! n = 0; ! break; ! case SSL_ERROR_SYSCALL: ! { ! char sebuf[256]; ! ! if (n == -1) ! { ! #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32) ! if (SOCK_ERRNO == EPIPE) ! got_epipe = true; ! #endif ! printfPQExpBuffer(&conn->errorMessage, ! 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")); ! SOCK_ERRNO_SET(ECONNRESET); ! n = -1; ! } ! break; ! } ! case SSL_ERROR_SSL: ! { ! char *err = SSLerrmessage(); ! ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("SSL error: %s\n"), err); ! SSLerrfree(err); ! } ! /* fall through */ ! case SSL_ERROR_ZERO_RETURN: ! SOCK_ERRNO_SET(ECONNRESET); ! n = -1; ! break; ! default: ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("unrecognized SSL error code: %d\n"), ! err); ! n = -1; ! break; ! } ! } else #endif { --- 226,232 ---- #ifdef USE_SSL if (conn->ssl) ! n = pqTLS_write(conn, ptr, len, &got_epipe); else #endif { *************** *** 433,1036 **** return n; } - /* ------------------------------------------------------------ */ - /* SSL specific code */ - /* ------------------------------------------------------------ */ - #ifdef USE_SSL - - /* - * Certificate verification callback - * - * This callback allows us to log intermediate problems during - * verification, but there doesn't seem to be a clean way to get - * our PGconn * structure. So we can't log anything! - * - * This callback also allows us to override the default acceptance - * criteria (e.g., accepting self-signed or expired certs), but - * for now we accept the default checks. - */ - static int - verify_cb(int ok, X509_STORE_CTX *ctx) - { - return ok; - } - - #ifdef NOT_USED /* ! * Verify that common name resolves to peer. */ ! static int ! verify_peer(PGconn *conn) ! { ! struct hostent *h = NULL; ! struct sockaddr addr; ! struct sockaddr_in *sin; ! ACCEPT_TYPE_ARG3 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]; ! ! 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) ! 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; ! } ! ! /* ! * 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; ! } ! ! /* 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); ! } ! ! return -1; ! } ! #endif /* NOT_USED */ ! ! /* ! * Callback used by SSL to load client cert and key. ! * This callback is only called when the server wants a ! * client cert. ! * ! * Must return 1 on success, 0 on no data or error. ! */ ! static int ! client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) ! { ! char homedir[MAXPGPATH]; ! struct stat buf; ! ! #ifndef WIN32 ! struct stat buf2; ! #endif ! char fnbuf[MAXPGPATH]; ! FILE *fp; ! PGconn *conn = (PGconn *) SSL_get_app_data(ssl); ! int (*cb) () = NULL; /* how to read user password */ ! char sebuf[256]; ! ! if (!pqGetHomeDirectory(homedir, sizeof(homedir))) ! { ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("could not get user information\n")); ! return 0; ! } ! ! /* read the user certificate */ ! snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE); ! if ((fp = 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; ! } ! if (PEM_read_X509(fp, x509, NULL, NULL) == NULL) ! { ! char *err = SSLerrmessage(); ! ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("could not read certificate file \"%s\": %s\n"), ! fnbuf, err); ! SSLerrfree(err); ! fclose(fp); ! return 0; ! } ! fclose(fp); ! ! /* read the user key */ ! snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE); ! if (stat(fnbuf, &buf) == -1) ! { ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("certificate present, but not private key file \"%s\"\n"), ! fnbuf); ! return 0; ! } ! #ifndef WIN32 ! if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) || ! buf.st_uid != geteuid()) ! { ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("private key file \"%s\" has wrong permissions\n"), ! fnbuf); ! return 0; ! } ! #endif ! if ((fp = fopen(fnbuf, "r")) == NULL) ! { ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("could not open private key file \"%s\": %s\n"), ! fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); ! return 0; ! } ! #ifndef WIN32 ! 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); ! return 0; ! } ! #endif ! if (PEM_read_PrivateKey(fp, pkey, cb, NULL) == NULL) ! { ! char *err = SSLerrmessage(); ! ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("could not read private key file \"%s\": %s\n"), ! fnbuf, err); ! SSLerrfree(err); ! fclose(fp); ! return 0; ! } ! fclose(fp); ! ! /* verify that the cert and key go together */ ! if (!X509_check_private_key(*x509, *pkey)) ! { ! char *err = SSLerrmessage(); ! ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("certificate does not match private key file \"%s\": %s\n"), ! fnbuf, err); ! SSLerrfree(err); ! return 0; ! } ! ! return 1; ! } ! ! #ifdef ENABLE_THREAD_SAFETY ! ! static unsigned long ! pq_threadidcallback(void) ! { ! /* ! * This is not starndard-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]); ! } ! #endif /* ENABLE_THREAD_SAFETY */ ! ! static int ! init_ssl_system(PGconn *conn) ! { ! #ifdef ENABLE_THREAD_SAFETY ! #ifndef WIN32 ! 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) ! pthread_mutex_init(&init_mutex, NULL); ! InterlockedExchange(&mutex_initlock, 0); ! } ! #endif ! pthread_mutex_lock(&init_mutex); ! ! 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) ! { ! pthread_mutex_unlock(&init_mutex); ! return -1; ! } ! for (i = 0; i < CRYPTO_num_locks(); i++) ! pthread_mutex_init(&pq_lockarray[i], NULL); ! ! CRYPTO_set_locking_callback(pq_lockingcallback); ! } ! #endif ! if (!SSL_context) ! { ! if (pq_initssllib) ! { ! SSL_library_init(); ! SSL_load_error_strings(); ! } ! SSL_context = SSL_CTX_new(TLSv1_method()); ! if (!SSL_context) ! { ! char *err = SSLerrmessage(); ! ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("could not create SSL context: %s\n"), ! err); ! SSLerrfree(err); ! #ifdef ENABLE_THREAD_SAFETY ! pthread_mutex_unlock(&init_mutex); ! #endif ! return -1; ! } ! } ! #ifdef ENABLE_THREAD_SAFETY ! pthread_mutex_unlock(&init_mutex); ! #endif ! return 0; ! } ! ! /* ! * Initialize global SSL context. ! */ ! static int ! initialize_SSL(PGconn *conn) ! { ! struct stat buf; ! char homedir[MAXPGPATH]; ! char fnbuf[MAXPGPATH]; ! ! if (init_ssl_system(conn)) ! return -1; ! ! /* Set up to verify server cert, if root.crt is present */ ! if (pqGetHomeDirectory(homedir, sizeof(homedir))) ! { ! snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE); ! if (stat(fnbuf, &buf) == 0) ! { ! 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; ! } ! ! SSL_CTX_set_verify(SSL_context, SSL_VERIFY_PEER, verify_cb); ! } ! } ! ! /* set up mechanism to provide client certificate, if available */ ! SSL_CTX_set_client_cert_cb(SSL_context, client_cert_cb); ! ! return 0; ! } ! ! /* ! * Destroy global SSL context. ! */ ! static void ! destroy_SSL(void) ! { ! if (SSL_context) ! { ! SSL_CTX_free(SSL_context); ! SSL_context = NULL; ! } ! } ! ! /* ! * Attempt to negotiate SSL connection. ! */ ! static PostgresPollingStatusType ! open_client_SSL(PGconn *conn) { ! int r; ! ! r = SSL_connect(conn->ssl); ! if (r <= 0) ! { ! int err = SSL_get_error(conn->ssl, r); ! ! switch (err) ! { ! case SSL_ERROR_WANT_READ: ! return PGRES_POLLING_READING; ! ! case SSL_ERROR_WANT_WRITE: ! return PGRES_POLLING_WRITING; ! ! case SSL_ERROR_SYSCALL: ! { ! char sebuf[256]; ! ! if (r == -1) ! printfPQExpBuffer(&conn->errorMessage, ! 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")); ! close_SSL(conn); ! return PGRES_POLLING_FAILED; ! } ! case SSL_ERROR_SSL: ! { ! /* ! * 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: %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 ! */ ! 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(); ! ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("certificate could not be obtained: %s\n"), ! err); ! SSLerrfree(err); ! close_SSL(conn); ! return PGRES_POLLING_FAILED; ! } ! ! X509_NAME_oneline(X509_get_subject_name(conn->peer), ! 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), ! 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) ! { ! close_SSL(conn); ! return PGRES_POLLING_FAILED; ! } #endif ! ! /* SSL handshake is complete */ ! return PGRES_POLLING_OK; ! } ! ! /* ! * Close SSL connection. ! */ ! static void ! close_SSL(PGconn *conn) ! { ! if (conn->ssl) ! { ! SSL_shutdown(conn->ssl); ! SSL_free(conn->ssl); ! conn->ssl = NULL; ! } ! ! if (conn->peer) ! { ! X509_free(conn->peer); ! conn->peer = NULL; ! } ! } ! ! /* ! * Obtain reason string for last SSL error ! * ! * Some caution is needed here since ERR_reason_error_string will ! * 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 * ! SSLerrmessage(void) ! { ! unsigned long errcode; ! const char *errreason; ! 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"); ! return errbuf; ! } ! errreason = ERR_reason_error_string(errcode); ! if (errreason != NULL) ! { ! strncpy(errbuf, errreason, SSL_ERR_LEN - 1); ! errbuf[SSL_ERR_LEN - 1] = '\0'; ! return errbuf; ! } ! snprintf(errbuf, SSL_ERR_LEN, "SSL error code %lu", errcode); ! return errbuf; ! } ! ! static void ! SSLerrfree(char *buf) ! { ! if (buf != ssl_nomem) ! free(buf); ! } ! ! /* ! * Return pointer to OpenSSL object. ! */ ! void * ! PQgetssl(PGconn *conn) ! { ! if (!conn) ! return NULL; ! return conn->ssl; ! } ! #else /* !USE_SSL */ ! ! void * ! PQgetssl(PGconn *conn) ! { ! return NULL; } - #endif /* USE_SSL */ #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32) --- 248,265 ---- return n; } /* ! * Returns true if the SSL connection has bytes to read */ ! int ! pqsecure_pending(PGconn *conn) { ! #ifdef USE_SSL ! if(conn->ssl) ! return pqTLS_pending(conn); #endif ! return 0; } #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32) *************** *** 1117,1119 **** --- 346,493 ---- } #endif /* ENABLE_THREAD_SAFETY */ + /* ------------------------------------------------------------ */ + /* SSL public interface stub definitions */ + /* ------------------------------------------------------------ */ + + #ifndef USE_SSL + /* + * Return pointer to SSL object. + * + * Normally defined in the file proving SSL service but if no SSL is compiled in we defined the function here + */ + + void * + PQgetssl(PGconn *conn) + { + return NULL; + } + /* + * Exported function to allow application to tell us it's already + * initialized OpenSSL. Does nothing in the no-SSL case. + */ + void + PQinitSSL(int do_init) + { + } + + #endif /* USE_SSL */ + + void + pqsecure_tlsinfo_add_row( PGresult *res, const char *key, const char *value ) + { + PGresAttValue *row = pqResultAlloc( res, sizeof( PGresAttValue ) * 2, false ); + + row[0].value = pqResultStrdup( res, key ); + row[0].len = strlen(key); + row[1].value = pqResultStrdup( res, value ); + row[1].len = strlen(value); + + pqAddTuple( res, row ); + } + + + PGresult * + PQgettlsinfo(PGconn *conn) + { + #ifdef USE_SSL + PGresult *res = PQmakeEmptyPGresult( conn, PGRES_TUPLES_OK ); + res->numAttributes = 2; + res->attDescs = (PGresAttDesc *) pqResultAlloc(res, 2 * sizeof(PGresAttDesc), TRUE); + memset( res->attDescs, 0, 2 * sizeof(PGresAttDesc) ); + res->attDescs[0].name = pqResultStrdup( res, "key" ); + res->attDescs[0].format = 0; + res->attDescs[0].typid = 25; + res->attDescs[0].typlen = -1; + res->attDescs[0].format = 0; + res->attDescs[1].name = pqResultStrdup( res, "value" ); + res->attDescs[1].format = 0; + res->attDescs[1].typid = 25; + res->attDescs[1].typlen = -1; + res->attDescs[1].format = 0; + + return pqTLS_gettlsinfo( conn, res ); + #else + return NULL; + #endif + } + + static PostgresPollingStatusType + pq_pt_read(PGconn *conn, void *buf, int *len) + { + size_t s; + + if( conn->status != CONNECTION_PASSTHROUGH ) + return PGRES_POLLING_FAILED; + + retry_read: + s = pqsecure_read(conn, buf, *len); + if( s < 0 ) + { + *len = 0; + switch( SOCK_ERRNO ) + { + case EINTR: + goto retry_read; + #ifdef EAGAIN + case EAGAIN: + return PGRES_POLLING_READING; + #endif + #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EAGAIN != EWOULDBLOCK)) + case EWOULDBLOCK: + return PGRES_POLLING_READING; + #endif + default: + return PGRES_POLLING_FAILED; + } + } + else + { + *len = s; + return PGRES_POLLING_OK; + } + } + + static PostgresPollingStatusType + pq_pt_write(PGconn *conn, const void *buf, int *len) + { + size_t s; + + if( conn->status != CONNECTION_PASSTHROUGH ) + return PGRES_POLLING_FAILED; + + retry_write: + s = pqsecure_write(conn, buf, *len); + if( s < 0 ) + { + *len = 0; + switch( SOCK_ERRNO ) + { + case EINTR: + goto retry_write; + #ifdef EAGAIN + case EAGAIN: + return PGRES_POLLING_WRITING; + #endif + #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EAGAIN != EWOULDBLOCK)) + case EWOULDBLOCK: + return PGRES_POLLING_WRITING; + #endif + default: + return PGRES_POLLING_FAILED; + } + } + else + { + *len = s; + return PGRES_POLLING_OK; + } + } + + void + pqsecure_pt_setup( PQpassthrough *state ) + { + state->read = pq_pt_read; + state->write = pq_pt_write; + state->pending = pqsecure_pending; + } Index: src/interfaces/libpq/libpq-fe.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v retrieving revision 1.127 diff -c -r1.127 libpq-fe.h *** src/interfaces/libpq/libpq-fe.h 27 Apr 2006 00:53:58 -0000 1.127 --- src/interfaces/libpq/libpq-fe.h 4 May 2006 13:35:54 -0000 *************** *** 53,59 **** * backend startup. */ CONNECTION_SETENV, /* Negotiating environment. */ CONNECTION_SSL_STARTUP, /* Negotiating SSL. */ ! CONNECTION_NEEDED /* Internal state: connect() needed */ } ConnStatusType; typedef enum --- 53,61 ---- * backend startup. */ CONNECTION_SETENV, /* Negotiating environment. */ CONNECTION_SSL_STARTUP, /* Negotiating SSL. */ ! CONNECTION_NEEDED, /* Internal state: connect() needed */ ! ! CONNECTION_PASSTHROUGH, /* Connection set for pass-through mode */ } ConnStatusType; typedef enum *************** *** 270,278 **** --- 272,304 ---- * unencrypted connections or if any other TLS library is in use. */ extern void *PQgetssl(PGconn *conn); + /* Get data about current TLS connection */ + extern PGresult *PQgettlsinfo(PGconn *conn); + /* Tell libpq whether it needs to initialize OpenSSL */ extern void PQinitSSL(int do_init); + /* Tell libpq we're taking over the connection. After this, no normal + * queries may be sent anymore. When finished you may close the connection */ + typedef PostgresPollingStatusType (*pq_read_func)( PGconn *conn, void *buf, int *len); + typedef PostgresPollingStatusType (*pq_write_func)( PGconn *conn, const void *buf, int *len); + typedef int (*pq_pending_func)( PGconn *conn ); + + typedef struct { + int len; /* Length of this structure, so users may determine if the + info they require is there. For backward compatability, + new members can only be added to the end. */ + pq_read_func read; + pq_write_func write; + pq_pending_func pending; + + /* char *ssllibname; Need not yet demonstrated. */ + /* void *sslptr; */ + } PQpassthrough; + + /* The pointer returned in state must be freed with PQfreemem() */ + extern int PQsetPassthrough(PGconn *conn, PQpassthrough **state ); + /* Set verbosity for PQerrorMessage and PQresultErrorMessage */ extern PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity); Index: src/interfaces/libpq/libpq-int.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.112 diff -c -r1.112 libpq-int.h *** src/interfaces/libpq/libpq-int.h 14 Mar 2006 22:48:23 -0000 1.112 --- src/interfaces/libpq/libpq-int.h 4 May 2006 13:35:54 -0000 *************** *** 49,59 **** /* include stuff found in fe only */ #include "pqexpbuffer.h" - #ifdef USE_SSL - #include - #include - #endif - /* * POSTGRES backend dependent Constants. */ --- 49,54 ---- *************** *** 337,344 **** bool allow_ssl_try; /* Allowed to try SSL negotiation */ 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 */ char peer_cn[SM_USER + 1]; /* peer common name */ #endif --- 332,338 ---- bool allow_ssl_try; /* Allowed to try SSL negotiation */ bool wait_ssl_try; /* Delay SSL negotiation until after * attempting normal connection */ ! void *ssl; /* SSL status, if have SSL connection */ char peer_dn[256 + 1]; /* peer distinguished name */ char peer_cn[SM_USER + 1]; /* peer common name */ #endif *************** *** 479,484 **** --- 473,481 ---- extern void pqsecure_close(PGconn *); extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len); extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len); + extern int pqsecure_pending(PGconn *); + extern void pqsecure_tlsinfo_add_row(PGresult *res, const char *key, const char *value); + extern void pqsecure_pt_setup( PQpassthrough *state ); #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32) extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending); Index: src/backend/libpq/be-secure-gnutls.c ========================================================================= *** src/backend/libpq/be-secure-gnutls.c.orig Thu May 4 15:35:59 2006 --- src/backend/libpq/be-secure-gnutls.c Thu May 4 15:20:50 2006 *************** *** 0 **** --- 1,511 ---- + /*------------------------------------------------------------------------- + * + * be-secure.c + * functions related to setting up a secure connection to the frontend. + * Secure connections are expected to provide confidentiality, + * message integrity and endpoint authentication. + * + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/libpq/be-secure.c,v 1.63 2006/03/21 18:18:35 neilc Exp $ + * + * This file implements the GnuTLS version of the functions needed by be-secure.c + * + *------------------------------------------------------------------------- + */ + + #include "postgres.h" + + #include + #include + #include + #include + #include + #include + #include + #include + #ifdef HAVE_NETINET_TCP_H + #include + #include + #endif + + #include + #include + + /* If thread safety ever becomes an issue for the backend, we will need to + * include this. See the frontend code in libpq. */ + /* #include */ + + #include "libpq/libpq.h" + #include "miscadmin.h" + #include "tcop/tcopprot.h" + + + #ifdef NOT_USED + static int verify_cb(int, X509_STORE_CTX *); + static void info_cb(const SSL *ssl, int type, int args); + #endif + + static const char *SSLerrmessage(int); + + static bool SSL_verify_client; + static gnutls_certificate_credentials SSL_cred; /* Credentials for connecting */ + /* + * How much data can be sent across a secure connection + * (total in both directions) before we require renegotiation. + */ + #define RENEGOTIATION_LIMIT (512 * 1024 * 1024) + + #define DH_BITS 1024 + + /* + * Read data from a secure connection. + */ + ssize_t + pgtls_read(Port *port, void *ptr, size_t len) + { + ssize_t n; + + rloop: + n = gnutls_record_recv(port->ssl, ptr, len); + if( n < 0 ) + { + if( n == GNUTLS_E_INTERRUPTED || + n == GNUTLS_E_AGAIN ) + { + #ifdef WIN32 + pgwin32_waitforsinglesocket(gnutls_transport_get_ptr(port->ssl), + (gnutls_record_get_direction(port->ssl) == 0) ? + FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE); + #endif + goto rloop; + } + else + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL error: %s", SSLerrmessage(n)))); + errno = ECONNRESET; + n = -1; + } + } + else + port->count += n; /* count bytes */ + + return n; + } + + /* + * Write data to a secure connection. + */ + ssize_t + pgtls_write(Port *port, void *ptr, size_t len) + { + ssize_t n; + + int err; + + wloop: + n = gnutls_record_send(port->ssl, ptr, len); + if( n < 0 ) + { + if( n == GNUTLS_E_INTERRUPTED || + n == GNUTLS_E_AGAIN ) + { + #ifdef WIN32 + pgwin32_waitforsinglesocket(port->sock), + (gnutls_record_get_direction(port->ssl) == 0) ? + FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE); + #endif + goto wloop; + } + else + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL error: %s", SSLerrmessage(n)))); + errno = ECONNRESET; + n = -1; + } + return n; + } + port->count += n; + + if (port->count > RENEGOTIATION_LIMIT) + { + /* Note: we don't allow the user to ignore the renegotiation request. We could... */ + do { + err = gnutls_rehandshake(port->ssl); + } while( err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN ); + + if (err < 0) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL renegotiation failure: %s",SSLerrmessage(err)))); + errno = ECONNRESET; + return -1; + } + + do { + err = gnutls_handshake(port->ssl); + } while( err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN ); + + if (err < 0) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL renegotiation failure: %s", SSLerrmessage(err)))); + + errno = ECONNRESET; + return -1; + } + + port->count = 0; + } + + return n; + } + + /* + * Private substitute BIO: this wraps the SSL library's standard socket BIO + * so that we can enable and disable interrupts just while calling recv(). + * We cannot have interrupts occurring while the bulk of openssl runs, + * because it uses malloc() and possibly other non-reentrant libc facilities. + */ + + static int + gnutls_raw_read(gnutls_transport_ptr fd, void *buf, size_t size) + { + int res; + + prepare_for_client_read(); + + res = read((int)fd, buf, size); + + client_read_ended(); + + return res; + } + + /* + * Certificate verification callback + * + * This callback allows us to log intermediate problems during + * verification, but for now we'll see if the final error message + * contains enough information. + * + * This callback also allows us to override the default acceptance + * criteria (e.g., accepting self-signed or expired certs), but + * for now we accept the default checks. + */ + #ifdef NOT_USED + static int + verify_cb(int ok, X509_STORE_CTX *ctx) + { + return ok; + } + #endif + + /* + * This callback is used to copy SSL information messages + * into the PostgreSQL log. + */ + #ifdef NOT_USED + static void + info_cb(const SSL *ssl, int type, int args) + { + } + #endif + + /* + * Initialize global SSL context. This will be called once, by the postmaster during startup + */ + void + pgtls_initialize(void) + { + struct stat buf; + gnutls_dh_params dh_params; + int error; + + #ifdef ENABLE_THREAD_SAFETY + /* GnuTLS is thread-safe by design, but gcrypt may not be. Here we assume that the lib is pthread */ + /* Thread safety not an issue for server */ + /* gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); */ + #endif + /* Do gnutls_check_version(LIBGNUTLS_VERSION) ? */ + error = gnutls_global_init(); /* Check for error */ + if( error < 0 ) + ereport(FATAL, + (errmsg("could not initialize GnuTLS: %s", + SSLerrmessage(error)))); + + gnutls_certificate_allocate_credentials (&SSL_cred); + + if (stat(SERVER_PRIVATE_KEY_FILE, &buf) == -1) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not access private key file \"%s\": %m", + SERVER_PRIVATE_KEY_FILE))); + + /* + * Require no public access to key file. + * + * XXX temporarily suppress check when on Windows, because there may + * not be proper support for Unix-y file permissions. Need to think + * of a reasonable check to apply on Windows. (See also the data + * directory permission check in postmaster.c) + */ + #if !defined(WIN32) && !defined(__CYGWIN__) + if (!S_ISREG(buf.st_mode) || (buf.st_mode & (S_IRWXG | S_IRWXO)) || + buf.st_uid != geteuid()) + ereport(FATAL, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("unsafe permissions on private key file \"%s\"", + SERVER_PRIVATE_KEY_FILE), + errdetail("File must be owned by the database user and must have no permissions for \"group\" or \"other\"."))); + #endif + + if( (error = gnutls_certificate_set_x509_key_file( SSL_cred, SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, GNUTLS_X509_FMT_PEM )) < 0 ) + ereport(FATAL, + (errmsg("could not load private key/certificate file \"%s\", \"%s\": %s", + SERVER_PRIVATE_KEY_FILE, SERVER_CERT_FILE, SSLerrmessage(error)))); + + + /* set up empheral DH keys */ + #if 0 + gnutls_certificate_set_params_function(SSL_cred, tmp_dh_cb); + #else + gnutls_dh_params_init (&dh_params); + gnutls_dh_params_generate2 (dh_params, DH_BITS); + gnutls_certificate_set_dh_params (SSL_cred, dh_params); + #endif + + /* Cannot setup the allowed cipher list, must do that at connection start */ + + /* + * Require and check client certificates only if we have a root.crt file. + */ + error = gnutls_certificate_set_x509_trust_file (SSL_cred, ROOT_CERT_FILE, GNUTLS_X509_FMT_PEM); + if ( error < 0 ) + { + /* Not fatal - we do not require client certificates */ + ereport(LOG, + (errmsg("could not load root certificate file \"%s\": %s", + ROOT_CERT_FILE, SSLerrmessage(error)), + errdetail("Will not verify client certificates."))); + } + else + { + SSL_verify_client = TRUE; + error = gnutls_certificate_set_x509_crl_file (SSL_cred, ROOT_CRL_FILE, GNUTLS_X509_FMT_PEM); + if ( error < 0 ) + { + /* Not fatal - we do not require a CRL file */ + ereport(LOG, + (errmsg("SSL Certificate Revocation List (CRL) file \"%s\" not found, skipping: %s", + ROOT_CRL_FILE, SSLerrmessage(error)), + errdetail("Will not check certificates against CRL."))); + } + } + + #ifdef NOT_USED + /* We don't use a verify function, but if we did, here's where we'd set it */ + SSL_CTX_set_verify(SSL_context, + (SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT | + SSL_VERIFY_CLIENT_ONCE), + verify_cb); + #endif + } + + /* + * Destroy global SSL context. + */ + void + pgtls_destroy(void) + { + gnutls_certificate_free_credentials(SSL_cred); + SSL_cred = NULL; + gnutls_global_deinit(); + } + + /* + * Attempt to negotiate SSL connection. + */ + int + pgtls_open_server(Port *port) + { + int err; + int certcount; + + const int kx_prio[] = { GNUTLS_KX_DHE_RSA, GNUTLS_KX_DHE_DSS, 0 }; + /* Only do X509 certificates */ + const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; + const int cipher_type_priority[] = { GNUTLS_CIPHER_AES_256_CBC, GNUTLS_CIPHER_AES_128_CBC, GNUTLS_CIPHER_ARCFOUR_128, 0 }; + const int mac_type_priority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_RMD160, 0 }; + + Assert(!port->ssl); + Assert(!port->peer); + + err = gnutls_init (&port->ssl, GNUTLS_SERVER); + if( err < 0 ) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not initialize SSL connection: %s", + SSLerrmessage(err)))); + pgtls_close(port); + return -1; + } + /* Use default priorities */ + gnutls_set_default_priority (port->ssl); + /* Server uses: ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH. This means: + * - No anonymous DH cipher suites. + * - Low security keys + * - No export algorithms + * - No MD5 */ + gnutls_kx_set_priority (port->ssl, kx_prio); /* DH keyexchange only */ + gnutls_certificate_type_set_priority (port->ssl, cert_type_priority); /* X.509 certs only */ + gnutls_cipher_set_priority( port->ssl, cipher_type_priority ); + gnutls_mac_set_priority( port->ssl, mac_type_priority ); + + gnutls_transport_set_ptr (port->ssl, (gnutls_transport_ptr) port->sock); + gnutls_session_set_ptr (port->ssl, port); + + /* setup customer read function */ + gnutls_transport_set_pull_function( port->ssl, gnutls_raw_read ); + + /* set up mechanism to provide client certificate, if available */ + gnutls_credentials_set (port->ssl, GNUTLS_CRD_CERTIFICATE, SSL_cred); + + /* Set the number of bits for DH */ + gnutls_dh_set_prime_bits (port->ssl, DH_BITS); + + /* Request a certificate, if any */ + gnutls_certificate_server_set_request (port->ssl, GNUTLS_CERT_REQUEST); + + aloop: + err = gnutls_handshake (port->ssl); + if (err < 0) + { + if( err == GNUTLS_E_AGAIN || + err == GNUTLS_E_INTERRUPTED ) + { + #ifdef WIN32 + pgwin32_waitforsinglesocket(port->sock), + (gnutls_record_get_direction(port->ssl) == 0) ? + FD_READ | FD_CLOSE | FD_ACCEPT : FD_WRITE | FD_CLOSE); + #endif + goto aloop; + } + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("could not accept SSL connection: %s", SSLerrmessage(err)))); + pgtls_close(port); + return -1; + } + + port->count = 0; + + /* get server certificate */ + port->peer = gnutls_certificate_get_peers(port->ssl, &certcount); + + strncpy(port->peer_dn, "(anonymous)", sizeof(port->peer_dn)); + strncpy(port->peer_cn, "(anonymous)", sizeof(port->peer_cn)); + + if( port->peer ) + { + gnutls_x509_crt cert; + + err = gnutls_x509_crt_init (&cert); + if(err == 0) + err = gnutls_x509_crt_import (cert, &port->peer[0], GNUTLS_X509_FMT_DER); + if(err < 0) + { + ereport(WARNING, + (errmsg("gnutls_x509_crt_import failure: %s\n", SSLerrmessage(err)))); + } + else + { + int size = sizeof(port->peer_dn); + gnutls_x509_crt_get_dn (cert, port->peer_dn, &size); + port->peer_dn[sizeof(port->peer_dn) - 1] = '\0'; + + size = sizeof(port->peer_cn); + gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, port->peer_cn, &size ); + port->peer_cn[sizeof(port->peer_cn) - 1] = '\0'; + + gnutls_x509_crt_deinit( cert ); + } + } + /* If asked to verify server, do so */ + if( SSL_verify_client ) + { + int status; + err = gnutls_certificate_verify_peers2 (port->ssl, &status); + if(err < 0) + { + ereport(COMMERROR, + (errmsg("SSL verification error: %s\n", SSLerrmessage(err)))); + pgtls_close(port); + return -1; + } + if( status ) + { + ereport(COMMERROR, + (errmsg("Server cert verification failure: %d\n", status))); + pgtls_close(port); + return -1; + } + } + ereport(DEBUG2, + (errmsg("SSL connection from \"%s\"", port->peer_cn))); + + #ifdef NOT_USED + /* set up debugging/info callback */ + SSL_CTX_set_info_callback(SSL_context, info_cb); + #endif + + return 0; + } + + /* + * Close SSL connection. + */ + void + pgtls_close(Port *port) + { + /* Isn't allocated, so don't free */ + if (port->peer) + port->peer = NULL; + /* Clear SSL data */ + if (port->ssl) + { + gnutls_deinit(port->ssl); + port->ssl = NULL; + } + + } + + /* + * Obtain reason string for last SSL error + * + * gnutls_strerror returns a useful string even if it doesn't recognise the error code + */ + static const char * + SSLerrmessage(int errcode) + { + if (errcode == 0) + return "No SSL error reported"; + /* Error in push/pull function is error in socket send/recv */ + if( errcode == GNUTLS_E_PUSH_ERROR || errcode == GNUTLS_E_PULL_ERROR ) + return strerror(errno); + + return gnutls_strerror(errcode); + } + Index: src/backend/libpq/be-secure-openssl.c ========================================================================= *** src/backend/libpq/be-secure-openssl.c.orig Thu May 4 15:35:59 2006 --- src/backend/libpq/be-secure-openssl.c Thu May 4 13:04:32 2006 *************** *** 0 **** --- 1,839 ---- + /*------------------------------------------------------------------------- + * + * be-secure.c + * functions related to setting up a secure connection to the frontend. + * Secure connections are expected to provide confidentiality, + * message integrity and endpoint authentication. + * + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/libpq/be-secure.c,v 1.63 2006/03/21 18:18:35 neilc Exp $ + * + * This file implements the OpenSSL version of the functions needed by be-secure.c + * + *------------------------------------------------------------------------- + */ + + #include "postgres.h" + + #include + #include + #include + #include + #include + #include + #include + #include + #ifdef HAVE_NETINET_TCP_H + #include + #include + #endif + + #include + #include + + #include "libpq/libpq.h" + #include "miscadmin.h" + #include "tcop/tcopprot.h" + + + 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 int verify_cb(int, X509_STORE_CTX *); + static void info_cb(const SSL *ssl, int type, int args); + static const char *SSLerrmessage(void); + + /* + * How much data can be sent across a secure connection + * (total in both directions) before we require renegotiation. + */ + #define RENEGOTIATION_LIMIT (512 * 1024 * 1024) + + static SSL_CTX *SSL_context = NULL; + + /* ------------------------------------------------------------ */ + /* Hardcoded values */ + /* ------------------------------------------------------------ */ + + /* + * 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 DBA... or an attacker... deletes the + * $DataDir/dh*.pem files. + * + * We could refuse SSL connections unless a good DH parameter + * file exists, but some clients may quietly renegotiate an + * unsecured connection without fully informing the user. + * Very uncool. + * + * Alternately, the backend could attempt to load these files + * on startup if SSL is enabled - and refuse to start if any + * do not exist - but this would tend to piss off DBAs. + * + * If you want to create your own hardcoded DH parameters + * for fun and profit, review "Assigned Number for SKIP + * Protocols" (http://www.skip-vpn.org/spec/numbers.html) + * for suggestions. + */ + + 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"; + + /* + * Read data from a secure connection. + */ + ssize_t + pgtls_read(Port *port, void *ptr, size_t len) + { + ssize_t n; + + int err; + + rloop: + n = SSL_read(port->ssl, ptr, len); + err = SSL_get_error(port->ssl, n); + switch (err) + { + case SSL_ERROR_NONE: + port->count += n; + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + #ifdef WIN32 + pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl), + (err == SSL_ERROR_WANT_READ) ? + FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE); + #endif + goto rloop; + case SSL_ERROR_SYSCALL: + if (n == -1) + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("SSL SYSCALL error: %m"))); + else + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL SYSCALL error: EOF detected"))); + errno = ECONNRESET; + n = -1; + } + break; + case SSL_ERROR_SSL: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL error: %s", SSLerrmessage()))); + /* fall through */ + case SSL_ERROR_ZERO_RETURN: + errno = ECONNRESET; + n = -1; + break; + default: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("unrecognized SSL error code: %d", + err))); + n = -1; + break; + } + + return n; + } + + /* + * Write data to a secure connection. + */ + ssize_t + pgtls_write(Port *port, void *ptr, size_t len) + { + ssize_t n; + + int err; + + if (port->count > RENEGOTIATION_LIMIT) + { + SSL_set_session_id_context(port->ssl, (void *) &SSL_context, + sizeof(SSL_context)); + if (SSL_renegotiate(port->ssl) <= 0) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL renegotiation failure"))); + if (SSL_do_handshake(port->ssl) <= 0) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL renegotiation failure"))); + if (port->ssl->state != SSL_ST_OK) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL failed to send renegotiation request"))); + port->ssl->state |= SSL_ST_ACCEPT; + SSL_do_handshake(port->ssl); + if (port->ssl->state != SSL_ST_OK) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL renegotiation failure"))); + port->count = 0; + } + + wloop: + n = SSL_write(port->ssl, ptr, len); + err = SSL_get_error(port->ssl, n); + switch (err) + { + case SSL_ERROR_NONE: + port->count += n; + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + #ifdef WIN32 + pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl), + (err == SSL_ERROR_WANT_READ) ? + FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE); + #endif + goto wloop; + case SSL_ERROR_SYSCALL: + if (n == -1) + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("SSL SYSCALL error: %m"))); + else + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL SYSCALL error: EOF detected"))); + errno = ECONNRESET; + n = -1; + } + break; + case SSL_ERROR_SSL: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL error: %s", SSLerrmessage()))); + /* fall through */ + case SSL_ERROR_ZERO_RETURN: + errno = ECONNRESET; + n = -1; + break; + default: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("unrecognized SSL error code: %d", + err))); + n = -1; + break; + } + + return n; + } + + /* ------------------------------------------------------------ */ + /* SSL specific code */ + /* ------------------------------------------------------------ */ + + /* + * Private substitute BIO: this wraps the SSL library's standard socket BIO + * so that we can enable and disable interrupts just while calling recv(). + * We cannot have interrupts occurring while the bulk of openssl runs, + * because it uses malloc() and possibly other non-reentrant libc facilities. + * + * As of openssl 0.9.7, we can use the reasonably clean method of interposing + * a wrapper around the standard socket BIO's sock_read() method. This relies + * on the fact that sock_read() doesn't call anything non-reentrant, in fact + * not much of anything at all except recv(). If this ever changes we'd + * probably need to duplicate the code of sock_read() in order to push the + * interrupt enable/disable down yet another level. + */ + + static bool my_bio_initialized = false; + static BIO_METHOD my_bio_methods; + static int (*std_sock_read) (BIO *h, char *buf, int size); + + static int + my_sock_read(BIO *h, char *buf, int size) + { + int res; + + prepare_for_client_read(); + + res = std_sock_read(h, buf, size); + + client_read_ended(); + + return res; + } + + static BIO_METHOD * + my_BIO_s_socket(void) + { + if (!my_bio_initialized) + { + memcpy(&my_bio_methods, BIO_s_socket(), sizeof(BIO_METHOD)); + std_sock_read = my_bio_methods.bread; + my_bio_methods.bread = my_sock_read; + my_bio_initialized = true; + } + return &my_bio_methods; + } + + /* This should exactly match openssl's SSL_set_fd except for using my BIO */ + static int + my_SSL_set_fd(SSL *s, int fd) + { + int ret = 0; + BIO *bio = NULL; + + bio = BIO_new(my_BIO_s_socket()); + + if (bio == NULL) + { + SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB); + goto err; + } + BIO_set_fd(bio, fd, BIO_NOCLOSE); + SSL_set_bio(s, bio, bio); + ret = 1; + err: + return ret; + } + + /* + * 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. + */ + static DH * + load_dh_file(int keylength) + { + FILE *fp; + char fnbuf[MAXPGPATH]; + DH *dh = NULL; + int codes; + + /* attempt to open file. It's not an error if it doesn't exist. */ + snprintf(fnbuf, sizeof(fnbuf), "dh%d.pem", 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) + { + elog(LOG, "DH errors (%s): %d bits expected, %d bits found", + fnbuf, keylength, 8 * DH_size(dh)); + dh = NULL; + } + + /* make sure the DH parameters are usable */ + if (dh != NULL) + { + if (DH_check(dh, &codes)) + { + elog(LOG, "DH_check error (%s): %s", fnbuf, SSLerrmessage()); + return NULL; + } + if (codes & DH_CHECK_P_NOT_PRIME) + { + elog(LOG, "DH error (%s): p is not prime", fnbuf); + return NULL; + } + if ((codes & DH_NOT_SUITABLE_GENERATOR) && + (codes & DH_CHECK_P_NOT_SAFE_PRIME)) + { + elog(LOG, + "DH error (%s): neither suitable generator or safe prime", + fnbuf); + return NULL; + } + } + + return dh; + } + + /* + * 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); + if (dh == NULL) + ereport(DEBUG2, + (errmsg_internal("DH load buffer: %s", + SSLerrmessage()))); + 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) + { + 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; + } + + /* this may take a long time, but it may be necessary... */ + if (r == NULL || 8 * DH_size(r) < keylength) + { + ereport(DEBUG2, + (errmsg_internal("DH: generating parameters (%d bits)....", + keylength))); + r = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL); + } + + return r; + } + + /* + * Certificate verification callback + * + * This callback allows us to log intermediate problems during + * verification, but for now we'll see if the final error message + * contains enough information. + * + * This callback also allows us to override the default acceptance + * criteria (e.g., accepting self-signed or expired certs), but + * for now we accept the default checks. + */ + static int + verify_cb(int ok, X509_STORE_CTX *ctx) + { + return ok; + } + + /* + * This callback is used to copy SSL information messages + * into the PostgreSQL log. + */ + static void + info_cb(const SSL *ssl, int type, int args) + { + switch (type) + { + case SSL_CB_HANDSHAKE_START: + ereport(DEBUG4, + (errmsg_internal("SSL: handshake start"))); + break; + case SSL_CB_HANDSHAKE_DONE: + ereport(DEBUG4, + (errmsg_internal("SSL: handshake done"))); + break; + case SSL_CB_ACCEPT_LOOP: + ereport(DEBUG4, + (errmsg_internal("SSL: accept loop"))); + break; + case SSL_CB_ACCEPT_EXIT: + ereport(DEBUG4, + (errmsg_internal("SSL: accept exit (%d)", args))); + break; + case SSL_CB_CONNECT_LOOP: + ereport(DEBUG4, + (errmsg_internal("SSL: connect loop"))); + break; + case SSL_CB_CONNECT_EXIT: + ereport(DEBUG4, + (errmsg_internal("SSL: connect exit (%d)", args))); + break; + case SSL_CB_READ_ALERT: + ereport(DEBUG4, + (errmsg_internal("SSL: read alert (0x%04x)", args))); + break; + case SSL_CB_WRITE_ALERT: + ereport(DEBUG4, + (errmsg_internal("SSL: write alert (0x%04x)", args))); + break; + } + } + + /* + * Initialize global SSL context. + */ + void + pgtls_initialize(void) + { + struct stat buf; + + if (!SSL_context) + { + SSL_library_init(); + SSL_load_error_strings(); + SSL_context = SSL_CTX_new(SSLv23_method()); + if (!SSL_context) + ereport(FATAL, + (errmsg("could not create SSL context: %s", + SSLerrmessage()))); + + /* + * Load and verify certificate and private key + */ + if (!SSL_CTX_use_certificate_file(SSL_context, + SERVER_CERT_FILE, + SSL_FILETYPE_PEM)) + ereport(FATAL, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not load server certificate file \"%s\": %s", + SERVER_CERT_FILE, SSLerrmessage()))); + + if (stat(SERVER_PRIVATE_KEY_FILE, &buf) == -1) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not access private key file \"%s\": %m", + SERVER_PRIVATE_KEY_FILE))); + + /* + * Require no public access to key file. + * + * XXX temporarily suppress check when on Windows, because there may + * not be proper support for Unix-y file permissions. Need to think + * of a reasonable check to apply on Windows. (See also the data + * directory permission check in postmaster.c) + */ + #if !defined(WIN32) && !defined(__CYGWIN__) + if (!S_ISREG(buf.st_mode) || (buf.st_mode & (S_IRWXG | S_IRWXO)) || + buf.st_uid != geteuid()) + ereport(FATAL, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("unsafe permissions on private key file \"%s\"", + SERVER_PRIVATE_KEY_FILE), + errdetail("File must be owned by the database user and must have no permissions for \"group\" or \"other\"."))); + #endif + + if (!SSL_CTX_use_PrivateKey_file(SSL_context, + SERVER_PRIVATE_KEY_FILE, + SSL_FILETYPE_PEM)) + ereport(FATAL, + (errmsg("could not load private key file \"%s\": %s", + SERVER_PRIVATE_KEY_FILE, SSLerrmessage()))); + + if (!SSL_CTX_check_private_key(SSL_context)) + ereport(FATAL, + (errmsg("check of private key failed: %s", + SSLerrmessage()))); + } + + /* 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 | SSL_OP_NO_SSLv2); + + /* setup the allowed cipher list */ + if (SSL_CTX_set_cipher_list(SSL_context, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH") != 1) + elog(FATAL, "could not set the cipher list (no valid ciphers available)"); + + /* + * Require and check client certificates only if we have a root.crt file. + */ + if (!SSL_CTX_load_verify_locations(SSL_context, ROOT_CERT_FILE, NULL)) + { + /* Not fatal - we do not require client certificates */ + ereport(LOG, + (errmsg("could not load root certificate file \"%s\": %s", + ROOT_CERT_FILE, SSLerrmessage()), + errdetail("Will not verify client certificates."))); + } + else + { + /* + * Check the Certificate Revocation List (CRL) if file exists. + * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html + */ + X509_STORE *cvstore = SSL_CTX_get_cert_store(SSL_context); + + if (cvstore) + { + if (X509_STORE_load_locations(cvstore, ROOT_CRL_FILE, NULL) != 0) + /* setting the flags to check against the complete CRL chain */ + X509_STORE_set_flags(cvstore, + X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); + else + { + /* Not fatal - we do not require CRL */ + ereport(LOG, + (errmsg("SSL Certificate Revocation List (CRL) file \"%s\" not found, skipping: %s", + ROOT_CRL_FILE, SSLerrmessage()), + errdetail("Will not check certificates against CRL."))); + } + } + + SSL_CTX_set_verify(SSL_context, + (SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT | + SSL_VERIFY_CLIENT_ONCE), + verify_cb); + } + } + + /* + * Destroy global SSL context. + */ + void + pgtls_destroy(void) + { + if (SSL_context) + { + SSL_CTX_free(SSL_context); + SSL_context = NULL; + } + } + + /* + * Attempt to negotiate SSL connection. + */ + int + pgtls_open_server(Port *port) + { + int r; + int err; + + Assert(!port->ssl); + Assert(!port->peer); + + if (!(port->ssl = SSL_new(SSL_context))) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not initialize SSL connection: %s", + SSLerrmessage()))); + pgtls_close(port); + return -1; + } + if (!my_SSL_set_fd(port->ssl, port->sock)) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not set SSL socket: %s", + SSLerrmessage()))); + pgtls_close(port); + return -1; + } + + aloop: + r = SSL_accept(port->ssl); + if (r <= 0) + { + err = SSL_get_error(port->ssl, r); + switch (err) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + #ifdef WIN32 + pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl), + (err == SSL_ERROR_WANT_READ) ? + FD_READ | FD_CLOSE | FD_ACCEPT : FD_WRITE | FD_CLOSE); + #endif + goto aloop; + case SSL_ERROR_SYSCALL: + if (r < 0) + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("could not accept SSL connection: %m"))); + else + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not accept SSL connection: EOF detected"))); + break; + case SSL_ERROR_SSL: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not accept SSL connection: %s", + SSLerrmessage()))); + break; + case SSL_ERROR_ZERO_RETURN: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not accept SSL connection: EOF detected"))); + break; + default: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("unrecognized SSL error code: %d", + err))); + break; + } + pgtls_close(port); + return -1; + } + + port->count = 0; + + /* get client certificate, if available. */ + port->peer = SSL_get_peer_certificate(port->ssl); + if (port->peer == NULL) + { + strncpy(port->peer_dn, "(anonymous)", sizeof(port->peer_dn)); + strncpy(port->peer_cn, "(anonymous)", sizeof(port->peer_cn)); + } + else + { + X509_NAME_oneline(X509_get_subject_name(port->peer), + port->peer_dn, sizeof(port->peer_dn)); + port->peer_dn[sizeof(port->peer_dn) - 1] = '\0'; + X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer), + NID_commonName, port->peer_cn, sizeof(port->peer_cn)); + port->peer_cn[sizeof(port->peer_cn) - 1] = '\0'; + } + ereport(DEBUG2, + (errmsg("SSL connection from \"%s\"", port->peer_cn))); + + /* set up debugging/info callback */ + SSL_CTX_set_info_callback(SSL_context, info_cb); + + return 0; + } + + /* + * Close SSL connection. + */ + void + pgtls_close(Port *port) + { + if (port->ssl) + { + SSL_shutdown(port->ssl); + SSL_free(port->ssl); + port->ssl = NULL; + } + + if (port->peer) + { + X509_free(port->peer); + port->peer = NULL; + } + } + + /* + * Obtain reason string for last SSL error + * + * Some caution is needed here since ERR_reason_error_string will + * return NULL if it doesn't recognize the error code. We don't + * want to return NULL ever. + */ + static const char * + SSLerrmessage(void) + { + unsigned long errcode; + const char *errreason; + static char errbuf[32]; + + errcode = ERR_get_error(); + if (errcode == 0) + return "No SSL error reported"; + errreason = ERR_reason_error_string(errcode); + if (errreason != NULL) + return errreason; + snprintf(errbuf, sizeof(errbuf), "SSL error code %lu", errcode); + return errbuf; + } + Index: src/interfaces/libpq/fe-secure-gnutls.c ========================================================================= *** src/interfaces/libpq/fe-secure-gnutls.c.orig Thu May 4 15:35:59 2006 --- src/interfaces/libpq/fe-secure-gnutls.c Thu May 4 14:02:16 2006 *************** *** 0 **** --- 1,756 ---- + /*------------------------------------------------------------------------- + * + * fe-secure.c + * GnuTLS specific functions related to setting up a secure + * connection to the backend. Secure connections are expected to + * provide confidentiality, message integrity and endpoint + * authentication. + * + * + * Portions Copyright (c) 1996-2005, 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.74 2006/01/24 16:38:42 tgl Exp $ + * + *------------------------------------------------------------------------- + */ + + #include "postgres_fe.h" + + #include "libpq-fe.h" + #include "libpq-int.h" + + #ifdef WIN32 + #include "win32.h" + #else + #include + #endif + #include + + #ifdef ENABLE_THREAD_SAFETY + #ifdef WIN32 + #include "pthread-win32.h" + #else + #include + #endif + #endif + + #include + #include + + #ifdef ENABLE_THREAD_SAFETY + #include + #endif + + #include "libpq-tls.h" + + /* Define this for extra debugging info */ + /* #define GNUTLS_DEBUG */ + + static int pq_gnutls_init_ssl_system(PGconn *conn); + static char *pq_gnutls_errmessage(int err); + static void pq_gnutls_errfree(char *buf); + + static int SSL_verify_server; + static int SSL_handshake; + static gnutls_certificate_credentials SSL_cred; /* Credentials for connecting */ + + /* ------------------------------------------------------------ */ + /* Procedures common to all secure sessions */ + /* ------------------------------------------------------------ */ + + + /* + * Read data from a secure connection. + */ + ssize_t + pqTLS_read(PGconn *conn, void *ptr, size_t len) + { + ssize_t n; + + rloop: + if( SSL_handshake ) + n = gnutls_handshake(conn->ssl); + else + n = gnutls_record_recv(conn->ssl, ptr, len); + if( n < 0 ) + { + if( n == GNUTLS_E_INTERRUPTED || + n == GNUTLS_E_AGAIN ) + { + /* + * 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. + */ + if( gnutls_record_get_direction(conn->ssl) == 0 ) /* read */ + n = 0; + else + goto rloop; + } + else if( n == GNUTLS_E_REHANDSHAKE ) + { + /* Server wants a rehandshake. */ + SSL_handshake = true; + goto rloop; + } + else + { + char *err = pq_gnutls_errmessage(n); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), err); + pq_gnutls_errfree(err); + SOCK_ERRNO_SET(ECONNRESET); + n = -1; + } + } + /* If we get here, the handshake has completed */ + else if( SSL_handshake ) + { + SSL_handshake = false; + /* Go back and read actual data now */ + goto rloop; + } + return n; + } + + /* + * Write data to a secure connection. + */ + ssize_t + pqTLS_write(PGconn *conn, const void *ptr, size_t len, bool *got_epipe) + { + ssize_t n; + + *got_epipe = false; + if( SSL_handshake ) + n = gnutls_handshake(conn->ssl); + else + n = gnutls_record_send(conn->ssl, ptr, len); + if( n < 0 ) + { + #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32) + if (SOCK_ERRNO == EPIPE) + *got_epipe = true; + #endif + if( n == GNUTLS_E_INTERRUPTED || + n == GNUTLS_E_AGAIN ) + { + /* + * Returning 0 here causes caller to wait + * for write-ready, which is may not really + * the right thing, but it's the best we can + * do. + */ + n = 0; + } + else + { + char *err = pq_gnutls_errmessage(n); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), err); + pq_gnutls_errfree(err); + SOCK_ERRNO_SET(ECONNRESET); + n = -1; + SSL_handshake = false; + } + } + else + SSL_handshake = false; + + return n; + } + + /* ------------------------------------------------------------ */ + /* SSL specific code */ + /* ------------------------------------------------------------ */ + + /* + * Certificate verification callback + * + * This callback allows us to log intermediate problems during + * verification, but there doesn't seem to be a clean way to get + * our PGconn * structure. So we can't log anything! + * + * This callback also allows us to override the default acceptance + * criteria (e.g., accepting self-signed or expired certs), but + * for now we accept the default checks. + */ + #ifdef NOT_USED + static int + pq_gnutls_verify_cb(int ok, X509_STORE_CTX *ctx) + { + return ok; + } + #endif + + static void + pq_gnutls_load_file( PGconn *conn, const char *filename, gnutls_datum *datum ) + { + int filesize; + FILE *f; + + datum->size = 0; + datum->data = NULL; + + if( (f = fopen( filename, "r" )) == NULL ) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not open file \"%s\": %m\n"), + filename); + return; + } + if( (filesize = fseek( f, SEEK_END, 0 )) < 0 ) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not seek file \"%s\": %m\n"), + filename); + return; + } + if( fseek( f, SEEK_SET, 0 ) < 0 ) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not seek file \"%s\": %m\n"), + filename); + return; + } + datum->data = malloc( filesize ); + if( fread( datum->data, filesize, 1, f ) != 1 ) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read file \"%s\": %m\n"), + filename); + return; + } + fclose(f); + datum->size = filesize; + return; + } + /* + * Callback used by SSL to load client cert and key. + * This callback is only called when the server wants a + * client cert. + * + * Must return 0 on success. + */ + static int + pg_gnutls_client_cert_cb(gnutls_session session, const gnutls_datum * req_ca_rdn, int nreqs, + const gnutls_pk_algorithm * pk_algos, + int pk_algos_length, gnutls_retr_st *st) + { + gnutls_certificate_type type; + gnutls_x509_crt ssl_clientcrt; + gnutls_x509_privkey ssl_clientkey; + + char homedir[MAXPGPATH]; + struct stat buf; + + char crtfile[MAXPGPATH], keyfile[MAXPGPATH]; + gnutls_datum crtdatum, keydatum; + + PGconn *conn = (PGconn *) gnutls_session_get_ptr(session); + + type = gnutls_certificate_type_get (session); + if (type != GNUTLS_CRT_X509) + return -1; + + /* Setup these values so we can return 0 at any time to try without cert */ + st->type = type; + st->ncerts = 0; + st->cert.x509 = NULL; + st->key.x509 = NULL; + st->deinit_all = 0; + + if (!pqGetHomeDirectory(homedir, sizeof(homedir))) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not get user information\n")); + return 0; + } + + /* find the user certificate */ + snprintf(crtfile, sizeof(crtfile), "%s/%s", homedir, USERCERTFILE); + if (stat(crtfile, &buf) == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not find certificate file \"%s\"\n"), + crtfile); + return 0; + } + /* find the user key */ + snprintf(keyfile, sizeof(keyfile), "%s/%s", homedir, USERKEYFILE); + if (stat(keyfile, &buf) == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate present, but not private key file \"%s\"\n"), + keyfile); + return 0; + } + #ifndef WIN32 + if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) || + buf.st_uid != geteuid()) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("private key file \"%s\" has wrong permissions\n"), + keyfile); + return 0; + } + #endif + pq_gnutls_load_file( conn, crtfile, &crtdatum ); + pq_gnutls_load_file( conn, keyfile, &keydatum ); + + if( crtdatum.size && keydatum.size ) + { + int err; + + gnutls_x509_crt_init( &ssl_clientcrt ); + gnutls_x509_privkey_init( &ssl_clientkey ); + + err = gnutls_x509_crt_import( ssl_clientcrt, &crtdatum, GNUTLS_X509_FMT_PEM ); + if( err == 0 ) + err = gnutls_x509_privkey_import( ssl_clientkey, &keydatum, GNUTLS_X509_FMT_PEM ); + if( err == 0 ) + { + st->ncerts = 1; + st->key.x509 = ssl_clientkey; + st->cert.x509 = malloc( sizeof(ssl_clientcrt)); /* No easy way to clean this up */ + st->cert.x509[0] = ssl_clientcrt; + st->deinit_all = 1; + } + else + { + char *error = pq_gnutls_errmessage(err); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not parse key/cert files: %s\n"), + error); + pq_gnutls_errfree(error); + } + } + if( crtdatum.data ) + free( crtdatum.data ); + if( keydatum.data ) + free( keydatum.data ); + + + return 0; + } + + #ifdef ENABLE_THREAD_SAFETY + GCRY_THREAD_OPTION_PTHREAD_IMPL; + #endif + + #ifdef GNUTLS_DEBUG + static void + pq_gnutls_log( int level, const char *str ) + { + fprintf( stderr, "%d: %s\n", level, str ); + } + #endif + + static int + pq_gnutls_init_ssl_system(PGconn *conn) + { + int error; + + #ifdef ENABLE_THREAD_SAFETY + /* GnuTLS is thread-safe by design, but gcrypt may not be. Here we assume that the lib is pthread */ + gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + #endif + /* Do gnutls_check_version(LIBGNUTLS_VERSION) ? */ + error = gnutls_global_init(); /* Check for error */ + if (error) + { + char *err = pq_gnutls_errmessage(error); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not create SSL context: %s\n"), + err); + pq_gnutls_errfree(err); + return -1; + } + #ifdef GNUTLS_DEBUG + gnutls_global_set_log_function( pq_gnutls_log ); + gnutls_global_set_log_level (9); + #endif + return 0; + } + + /* + * Initialize global SSL context. + */ + int + pqTLS_initialize(PGconn *conn) + { + struct stat buf; + char homedir[MAXPGPATH]; + char fnbuf[MAXPGPATH]; + + if (pq_gnutls_init_ssl_system(conn)) + return -1; + + gnutls_certificate_allocate_credentials (&SSL_cred); + + /* Set up to verify server cert, if root.crt is present */ + if (pqGetHomeDirectory(homedir, sizeof(homedir))) + { + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOTCERTFILE); + if (stat(fnbuf, &buf) == 0) + { + int error; + error = gnutls_certificate_set_x509_trust_file (SSL_cred, fnbuf, GNUTLS_X509_FMT_PEM); + if( error < 0 ) + { + char *err = pq_gnutls_errmessage(error); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read root certificate file \"%s\": %s\n"), + fnbuf, err); + pq_gnutls_errfree(err); + return -1; + } + SSL_verify_server = TRUE; + } + } + + gnutls_certificate_client_set_retrieve_function(SSL_cred, pg_gnutls_client_cert_cb); + + return 0; + } + + /* + * Destroy global SSL context. + */ + void + pqTLS_destroy(void) + { + if (SSL_cred) + { + gnutls_certificate_free_credentials (SSL_cred); + SSL_cred = NULL; + + gnutls_global_deinit (); + } + } + + /* + * Initialise the SSL on a single socket + */ + + PostgresPollingStatusType + pqTLS_initconn(PGconn *conn) + { + const int kx_prio[] = { GNUTLS_KX_DHE_RSA, GNUTLS_KX_DHE_DSS, 0 }; + /* Only do X509 certificates */ + const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; + const int cipher_type_priority[] = { GNUTLS_CIPHER_AES_256_CBC, GNUTLS_CIPHER_AES_128_CBC, GNUTLS_CIPHER_ARCFOUR_128, 0 }; + const int mac_type_priority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_RMD160, 0 }; + + /* First time through? */ + if (conn->ssl == NULL) + { + int error = gnutls_init ( (gnutls_session*)(&conn->ssl), GNUTLS_CLIENT); + if (error) + { + char *err = pq_gnutls_errmessage(error); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not create SSL context: %s\n"), + err); + pq_gnutls_errfree(err); + + return PGRES_POLLING_FAILED; + } + // gnutls_credentials_set (conn->ssl, GNUTLS_CRD_ANON, SSL_anoncred); + + /* Use default priorities */ + gnutls_set_default_priority (conn->ssl); + /* Server uses: ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH. This means: + * - No anonymous DH cipher suites. + * - Low security keys + * - No export algorithms + * - No MD5 */ + gnutls_kx_set_priority (conn->ssl, kx_prio); /* DH keyexchange only */ + gnutls_certificate_type_set_priority (conn->ssl, cert_type_priority); /* X.509 certs only */ + gnutls_cipher_set_priority( conn->ssl, cipher_type_priority ); + gnutls_mac_set_priority( conn->ssl, mac_type_priority ); + + gnutls_transport_set_ptr (conn->ssl, (gnutls_transport_ptr) conn->sock); + gnutls_session_set_ptr (conn->ssl, conn); + + /* set up mechanism to provide client certificate, if available */ + gnutls_credentials_set (conn->ssl, GNUTLS_CRD_CERTIFICATE, SSL_cred); + /* + * Initialize errorMessage to empty. This allows pqTLS_open_client() to + * detect whether pg_gnutls_client_cert_cb() has stored a message. + */ + resetPQExpBuffer(&conn->errorMessage); + } + return PGRES_POLLING_OK; + } + + /* + * Attempt to negotiate SSL connection. + */ + PostgresPollingStatusType + pqTLS_open_client(PGconn *conn) + { + int err; + int cert_list_size, status, size; + gnutls_x509_crt cert; + + err = gnutls_handshake(conn->ssl); + if (err < 0) + { + if( err == GNUTLS_E_AGAIN || + err == GNUTLS_E_INTERRUPTED ) + { + if( gnutls_record_get_direction(conn->ssl) == 0 ) + return PGRES_POLLING_READING; + else + return PGRES_POLLING_WRITING; + } + if( gnutls_error_is_fatal(err) ) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), + gnutls_strerror(err)); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + else + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Unexpected non-fatal SSL error: %s\n"), + gnutls_strerror(err)); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + } + + /* If asked to verify server, do so */ + if( SSL_verify_server ) + { + err = gnutls_certificate_verify_peers2 (conn->ssl, &status); + if(err < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL verification error: %s\n"), + gnutls_strerror(err)); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + if( status ) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Server cert verification failure: %d\n"), + status); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + } + /* pull out server distinguished and common names */ + const gnutls_datum *peer = gnutls_certificate_get_peers (conn->ssl, &cert_list_size); + if (peer == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate could not be obtained\n")); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + err = gnutls_x509_crt_init (&cert); + if(err < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("gnutls_x509_crt_init failure: %s\n"), + gnutls_strerror(err)); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + + err = gnutls_x509_crt_import (cert, &peer[0], GNUTLS_X509_FMT_DER); + if(err < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("gnutls_x509_crt_import failure: %s\n"), + gnutls_strerror(err)); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + size = sizeof(conn->peer_dn); + gnutls_x509_crt_get_dn (cert, conn->peer_dn, &size); + conn->peer_dn[sizeof(conn->peer_dn) - 1] = '\0'; + + size = sizeof(conn->peer_cn); + gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, conn->peer_cn, &size ); + conn->peer_cn[sizeof(conn->peer_cn) - 1] = '\0'; + + gnutls_x509_crt_deinit( cert ); + /* SSL handshake is complete */ + return PGRES_POLLING_OK; + } + + /* + * Close SSL connection. + */ + void + pqTLS_close(PGconn *conn) + { + if (conn->ssl) + { + #ifdef NOT_USED + conn->peer = NULL; /* Is allocated as part of session, no need to free */ + #endif + gnutls_bye (conn->ssl, GNUTLS_SHUT_RDWR); + gnutls_deinit (conn->ssl); + conn->ssl = NULL; + } + } + + /* + * Obtain reason string for last SSL error + * + * Some caution is needed here since ERR_reason_error_string will + * return NULL if it doesn't recognize the error code. We don't + * want to return NULL ever. + */ + #define SSL_ERR_LEN 128 + + static char * + pq_gnutls_errmessage(int errcode) + { + const char *errreason; + char *errbuf; + + errbuf = malloc(SSL_ERR_LEN); + if (!errbuf) + return pqTLS_ssl_nomem; + if (errcode >= 0) + { + strcpy(errbuf, "No SSL error reported"); + return errbuf; + } + errreason = gnutls_strerror(errcode); + if (errreason != NULL) + { + strncpy(errbuf, errreason, SSL_ERR_LEN - 1); + errbuf[SSL_ERR_LEN - 1] = '\0'; + return errbuf; + } + snprintf(errbuf, SSL_ERR_LEN, "SSL error code %u", errcode); + return errbuf; + } + + static void + pq_gnutls_errfree(char *buf) + { + if (buf != pqTLS_ssl_nomem) + free(buf); + } + + /* + * Return pointer to SSL object. + * + * Unfortunatly, this function was in the past defined to return a + * pointer to an OpenSSL structure, so here we just return NULL. + * + */ + void * + PQgetssl(PGconn *conn) + { + return NULL; + } + + /* + * Exported function to allow application to tell us it's already + * initialized OpenSSL. GnuTLS doesn't need that info. + */ + void + PQinitSSL(int do_init) + { + } + + int + pqTLS_pending(PGconn *conn) + { + return gnutls_record_check_pending(conn->ssl); + } + + PGresult * + pqTLS_gettlsinfo( PGconn *conn, PGresult *res ) + { + size_t keysize; + char buf[24]; + + pqsecure_tlsinfo_add_row( res, "tls_library", "GnuTLS" ); + pqsecure_tlsinfo_add_row( res, "tls_library_version", gnutls_check_version(NULL) ); + pqsecure_tlsinfo_add_row( res, "tls_sslmode", conn->sslmode ); + pqsecure_tlsinfo_add_row( res, "tls_active", conn->ssl ? "yes" : "no" ); + + if( !conn->ssl ) + return res; + + keysize = gnutls_cipher_get_key_size(gnutls_cipher_get(conn->ssl)); + sprintf( buf, "%d bits", keysize*8 ); + pqsecure_tlsinfo_add_row( res, "tls_verify_server", SSL_verify_server ? "yes" : "no" ); + pqsecure_tlsinfo_add_row( res, "tls_peerdn", conn->peer_dn ); + pqsecure_tlsinfo_add_row( res, "tls_peercn", conn->peer_cn ); + pqsecure_tlsinfo_add_row( res, "tls_protocol", gnutls_protocol_get_name(gnutls_protocol_get_version(conn->ssl)) ); + pqsecure_tlsinfo_add_row( res, "tls_cipher", gnutls_cipher_suite_get_name(gnutls_kx_get(conn->ssl),gnutls_cipher_get(conn->ssl),gnutls_mac_get(conn->ssl))); + pqsecure_tlsinfo_add_row( res, "tls_keysize", buf ); + pqsecure_tlsinfo_add_row( res, "tls_kx", gnutls_kx_get_name(gnutls_kx_get(conn->ssl)) ); + pqsecure_tlsinfo_add_row( res, "tls_mac", gnutls_mac_get_name(gnutls_mac_get(conn->ssl)) ); + pqsecure_tlsinfo_add_row( res, "tls_compression", gnutls_compression_get_name(gnutls_compression_get(conn->ssl)) ); + pqsecure_tlsinfo_add_row( res, "tls_certtype", gnutls_certificate_type_get_name(gnutls_certificate_type_get(conn->ssl)) ); + + { + int len; + const gnutls_datum *peer = gnutls_certificate_get_peers(conn->ssl, &len); + if( len > 0 ) + { + gnutls_x509_crt cert; + int err = gnutls_x509_crt_init (&cert); + if(err == 0) + { + err = gnutls_x509_crt_import (cert, &peer[0], GNUTLS_X509_FMT_DER); + if(err == 0) + { + int crtlen = 4096; + char *ptr = malloc(crtlen); + err = gnutls_x509_crt_export (cert, GNUTLS_X509_FMT_PEM, ptr, &crtlen ); + if( err == 0 ) + pqsecure_tlsinfo_add_row( res, "x509_peercert", ptr ); + free(ptr); + } + gnutls_x509_crt_deinit(cert); + } + } + } + { + const gnutls_datum *ours = gnutls_certificate_get_ours(conn->ssl); + if( ours ) + { + gnutls_x509_crt cert; + int err = gnutls_x509_crt_init (&cert); + if(err == 0) + { + err = gnutls_x509_crt_import (cert, ours, GNUTLS_X509_FMT_DER); + if(err == 0) + { + int crtlen = 4096; + char *ptr = malloc(crtlen); + err = gnutls_x509_crt_export (cert, GNUTLS_X509_FMT_PEM, ptr, &crtlen ); + if( err == 0 ) + pqsecure_tlsinfo_add_row( res, "x509_clientcert", ptr ); + free(ptr); + } + gnutls_x509_crt_deinit(cert); + } + } + } + + return res; + } Index: src/interfaces/libpq/fe-secure-openssl.c ========================================================================= *** src/interfaces/libpq/fe-secure-openssl.c.orig Thu May 4 15:35:59 2006 --- src/interfaces/libpq/fe-secure-openssl.c Thu May 4 13:04:32 2006 *************** *** 0 **** --- 1,922 ---- + /*------------------------------------------------------------------------- + * + * fe-secure.c + * OpenSSL functions related to setting up a secure connection to the + * backend. Secure connections are expected to provide + * confidentiality, message integrity and endpoint authentication. + * + * + * Portions Copyright (c) 1996-2006, 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.75 2006/03/05 15:59:09 momjian Exp $ + * + *------------------------------------------------------------------------- + */ + + #include "postgres_fe.h" + + #include + #include + #include + + #include "libpq-fe.h" + #include "libpq-int.h" + #include "fe-auth.h" + #include "pqsignal.h" + + #ifdef WIN32 + #include "win32.h" + #else + #include + #include + #include + #include + #ifdef HAVE_NETINET_TCP_H + #include + #endif + #include + #endif + #include + + #ifdef ENABLE_THREAD_SAFETY + #ifdef WIN32 + #include "pthread-win32.h" + #else + #include + #endif + #endif + + #ifndef HAVE_STRDUP + #include "strdup.h" + #endif + + #include + #include + + #include "libpq-tls.h" + + #ifdef NOT_USED + static int pq_openssl_verify_peer(PGconn *); + #endif + static int pq_openssl_verify_cb(int ok, X509_STORE_CTX *ctx); + static int pq_openssl_client_cert_cb(SSL *, X509 **, EVP_PKEY **); + static int pq_openssl_init_ssl_system(PGconn *conn); + static char *pq_openssl_errmessage(void); + static void pq_openssl_errfree(char *buf); + static bool pq_initssllib = TRUE; + + static SSL_CTX *SSL_context = NULL; + + /* ------------------------------------------------------------ */ + /* Procedures common to all secure sessions */ + /* ------------------------------------------------------------ */ + + + /* + * Read data from a secure connection. + */ + ssize_t + pqTLS_read(PGconn *conn, void *ptr, size_t len) + { + ssize_t n; + + int err; + + rloop: + n = SSL_read(conn->ssl, ptr, len); + err = SSL_get_error(conn->ssl, n); + switch (err) + { + case SSL_ERROR_NONE: + break; + case SSL_ERROR_WANT_READ: + n = 0; + break; + 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. + */ + goto rloop; + case SSL_ERROR_SYSCALL: + { + char sebuf[256]; + + if (n == -1) + printfPQExpBuffer(&conn->errorMessage, + 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")); + SOCK_ERRNO_SET(ECONNRESET); + n = -1; + } + break; + } + case SSL_ERROR_SSL: + { + char *err = pq_openssl_errmessage(); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), err); + pq_openssl_errfree(err); + } + /* fall through */ + case SSL_ERROR_ZERO_RETURN: + SOCK_ERRNO_SET(ECONNRESET); + n = -1; + break; + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unrecognized SSL error code: %d\n"), + err); + n = -1; + break; + } + return n; + } + + /* + * Write data to a secure connection. + */ + ssize_t + pqTLS_write(PGconn *conn, const void *ptr, size_t len, bool *got_epipe) + { + ssize_t n; + + *got_epipe = false; + + int err; + + n = SSL_write(conn->ssl, ptr, len); + err = SSL_get_error(conn->ssl, n); + switch (err) + { + case SSL_ERROR_NONE: + break; + case SSL_ERROR_WANT_READ: + /* + * 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. + */ + n = 0; + break; + case SSL_ERROR_WANT_WRITE: + n = 0; + break; + case SSL_ERROR_SYSCALL: + { + char sebuf[256]; + if (n == -1) + { + #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32) + if (SOCK_ERRNO == EPIPE) + *got_epipe = true; + #endif + printfPQExpBuffer(&conn->errorMessage, + 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")); + SOCK_ERRNO_SET(ECONNRESET); + n = -1; + } + break; + } + case SSL_ERROR_SSL: + { + char *err = pq_openssl_errmessage(); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), err); + pq_openssl_errfree(err); + } + /* fall through */ + case SSL_ERROR_ZERO_RETURN: + SOCK_ERRNO_SET(ECONNRESET); + n = -1; + break; + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unrecognized SSL error code: %d\n"), + err); + n = -1; + break; + } + + return n; + } + + /* ------------------------------------------------------------ */ + /* SSL specific code */ + /* ------------------------------------------------------------ */ + + /* + * Certificate verification callback + * + * This callback allows us to log intermediate problems during + * verification, but there doesn't seem to be a clean way to get + * our PGconn * structure. So we can't log anything! + * + * This callback also allows us to override the default acceptance + * criteria (e.g., accepting self-signed or expired certs), but + * for now we accept the default checks. + */ + static int + pq_openssl_verify_cb(int ok, X509_STORE_CTX *ctx) + { + return ok; + } + + #ifdef NOT_USED + /* + * Verify that common name resolves to peer. + */ + static int + pq_openssl_verify_peer(PGconn *conn) + { + struct hostent *h = NULL; + struct sockaddr addr; + struct sockaddr_in *sin; + ACCEPT_TYPE_ARG3 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]; + + 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) + 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; + } + + /* + * 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; + } + + /* 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); + } + + return -1; + } + #endif /* NOT_USED */ + + /* + * Callback used by SSL to load client cert and key. + * This callback is only called when the server wants a + * client cert. + * + * Must return 1 on success, 0 on no data or error. + */ + static int + pq_openssl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) + { + char homedir[MAXPGPATH]; + struct stat buf; + + #ifndef WIN32 + struct stat buf2; + #endif + char fnbuf[MAXPGPATH]; + FILE *fp; + PGconn *conn = (PGconn *) SSL_get_app_data(ssl); + int (*cb) () = NULL; /* how to read user password */ + char sebuf[256]; + + if (!pqGetHomeDirectory(homedir, sizeof(homedir))) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not get user information\n")); + return 0; + } + + /* read the user certificate */ + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USERCERTFILE); + if ((fp = 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; + } + if (PEM_read_X509(fp, x509, NULL, NULL) == NULL) + { + char *err = pq_openssl_errmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read certificate file \"%s\": %s\n"), + fnbuf, err); + pq_openssl_errfree(err); + fclose(fp); + return 0; + } + fclose(fp); + + /* read the user key */ + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USERKEYFILE); + if (stat(fnbuf, &buf) == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate present, but not private key file \"%s\"\n"), + fnbuf); + return 0; + } + #ifndef WIN32 + if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) || + buf.st_uid != geteuid()) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("private key file \"%s\" has wrong permissions\n"), + fnbuf); + return 0; + } + #endif + if ((fp = fopen(fnbuf, "r")) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not open private key file \"%s\": %s\n"), + fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); + return 0; + } + #ifndef WIN32 + 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); + return 0; + } + #endif + if (PEM_read_PrivateKey(fp, pkey, cb, NULL) == NULL) + { + char *err = pq_openssl_errmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read private key file \"%s\": %s\n"), + fnbuf, err); + pq_openssl_errfree(err); + fclose(fp); + return 0; + } + fclose(fp); + + /* verify that the cert and key go together */ + if (!X509_check_private_key(*x509, *pkey)) + { + char *err = pq_openssl_errmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate does not match private key file \"%s\": %s\n"), + fnbuf, err); + pq_openssl_errfree(err); + return 0; + } + + return 1; + } + + #ifdef ENABLE_THREAD_SAFETY + + static unsigned long + pq_threadidcallback(void) + { + /* + * This is not starndard-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]); + } + #endif /* ENABLE_THREAD_SAFETY */ + + static int + pq_openssl_init_ssl_system(PGconn *conn) + { + #ifdef ENABLE_THREAD_SAFETY + #ifndef WIN32 + 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) + pthread_mutex_init(&init_mutex, NULL); + InterlockedExchange(&mutex_initlock, 0); + } + #endif + pthread_mutex_lock(&init_mutex); + + 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) + { + pthread_mutex_unlock(&init_mutex); + return -1; + } + for (i = 0; i < CRYPTO_num_locks(); i++) + pthread_mutex_init(&pq_lockarray[i], NULL); + + CRYPTO_set_locking_callback(pq_lockingcallback); + } + #endif + if (!SSL_context) + { + if (pq_initssllib) + { + SSL_library_init(); + SSL_load_error_strings(); + } + SSL_context = SSL_CTX_new(TLSv1_method()); + if (!SSL_context) + { + char *err = pq_openssl_errmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not create SSL context: %s\n"), + err); + pq_openssl_errfree(err); + #ifdef ENABLE_THREAD_SAFETY + pthread_mutex_unlock(&init_mutex); + #endif + return -1; + } + } + #ifdef ENABLE_THREAD_SAFETY + pthread_mutex_unlock(&init_mutex); + #endif + return 0; + } + + /* + * Initialize global SSL context. + */ + int + pqTLS_initialize(PGconn *conn) + { + struct stat buf; + char homedir[MAXPGPATH]; + char fnbuf[MAXPGPATH]; + + if (pq_openssl_init_ssl_system(conn)) + return -1; + + /* Set up to verify server cert, if root.crt is present */ + if (pqGetHomeDirectory(homedir, sizeof(homedir))) + { + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOTCERTFILE); + if (stat(fnbuf, &buf) == 0) + { + if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL)) + { + char *err = pq_openssl_errmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read root certificate file \"%s\": %s\n"), + fnbuf, err); + pq_openssl_errfree(err); + return -1; + } + + SSL_CTX_set_verify(SSL_context, SSL_VERIFY_PEER, pq_openssl_verify_cb); + } + } + + /* set up mechanism to provide client certificate, if available */ + SSL_CTX_set_client_cert_cb(SSL_context, pq_openssl_client_cert_cb); + + return 0; + } + + /* + * Destroy global SSL context. + */ + void + pqTLS_destroy(void) + { + if (SSL_context) + { + SSL_CTX_free(SSL_context); + SSL_context = NULL; + } + } + + /* + * Initialise the SSL on a single socket + */ + + PostgresPollingStatusType + pqTLS_initconn(PGconn *conn) + { + /* First time through? */ + if (conn->ssl == NULL) + { + if (!(conn->ssl = SSL_new(SSL_context)) || + !SSL_set_app_data(conn->ssl, conn) || + !SSL_set_fd(conn->ssl, conn->sock)) + { + char *err = pq_openssl_errmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not establish SSL connection: %s\n"), + err); + pq_openssl_errfree(err); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + + /* + * Initialize errorMessage to empty. This allows pqTLS_open_client() to + * detect whether pq_openssl_client_cert_cb() has stored a message. + */ + resetPQExpBuffer(&conn->errorMessage); + } + return PGRES_POLLING_OK; + } + + /* + * Attempt to negotiate SSL connection. + */ + PostgresPollingStatusType + pqTLS_open_client(PGconn *conn) + { + int r; + + r = SSL_connect(conn->ssl); + if (r <= 0) + { + int err = SSL_get_error(conn->ssl, r); + + switch (err) + { + case SSL_ERROR_WANT_READ: + return PGRES_POLLING_READING; + + case SSL_ERROR_WANT_WRITE: + return PGRES_POLLING_WRITING; + + case SSL_ERROR_SYSCALL: + { + char sebuf[256]; + + if (r == -1) + printfPQExpBuffer(&conn->errorMessage, + 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")); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + case SSL_ERROR_SSL: + { + /* + * If there are problems with the local certificate files, + * these will be detected by pq_openssl_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 = pq_openssl_errmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), + err); + pq_openssl_errfree(err); + } + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unrecognized SSL error code: %d\n"), + err); + pqTLS_close(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 + */ + 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)); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + #endif + + /* pull out server distinguished and common names */ + X509 *peer = SSL_get_peer_certificate(conn->ssl); + if (peer == NULL) + { + char *err = pq_openssl_errmessage(); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate could not be obtained: %s\n"), + err); + pq_openssl_errfree(err); + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + + X509_NAME_oneline(X509_get_subject_name(peer), + 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(peer), + 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 (pq_openssl_verify_peer(conn) == -1) + { + pqTLS_close(conn); + return PGRES_POLLING_FAILED; + } + #endif + + /* SSL handshake is complete */ + return PGRES_POLLING_OK; + } + + /* + * Close SSL connection. + */ + void + pqTLS_close(PGconn *conn) + { + if (conn->ssl) + { + SSL_shutdown(conn->ssl); + SSL_free(conn->ssl); + conn->ssl = NULL; + } + #ifdef NOT_USED + if (conn->peer) + { + X509_free(conn->peer); + conn->peer = NULL; + } + #endif + } + + /* + * Obtain reason string for last SSL error + * + * Some caution is needed here since ERR_reason_error_string will + * return NULL if it doesn't recognize the error code. We don't + * want to return NULL ever. + */ + + #define SSL_ERR_LEN 128 + + static char * + pq_openssl_errmessage(void) + { + unsigned long errcode; + const char *errreason; + char *errbuf; + + errbuf = malloc(SSL_ERR_LEN); + if (!errbuf) + return pqTLS_ssl_nomem; + errcode = ERR_get_error(); + if (errcode == 0) + { + strcpy(errbuf, "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'; + return errbuf; + } + snprintf(errbuf, SSL_ERR_LEN, "SSL error code %lu", errcode); + return errbuf; + } + + static void + pq_openssl_errfree(char *buf) + { + if (buf != pqTLS_ssl_nomem) + free(buf); + } + + /* + * Return pointer to SSL object. + */ + void * + PQgetssl(PGconn *conn) + { + if (!conn) + return NULL; + return conn->ssl; + } + + /* + * Exported function to allow application to tell us it's already + * initialized OpenSSL. + */ + void + PQinitSSL(int do_init) + { + pq_initssllib = do_init; + } + + int + pqTLS_pending(PGconn *conn) + { + return SSL_pending(conn->ssl); + } + + PGresult * + pqTLS_gettlsinfo( PGconn *conn, PGresult *res ) + { + size_t keysize; + SSL_CIPHER *cipher; + // SSL_METHOD *method; + char buf[24]; + + pqsecure_tlsinfo_add_row( res, "tls_library", "OpenSSL" ); + pqsecure_tlsinfo_add_row( res, "tls_library_version", SSLeay_version(SSLEAY_VERSION) ); + pqsecure_tlsinfo_add_row( res, "tls_sslmode", conn->sslmode ); + pqsecure_tlsinfo_add_row( res, "tls_active", conn->ssl ? "yes" : "no" ); + + if( !conn->ssl ) + return res; + + cipher = SSL_get_current_cipher(conn->ssl); + // method = SSL_get_ssl_method(conn->ssl) + + keysize = SSL_CIPHER_get_bits( cipher, NULL); + sprintf( buf, "%d bits", keysize ); + // pqsecure_tlsinfo_add_row( res, "tls_verify_server", SSL_verify_server ? "yes" : "no" ); + pqsecure_tlsinfo_add_row( res, "tls_peerdn", conn->peer_dn ); + pqsecure_tlsinfo_add_row( res, "tls_peercn", conn->peer_cn ); + pqsecure_tlsinfo_add_row( res, "tls_cipher", SSL_CIPHER_get_name(cipher) ); + pqsecure_tlsinfo_add_row( res, "tls_protocol", SSL_CIPHER_get_version(cipher) ); + pqsecure_tlsinfo_add_row( res, "tls_keysize", buf ); + + { + X509 *peer = SSL_get_peer_certificate(conn->ssl); + if( peer ) + { + BIO *bio = BIO_new( BIO_s_mem() ); + if( PEM_write_bio_X509(bio, peer) ) + { + char *data; + long len; + // data = "\0"; + // BIO_write( bio, data, 1 ); /* Make sure NULL terminated */ + len = BIO_get_mem_data(bio, &data); + pqsecure_tlsinfo_add_row( res, "x509_peercert", data ); + BIO_free( bio ); + } + } + } + { + X509 *ours = SSL_get_certificate(conn->ssl); + if( ours ) + { + BIO *bio = BIO_new( BIO_s_mem() ); + if( PEM_write_bio_X509(bio, ours) ) + { + char *data; + long len; + // data = "\0"; + // BIO_write( bio, data, 1 ); /* Make sure NULL terminated */ + len = BIO_get_mem_data(bio, &data); + pqsecure_tlsinfo_add_row( res, "x509_clientcert", data ); + BIO_free( bio ); + } + } + } + + return res; + } Index: src/interfaces/libpq/libpq-tls.h ========================================================================= *** src/interfaces/libpq/libpq-tls.h.orig Thu May 4 15:35:59 2006 --- src/interfaces/libpq/libpq-tls.h Thu May 4 13:04:32 2006 *************** *** 0 **** --- 1,33 ---- + #ifdef USE_SSL + + int pqTLS_initialize(PGconn *); + void pqTLS_destroy(void); + PostgresPollingStatusType pqTLS_open_client(PGconn *); + void pqTLS_close(PGconn *); + ssize_t pqTLS_read(PGconn *conn, void *ptr, size_t len); + ssize_t pqTLS_write(PGconn *conn, const void *ptr, size_t len, bool *got_epipe); + PostgresPollingStatusType pqTLS_initconn(PGconn *conn); + int pqTLS_pending(PGconn *conn); + #ifndef WIN32 + #define USERCERTFILE ".postgresql/postgresql.crt" + #define USERKEYFILE ".postgresql/postgresql.key" + #define ROOTCERTFILE ".postgresql/root.crt" + #else + /* On Windows, the "home" directory is already PostgreSQL-specific */ + #define USERCERTFILE "postgresql.crt" + #define USERKEYFILE "postgresql.key" + #define ROOTCERTFILE "root.crt" + #endif + + int pqTLS_initialize(PGconn *); + void pqTLS_destroy(void); + PostgresPollingStatusType pqTLS_open_client(PGconn *); + void pqTLS_close(PGconn *); + ssize_t pqTLS_read(PGconn *conn, void *ptr, size_t len); + ssize_t pqTLS_write(PGconn *conn, const void *ptr, size_t len, bool *got_epipe); + int pqTLS_pending(PGconn *conn); + PostgresPollingStatusType pqTLS_initconn(PGconn *conn); + PGresult *pqTLS_gettlsinfo( PGconn *conn, PGresult *res ); + + extern char pqTLS_ssl_nomem[]; + #endif Index: src/include/pg_config.h.in =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/pg_config.h.in,v retrieving revision 1.96 diff -c -r1.96 pg_config.h.in *** src/include/pg_config.h.in 29 Apr 2006 20:47:31 -0000 1.96 --- src/include/pg_config.h.in 4 May 2006 13:36:02 -0000 *************** *** 213,218 **** --- 213,224 ---- /* Define to 1 if you have the `eay32' library (-leay32). */ #undef HAVE_LIBEAY32 + /* Define to 1 if you have the `gcrypt' library (-lgcrypt). */ + #undef HAVE_LIBGCRYPT + + /* Define to 1 if you have the `gnutls' library (-lgnutls). */ + #undef HAVE_LIBGNUTLS + /* Define to 1 if you have the `ldap' library (-lldap). */ #undef HAVE_LIBLDAP *************** *** 613,621 **** /* Use replacement snprintf() functions. */ #undef USE_REPL_SNPRINTF ! /* Define to build with (Open)SSL support. (--with-openssl) */ #undef USE_SSL /* Define to select SysV-style semaphores. */ #undef USE_SYSV_SEMAPHORES --- 619,633 ---- /* Use replacement snprintf() functions. */ #undef USE_REPL_SNPRINTF ! /* Define to build with SSL support (select either OpenSSL or GnuTLS) */ #undef USE_SSL + /* Define to build with GnuTLS (SSL) support. (--with-gnutls) */ + #undef USE_SSL_GNUTLS + + /* Define to build with (Open)SSL support. (--with-openssl) */ + #undef USE_SSL_OPENSSL + /* Define to select SysV-style semaphores. */ #undef USE_SYSV_SEMAPHORES