Index: subversion/include/svn_ra_svn.h =================================================================== --- subversion/include/svn_ra_svn.h (revision 11765) +++ subversion/include/svn_ra_svn.h (working copy) @@ -349,13 +349,6 @@ */ svn_error_t *svn_ra_svn_ssl_accept(svn_ra_svn_conn_t *conn, apr_pool_t *pool); -/** This function is only intended for use by client. - * - * Initiate a SSL connection. Assumes that svn_ra_svn_ssl_init has been called. - * @since New in 1.2. - */ -svn_error_t *svn_ra_svn_ssl_connect(svn_ra_svn_conn_t *conn, apr_pool_t *pool); - #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/include/svn_io.h =================================================================== --- subversion/include/svn_io.h (revision 11765) +++ subversion/include/svn_io.h (working copy) @@ -453,7 +453,20 @@ /** Close handler function for a generic stream. */ typedef svn_error_t *(*svn_close_fn_t) (void *baton); +/** + * @since New in 1.2. + * + * Timeout handler function for a generic stream. */ +typedef void (*svn_timeout_fn_t) (void *baton, + apr_interval_time_t interval); +/** + * @since New in 1.2. + * + * Data pending handler function for a generic stream. */ +typedef svn_boolean_t (*svn_data_pending_fn_t) (void *baton); + + /** Creating a generic stream. */ svn_stream_t *svn_stream_create (void *baton, apr_pool_t *pool); @@ -469,7 +482,19 @@ /** Set @a stream's close function to @a close_fn */ void svn_stream_set_close (svn_stream_t *stream, svn_close_fn_t close_fn); +/** + * @since New in 1.2. + * + * Set @a stream's timeout function to @a timeout_fn */ +void svn_stream_set_timeout (svn_stream_t *stream, svn_timeout_fn_t timeout_fn); +/** + * @since New in 1.2. + * + * Set @a stream's data pending function to @a data_pending_fn */ +void svn_stream_set_data_pending (svn_stream_t *stream, + svn_data_pending_fn_t data_pending_fn); + /** Convenience function to create a generic stream which is empty. */ svn_stream_t *svn_stream_empty (apr_pool_t *pool); @@ -516,7 +541,20 @@ /** Close a generic stream. */ svn_error_t *svn_stream_close (svn_stream_t *stream); +/** + * @since New in 1.2. + * + * Sets the timeout of a generic stream. */ +void svn_stream_timeout (svn_stream_t *stream, + apr_interval_time_t interval); +/** + * @since New in 1.2. + * + * Checks if data is pending in a generic stream. */ +svn_boolean_t svn_stream_data_pending (svn_stream_t *stream); + + /** Write to @a stream using a printf-style @a fmt specifier, passed through * @c apr_psprintf using memory from @a pool. */ Index: subversion/libsvn_subr/stream.c =================================================================== --- subversion/libsvn_subr/stream.c (revision 11765) +++ subversion/libsvn_subr/stream.c (working copy) @@ -42,6 +42,8 @@ svn_read_fn_t read_fn; svn_write_fn_t write_fn; svn_close_fn_t close_fn; + svn_timeout_fn_t timeout_fn; + svn_data_pending_fn_t data_pending_fn; }; @@ -58,6 +60,8 @@ stream->read_fn = NULL; stream->write_fn = NULL; stream->close_fn = NULL; + stream->timeout_fn = NULL; + stream->data_pending_fn = NULL; return stream; } @@ -90,6 +94,21 @@ } +void +svn_stream_set_timeout (svn_stream_t *stream, svn_timeout_fn_t timeout_fn) +{ + stream->timeout_fn = timeout_fn; +} + + +void +svn_stream_set_data_pending (svn_stream_t *stream, + svn_data_pending_fn_t data_pending_fn) +{ + stream->data_pending_fn = data_pending_fn; +} + + svn_error_t * svn_stream_read (svn_stream_t *stream, char *buffer, apr_size_t *len) { @@ -115,6 +134,23 @@ } +void +svn_stream_timeout (svn_stream_t *stream, + apr_interval_time_t interval) +{ + assert(stream->timeout_fn != NULL); + stream->timeout_fn (stream->baton, interval); +} + + +svn_boolean_t +svn_stream_data_pending (svn_stream_t *stream) +{ + assert(stream->data_pending_fn != NULL); + return stream->data_pending_fn (stream->baton); +} + + svn_error_t * svn_stream_printf (svn_stream_t *stream, apr_pool_t *pool, Index: subversion/libsvn_ra_svn/client.c =================================================================== --- subversion/libsvn_ra_svn/client.c (revision 11765) +++ subversion/libsvn_ra_svn/client.c (working copy) @@ -39,10 +39,8 @@ #include "svn_md5.h" #include "svn_base64.h" -#include "ra_svn.h" +#include "ra_svn_ssl.h" -#include - typedef struct { svn_ra_svn_conn_t *conn; int protocol_version; @@ -51,7 +49,7 @@ const char *user; const char *hostname; const char *realm_prefix; - SSL_CTX *ssl_ctx; + void *ssl_ctx; } ra_svn_session_baton_t; typedef struct { @@ -273,226 +271,6 @@ return SVN_NO_ERROR; } -/* Format an ASN1 time to a string. */ -static svn_boolean_t asn1time_to_string(ASN1_TIME *tm, char *buffer, - apr_size_t len) -{ - svn_boolean_t retval = FALSE; - if (len--) - { - BIO *mem = BIO_new(BIO_s_mem()); - if (mem) - { - if (ASN1_TIME_print(mem, tm)) - { - long data_len; - char *data; - data_len = BIO_get_mem_data(mem, &data); - if (data_len < len) - len = data_len; - memcpy(buffer, data, len); - buffer[len] = 0; - retval = TRUE; - } - BIO_free(mem); - } - } - return retval; -} - -/* Compare peername against hostname. Allow wildcard in leftmost - * position in peername, and the comparision is case insensitive. */ -static svn_boolean_t match_hostname(const char *peername, - const char *hostname, apr_pool_t *pool) -{ - char *str, *last_str; - char *hostname_copy; - - if (apr_strnatcasecmp(peername, hostname) == 0) - return TRUE; - - if (strlen(peername) < 3) - return FALSE; - - if (peername[0] != '*' || peername[1] != '.') - return FALSE; - - hostname_copy = apr_pstrdup(pool, hostname); - str = apr_strtok (hostname_copy, ".", &last_str); - if (str == NULL) - return FALSE; - - return (apr_strnatcasecmp(&peername[2], str) == 0); -} - -/* Verify that the certificates common name matches the hostname. - * Adapted from verify_callback() in postfixtls patch by Lutz Jaenicke - * at http://www.aet.tu-cottbus.de/personen/jaenicke/postfix_tls/ */ -static svn_boolean_t verify_hostname(ra_svn_session_baton_t *sess, - apr_pool_t *pool, - svn_auth_ssl_server_cert_info_t *cert_info) -{ - int i, r; - svn_boolean_t matched = FALSE; - svn_boolean_t dnsname_found = FALSE; - X509 *peer = SSL_get_peer_certificate(sess->conn->ssl); - STACK_OF(GENERAL_NAME) *gens; - - /* Check out the name certified against the hostname expected. - * Standards are not always clear with respect to the handling of - * dNSNames. RFC3207 does not specify the handling. We therefore follow - * the strict rules in RFC2818 (HTTP over TLS), Section 3.1: - * The Subject Alternative Name/dNSName has precedence over CommonName - * (CN). If dNSName entries are provided, CN is not checked anymore. */ - gens = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0); - if (gens) - { - for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; i++) - { - const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i); - if (gn->type == GEN_DNS) - { - dnsname_found = TRUE; - matched = match_hostname(gn->d.ia5->data, sess->hostname, pool); - if (matched) - break; - } - } - sk_GENERAL_NAME_free(gens); - if (dnsname_found) - return matched; - } - - return match_hostname(cert_info->hostname, sess->hostname, pool); -} - -/* Fill in the server certificate information, as well as check for some - * errors in the certificate. */ -static svn_error_t *fill_server_cert_info(ra_svn_session_baton_t *sess, - apr_pool_t *pool, - svn_auth_ssl_server_cert_info_t *cert_info, - apr_uint32_t *cert_failures) -{ - #define CERT_BUFFER_SIZE 256 - const char hexcodes[] = "0123456789ABCDEF"; - X509 *peer; - char buffer[CERT_BUFFER_SIZE]; - unsigned char md[EVP_MAX_MD_SIZE]; - char fingerprint[EVP_MAX_MD_SIZE * 3]; - unsigned int md_size; - unsigned int i; - unsigned int cert_buffer_size; - unsigned char *cert_buffer, *cert_buffer_pointer; - svn_stringbuf_t *ascii_cert; - long verify_result; - - cert_info->hostname = NULL; - cert_info->fingerprint = NULL; - cert_info->valid_from = NULL; - cert_info->valid_until = NULL; - cert_info->issuer_dname = NULL; - cert_info->ascii_cert = NULL; - - *cert_failures = 0; - - peer = SSL_get_peer_certificate(sess->conn->ssl); - if (peer == NULL) - return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, - _("Unable to obtain server certificate")); - - - if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peer), - NID_commonName, buffer, CERT_BUFFER_SIZE)) - return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, - _("Could not obtain server certificate CN")); - cert_info->hostname = apr_pstrdup(pool, buffer); - - if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer), - NID_commonName, buffer, CERT_BUFFER_SIZE)) - { - if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer), - NID_organizationName, buffer, - CERT_BUFFER_SIZE)) - return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, - _("Could not obtain server certificate issuer " - "or organization")); - - } - cert_info->issuer_dname = apr_pstrdup(pool, buffer); - - /* Neon uses sha1 for calculating fingerprint, and not MD5. */ - if (X509_digest(peer, EVP_sha1(), md, &md_size)) - { - for (i=0; i> 4]; - fingerprint[(3*i)+1] = hexcodes[(md[i] & 0x0f)]; - fingerprint[(3*i)+2] = ':'; - } - if (md_size > 0) - fingerprint[(3*(md_size-1))+2] = '\0'; - else - fingerprint[0] = '\0'; - cert_info->fingerprint = apr_pstrdup(pool, fingerprint); - } - else - cert_info->fingerprint = apr_pstrdup(pool, ""); - - cert_buffer_size = i2d_X509(peer, NULL); - if (cert_buffer_size > 0) - { - cert_buffer = apr_pcalloc(pool, cert_buffer_size); - cert_buffer_pointer = cert_buffer; - - /* cert_buffer_pointer is automatically incrementeded. */ - i2d_X509(peer, &cert_buffer_pointer); - - ascii_cert = svn_base64_from_buffer(cert_buffer, cert_buffer_size, pool); - cert_info->ascii_cert = apr_pstrdup(pool, ascii_cert->data); - } - - /* Read the certificate validity dates, but keep the output format - * same as in Neon. */ - if (asn1time_to_string(X509_get_notBefore(peer), buffer, CERT_BUFFER_SIZE)) - cert_info->valid_from = apr_pstrdup(pool, buffer); - else - cert_info->valid_from = apr_pstrdup(pool, "[invalid date]"); - - if (asn1time_to_string(X509_get_notAfter(peer), buffer, CERT_BUFFER_SIZE)) - cert_info->valid_until = apr_pstrdup(pool, buffer); - else - cert_info->valid_until = apr_pstrdup(pool, "[invalid date]"); - - /* Now we start checking the certificate. Similarly as done in Neon.*/ - if (X509_cmp_current_time(X509_get_notBefore(peer)) >= 0) - *cert_failures |= SVN_AUTH_SSL_NOTYETVALID; - else if (X509_cmp_current_time(X509_get_notAfter(peer)) <= 0) - *cert_failures |= SVN_AUTH_SSL_EXPIRED; - - /* Only the last verification failure will be returned from - * SSL_get_verify_result, even thugh there may be several errors. */ - verify_result = SSL_get_verify_result(sess->conn->ssl); - switch (verify_result) - { - case X509_V_OK: - case X509_V_ERR_CERT_NOT_YET_VALID: - case X509_V_ERR_CERT_HAS_EXPIRED: - break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - *cert_failures |= SVN_AUTH_SSL_UNKNOWNCA; - break; - default: - *cert_failures |= SVN_AUTH_SSL_OTHER; - } - - if (!verify_hostname(sess, pool, cert_info)) - *cert_failures |= SVN_AUTH_SSL_CNMISMATCH; - - return SVN_NO_ERROR; -} - /* Authenticate the server certificate. */ static svn_error_t *do_ssl_auth(ra_svn_session_baton_t *sess, apr_pool_t *pool) @@ -505,7 +283,9 @@ svn_auth_ssl_server_cert_info_t *cert_info = apr_palloc(pool, sizeof(*cert_info)); - SVN_ERR(fill_server_cert_info(sess, pool, cert_info, cert_failures)); + SVN_ERR(svn_ra_svn__fill_server_cert_info(sess->conn, pool, + sess->hostname, + cert_info, cert_failures)); svn_auth_set_parameter(sess->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_FAILURES, @@ -817,51 +597,10 @@ /* Guard against dotfile output to stdout on the server. */ *conn = svn_ra_svn_create_conn(NULL, proc->out, proc->in, pool); - (*conn)->proc = proc; SVN_ERR(svn_ra_svn_skip_leading_garbage(*conn, pool)); return SVN_NO_ERROR; } -/* Initializes the SSL context to be used by the client. */ -static svn_error_t *init_ssl_ctx(ra_svn_session_baton_t *sess, - apr_hash_t *config, - apr_pool_t *pool) -{ - /* List of ciphers that we allow for SSL connections. */ - const char *cipher_list = "ALL:!LOW"; - - SSL_load_error_strings(); - SSL_library_init(); - - /* TODO : Seed the randum number generator (RNG) - * for those operating systems that does not have /dev/urandom. - */ - - sess->ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - if (sess->ssl_ctx == NULL) - return svn_error_create(SVN_ERR_RA_SVN_SSL_INIT, NULL, - _("No SSL context created")); - - if (SSL_CTX_set_cipher_list(sess->ssl_ctx, cipher_list) != 1) - return svn_error_create(SVN_ERR_RA_SVN_SSL_INIT, NULL, - _("Could not set cipher list for SSL")); - - return SVN_NO_ERROR; -} - -/* Frees the allocated SSL context. - * Assumes that this is called after freeing of SSL - * allocated by svn_ra_svn_create_conn. */ -static apr_status_t destroy_ssl_ctx(void *data) -{ - ra_svn_session_baton_t *sess = data; - - if (sess->ssl_ctx != NULL) - SSL_CTX_free(sess->ssl_ctx); - - return APR_SUCCESS; -} - static svn_error_t *ra_svn_open(void **baton, const char *url, const svn_ra_callbacks_t *callbacks, void *callback_baton, @@ -945,15 +684,9 @@ /* Flush write buffer before initiating SSL handshake. */ svn_ra_svn_flush(sess->conn, pool); - /* Needs to cleanup SSL_CTX at destruction of the session. - * Note that SSL must be released before SSL_CTX. We cleanup - * SSL by registering a cleanup function in svn_ra_svn_ssl_init. */ - apr_pool_cleanup_register(pool, sess, destroy_ssl_ctx, - apr_pool_cleanup_null); - - SVN_ERR(init_ssl_ctx(sess, config, pool)); + SVN_ERR(svn_ra_svn__init_ssl_ctx(&sess->ssl_ctx, pool)); SVN_ERR(svn_ra_svn_ssl_init(sess->conn, pool, sess->ssl_ctx)); - SVN_ERR(svn_ra_svn_ssl_connect(sess->conn, pool)); + SVN_ERR(svn_ra_svn__ssl_connect(sess->conn, pool)); SVN_ERR(do_ssl_auth(sess, pool)); } Index: subversion/libsvn_ra_svn/ra_svn_ssl.h =================================================================== --- subversion/libsvn_ra_svn/ra_svn_ssl.h (revision 0) +++ subversion/libsvn_ra_svn/ra_svn_ssl.h (revision 0) @@ -0,0 +1,71 @@ +/* + * ra_svn_ssl.h : private SSL declarations for the ra_svn module + * + * ==================================================================== + * Copyright (c) 2000-2004 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + + + +#ifndef RA_SVN__SSL_H +#define RA_SVN_SSL_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "svn_auth.h" +#include "ra_svn.h" + +/* Fill in the server certificate information, as well as check for some + * errors in the certificate. */ +svn_error_t *svn_ra_svn__fill_server_cert_info(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const char *hostname, + svn_auth_ssl_server_cert_info_t *cert_info, + apr_uint32_t *cert_failures); + +/* Setup the svn_stream_t members of conn to use the SSL callbacks. + * Creates and initializes an SSL that is to be used for this connection. + * Internally, a BIO pair will be used to transfer data to/from Subversion + * and the network. + * + * Subversion | TLS-engine + * | | + * +----------> SSL_operations() + * | /\ || + * | || \/ + * | BIO-pair (internal_bio) + * +----------< BIO-pair (network_bio) + * | | + * socket | + */ +svn_error_t *svn_ra_svn__setup_ssl_conn(svn_ra_svn_conn_t *conn, void *ssl_ctx, + apr_pool_t *pool); + + +/* Wait for a SSL connection on the underlying socket connection. */ +svn_error_t *svn_ra_svn__ssl_accept(svn_ra_svn_conn_t *conn, apr_pool_t *pool); + +/* Do a SSL connect on the underlying socket connection. */ +svn_error_t *svn_ra_svn__ssl_connect(svn_ra_svn_conn_t *conn, apr_pool_t *pool); + +/* Initializes the SSL context to be used by the client. */ +svn_error_t *svn_ra_svn__init_ssl_ctx(void **ssl_ctx, apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* RA_SVN_SSL_H */ Index: subversion/libsvn_ra_svn/ssl.c =================================================================== --- subversion/libsvn_ra_svn/ssl.c (revision 0) +++ subversion/libsvn_ra_svn/ssl.c (revision 0) @@ -0,0 +1,635 @@ +/* + * ssl.c : SSL routines for Subversion protocol + * + * ==================================================================== + * Copyright (c) 2000-2004 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + + +#include +#include + +#define APR_WANT_STRFUNC +#include +#include +#include +#include +#include +#include + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_base64.h" +#include "svn_private_config.h" + +#include "ra_svn_ssl.h" + +#include +#include +#include + +/* Baton for a SSL stream connection. */ +typedef struct { + svn_ra_svn__sock_conn_t *conn; /* Inherited socket connection. */ + SSL *ssl; /* The SSL to use for this connection. */ + BIO *internal_bio; /* The Subversion/SSL side of a BIO pair. */ + BIO *network_bio; /* The network side of a BIO pair. */ +} ssl_conn_t; + +/* Print out SSL connection information for debugging. The callback can be + * set with SSL_CTX_set_info_callback. + * From man page of SSL_CTX_set_info_callback(3). */ +static void +apps_ssl_info_callback(SSL *s, int where, int ret) +{ + const char *str; + int w; + + w = where & ~SSL_ST_MASK; + + if (w & SSL_ST_CONNECT) + str = "SSL_connect"; + else if (w & SSL_ST_ACCEPT) + str = "SSL_accept"; + else + str = "undefined"; + + if (where & SSL_CB_LOOP) + fprintf(stderr, "%s:%s\n", str, SSL_state_string_long(s)); + else if (where & SSL_CB_ALERT) + { + str = (where & SSL_CB_READ) ? "read" : "write"; + if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY) + fprintf(stderr, "SSL3 alert %s:%s:%s\n", str, + SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } + else if (where & SSL_CB_EXIT) + { + if (ret == 0) + fprintf(stderr, "%s:failed in %s\n", + str, SSL_state_string_long(s)); + else if (ret < 0) + fprintf(stderr, "%s:error in %s\n", + str, SSL_state_string_long(s)); + } +} + +/* The interface layer between network and BIO-pair. The BIO-pair buffers + * the data to/from the TLS layer. Hence, at any time, there may be data + * in the buffer that must be written to the network. This writing has + * highest priority because the handshake might fail otherwise. + * Only then a read_request can be satisfied. + * Adapted from network_biopair_interop() in postfixtls patch by Lutz Jaenicke + * at http://www.aet.tu-cottbus.de/personen/jaenicke/postfix_tls/ */ +static svn_error_t * +network_biopair_interop(ssl_conn_t *conn) +{ + int want_write; + int num_write; + int write_pos; + int from_bio; + int want_read; + int num_read; + int to_bio; + apr_status_t status; + #define NETLAYER_BUFFERSIZE 8192 + char buffer[NETLAYER_BUFFERSIZE]; + apr_interval_time_t interval; + + status = apr_socket_timeout_get(conn->conn->sock, &interval); + if (status) + return svn_error_wrap_apr(status, _("Can't get socket timeout")); + + while ((want_write = BIO_ctrl_pending(conn->network_bio)) > 0) + { + if (want_write > NETLAYER_BUFFERSIZE) + want_write = NETLAYER_BUFFERSIZE; + from_bio = BIO_read(conn->network_bio, buffer, want_write); + + /* Write the complete contents of the buffer. Since TLS performs + * underlying handshaking, we cannot afford to leave the buffer + * unflushed, as we could run into a deadlock trap (the peer + * waiting for a final byte and we already waiting for his reply + * in read position). */ + write_pos = 0; + do { + num_write = from_bio - write_pos; + status = apr_socket_send(conn->conn->sock, buffer + write_pos, &num_write); + if (status) + return svn_error_wrap_apr(status, _("Can't write to connection")); + write_pos += num_write; + } while (write_pos < from_bio); + } + + while ((want_read = BIO_ctrl_get_read_request(conn->network_bio)) > 0) + { + if (want_read > NETLAYER_BUFFERSIZE) + want_read = NETLAYER_BUFFERSIZE; + + num_read = want_read; + /* We always block when reading from socket. */ + apr_socket_timeout_set(conn->conn->sock, -1); + status = apr_socket_recv(conn->conn->sock, buffer, &num_read); + apr_socket_timeout_set(conn->conn->sock, interval); + if (status && !APR_STATUS_IS_EOF(status)) + return svn_error_wrap_apr(status, _("Can't read from connection")); + if (num_read == 0) + return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, + _("Connection closed unexpectedly")); + + to_bio = BIO_write(conn->network_bio, buffer, num_read); + if (to_bio != num_read) + return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, + _("Failed to read from SSL socket layer")); + } + + return SVN_NO_ERROR; +} + +/* Function to perform the handshake for SSL_accept(), SSL_connect(), + * and SSL_shutdown() and perform the SSL_read(), SSL_write() operations. + * Call the underlying network_biopair_interop-layer to make sure the + * write buffer is flushed after every operation (that did not fail with + * a fatal error). + * The value of *count will be the result of the underlying SSL_operation(), + * and for SSL_read/SSL_write this will be number of bytes read/written + * if the operation was successfull. + * + * Adapted from do_tls_operation() in postfixtls patch by Lutz Jaenicke + * at http://www.aet.tu-cottbus.de/personen/jaenicke/postfix_tls/ */ +static svn_error_t * +do_ssl_operation(ssl_conn_t *conn, + int (*hsfunc)(SSL *), + int (*rfunc)(SSL *, void *, int), + int (*wfunc)(SSL *, const void *, int), + char *buffer, int *count) +{ + int status; + int ssl_err; + int count_val; + int ret_status = -1; + svn_boolean_t done = FALSE; + + count_val = (count == NULL) ? 0 : *count; + + while (!done) + { + if (hsfunc) + status = hsfunc(conn->ssl); + else if (rfunc) + status = rfunc(conn->ssl, buffer, count_val); + else + status = wfunc(conn->ssl, (const char *)buffer, count_val); + ssl_err = SSL_get_error(conn->ssl, status); + + switch (ssl_err) + { + case SSL_ERROR_NONE: /* success */ + ret_status = status; + done = TRUE; /* no break, flush buffer before */ + /* leaving */ + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + SVN_ERR(network_biopair_interop(conn)); + break; + case SSL_ERROR_ZERO_RETURN: /* connection was closed cleanly */ + case SSL_ERROR_SYSCALL: + case SSL_ERROR_SSL: + default: + ret_status = status; + done = TRUE; + }; + }; + + if (count != NULL) + *count = ret_status; + + if (ret_status > 0) + return SVN_NO_ERROR; + else + return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, + _("SSL network problem")); +} + +/* Format an ASN1 time to a string. */ +static svn_boolean_t +asn1time_to_string(ASN1_TIME *tm, char *buffer, apr_size_t len) +{ + svn_boolean_t retval = FALSE; + if (len--) + { + BIO *mem = BIO_new(BIO_s_mem()); + if (mem) + { + if (ASN1_TIME_print(mem, tm)) + { + long data_len; + char *data; + data_len = BIO_get_mem_data(mem, &data); + if (data_len < len) + len = data_len; + memcpy(buffer, data, len); + buffer[len] = 0; + retval = TRUE; + } + BIO_free(mem); + } + } + return retval; +} + +/* Compare peername against hostname. Allow wildcard in leftmost + * position in peername, and the comparision is case insensitive. */ +static svn_boolean_t +match_hostname(const char *peername, const char *hostname, apr_pool_t *pool) +{ + char *str, *last_str; + char *hostname_copy; + + if (apr_strnatcasecmp(peername, hostname) == 0) + return TRUE; + + if (strlen(peername) < 3) + return FALSE; + + if (peername[0] != '*' || peername[1] != '.') + return FALSE; + + hostname_copy = apr_pstrdup(pool, hostname); + str = apr_strtok (hostname_copy, ".", &last_str); + if (str == NULL) + return FALSE; + + return (apr_strnatcasecmp(&peername[2], str) == 0); +} + +/* Verify that the certificates common name matches the hostname. + * Adapted from verify_callback() in postfixtls patch by Lutz Jaenicke + * at http://www.aet.tu-cottbus.de/personen/jaenicke/postfix_tls/ */ +static svn_boolean_t +verify_hostname(ssl_conn_t *ssl_conn, + apr_pool_t *pool, + const char *hostname, + svn_auth_ssl_server_cert_info_t *cert_info) +{ + int i, r; + svn_boolean_t matched = FALSE; + svn_boolean_t dnsname_found = FALSE; + X509 *peer = SSL_get_peer_certificate(ssl_conn->ssl); + STACK_OF(GENERAL_NAME) *gens; + + /* Check out the name certified against the hostname expected. + * Standards are not always clear with respect to the handling of + * dNSNames. RFC3207 does not specify the handling. We therefore follow + * the strict rules in RFC2818 (HTTP over TLS), Section 3.1: + * The Subject Alternative Name/dNSName has precedence over CommonName + * (CN). If dNSName entries are provided, CN is not checked anymore. */ + gens = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0); + if (gens) + { + for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; i++) + { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i); + if (gn->type == GEN_DNS) + { + dnsname_found = TRUE; + matched = match_hostname(gn->d.ia5->data, hostname, pool); + if (matched) + break; + } + } + sk_GENERAL_NAME_free(gens); + if (dnsname_found) + return matched; + } + + return match_hostname(cert_info->hostname, hostname, pool); +} + +svn_error_t * +svn_ra_svn__fill_server_cert_info(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const char *hostname, + svn_auth_ssl_server_cert_info_t *cert_info, + apr_uint32_t *cert_failures) +{ + #define CERT_BUFFER_SIZE 256 + const char hexcodes[] = "0123456789ABCDEF"; + X509 *peer; + char buffer[CERT_BUFFER_SIZE]; + unsigned char md[EVP_MAX_MD_SIZE]; + char fingerprint[EVP_MAX_MD_SIZE * 3]; + unsigned int md_size; + unsigned int i; + unsigned int cert_buffer_size; + unsigned char *cert_buffer, *cert_buffer_pointer; + svn_stringbuf_t *ascii_cert; + long verify_result; + ssl_conn_t *ssl_conn = conn->user_data_baton; + + cert_info->hostname = NULL; + cert_info->fingerprint = NULL; + cert_info->valid_from = NULL; + cert_info->valid_until = NULL; + cert_info->issuer_dname = NULL; + cert_info->ascii_cert = NULL; + + *cert_failures = 0; + + peer = SSL_get_peer_certificate(ssl_conn->ssl); + if (peer == NULL) + return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, + _("Unable to obtain server certificate")); + + + if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peer), + NID_commonName, buffer, CERT_BUFFER_SIZE)) + return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, + _("Could not obtain server certificate CN")); + cert_info->hostname = apr_pstrdup(pool, buffer); + + if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer), + NID_commonName, buffer, CERT_BUFFER_SIZE)) + { + if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer), + NID_organizationName, buffer, + CERT_BUFFER_SIZE)) + return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, + _("Could not obtain server certificate issuer " + "or organization")); + } + cert_info->issuer_dname = apr_pstrdup(pool, buffer); + + /* Neon uses sha1 for calculating fingerprint, and not MD5. */ + if (X509_digest(peer, EVP_sha1(), md, &md_size)) + { + for (i=0; i> 4]; + fingerprint[(3*i)+1] = hexcodes[(md[i] & 0x0f)]; + fingerprint[(3*i)+2] = ':'; + } + if (md_size > 0) + fingerprint[(3*(md_size-1))+2] = '\0'; + else + fingerprint[0] = '\0'; + cert_info->fingerprint = apr_pstrdup(pool, fingerprint); + } + else + cert_info->fingerprint = apr_pstrdup(pool, ""); + + cert_buffer_size = i2d_X509(peer, NULL); + if (cert_buffer_size > 0) + { + cert_buffer = apr_pcalloc(pool, cert_buffer_size); + cert_buffer_pointer = cert_buffer; + + /* cert_buffer_pointer is automatically incrementeded. */ + i2d_X509(peer, &cert_buffer_pointer); + + ascii_cert = svn_base64_from_buffer(cert_buffer, cert_buffer_size, pool); + cert_info->ascii_cert = apr_pstrdup(pool, ascii_cert->data); + } + + /* Read the certificate validity dates, but keep the output format + * same as in Neon. */ + if (asn1time_to_string(X509_get_notBefore(peer), buffer, CERT_BUFFER_SIZE)) + cert_info->valid_from = apr_pstrdup(pool, buffer); + else + cert_info->valid_from = apr_pstrdup(pool, "[invalid date]"); + + if (asn1time_to_string(X509_get_notAfter(peer), buffer, CERT_BUFFER_SIZE)) + cert_info->valid_until = apr_pstrdup(pool, buffer); + else + cert_info->valid_until = apr_pstrdup(pool, "[invalid date]"); + + /* Now we start checking the certificate. Similarly as done in Neon.*/ + if (X509_cmp_current_time(X509_get_notBefore(peer)) >= 0) + *cert_failures |= SVN_AUTH_SSL_NOTYETVALID; + else if (X509_cmp_current_time(X509_get_notAfter(peer)) <= 0) + *cert_failures |= SVN_AUTH_SSL_EXPIRED; + + /* Only the last verification failure will be returned from + * SSL_get_verify_result, even thugh there may be several errors. */ + verify_result = SSL_get_verify_result(ssl_conn->ssl); + switch (verify_result) + { + case X509_V_OK: + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + *cert_failures |= SVN_AUTH_SSL_UNKNOWNCA; + break; + default: + *cert_failures |= SVN_AUTH_SSL_OTHER; + } + + if (!verify_hostname(ssl_conn, pool, hostname, cert_info)) + *cert_failures |= SVN_AUTH_SSL_CNMISMATCH; + + return SVN_NO_ERROR; +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static void +ssl_timeout_cb(void *baton, apr_interval_time_t interval) +{ + ssl_conn_t *ssl_conn = baton; + apr_socket_timeout_set(ssl_conn->conn->sock, interval); +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_boolean_t +ssl_data_pending_cb(void *baton) +{ + svn_error_t *err; + ssl_conn_t *conn = baton; + int n; + + /* Note that SSL_pending may return number of bytes to read, + * even if the data is not application data. */ + err = do_ssl_operation(conn, SSL_pending, NULL, NULL, NULL, &n); + return (err || n <= 0) ? FALSE : TRUE; +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_error_t * +ssl_read_cb(void *baton, char *buffer, apr_size_t *len) +{ + svn_error_t *err; + ssl_conn_t *ssl_conn = baton; + + /* We always blockon reading. */ + apr_socket_timeout_set(ssl_conn->conn->sock, -1); + err = do_ssl_operation(ssl_conn, NULL, SSL_read, NULL, buffer, len); + apr_socket_timeout_set(ssl_conn->conn->sock, 0); + if (err) + return err; + if (*len == 0) + return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, + _("Connection closed unexpectedly")); + return SVN_NO_ERROR; +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_error_t * +ssl_write_cb(void *baton, const char *buffer, apr_size_t *len) +{ + ssl_conn_t *ssl_conn = baton; + if (*len > 0) + SVN_ERR(do_ssl_operation(ssl_conn, NULL, NULL, SSL_write, (char *) buffer, + len)); + else + *len = 0; + return SVN_NO_ERROR; +} + +/* Releases the resources allocated by SSL. */ +static apr_status_t +cleanup_ssl(void *data) +{ + ssl_conn_t *conn = data; + + /* The connection has been setup between client and server, + * so we tell the other side that we are finished. */ + if (!do_ssl_operation(conn, SSL_shutdown, NULL, NULL, NULL, NULL)) + do_ssl_operation(conn, SSL_shutdown, NULL, NULL, NULL, NULL); + + /* Implicitely frees conn->internal_bio. */ + SSL_free(conn->ssl); + BIO_free(conn->network_bio); + + return APR_SUCCESS; +} + +svn_error_t * +svn_ra_svn__setup_ssl_conn(svn_ra_svn_conn_t *conn, void *ssl_ctx, + apr_pool_t *pool) +{ + SSL_CTX *ssl_context = (SSL_CTX *) ssl_ctx; + ssl_conn_t *ssl_conn = apr_palloc(pool, sizeof(*ssl_conn)); + + ssl_conn->ssl = SSL_new(ssl_context); + if (ssl_conn->ssl == NULL) + return svn_error_create(SVN_ERR_RA_SVN_SSL_INIT, NULL, + "Could not create a SSL from the SSL context"); + + if (!BIO_new_bio_pair(&ssl_conn->internal_bio, 8192, + &ssl_conn->network_bio, 8192)) + { + SSL_free(ssl_conn->ssl); + return svn_error_create(SVN_ERR_RA_SVN_SSL_INIT, NULL, + "Could not create a a new BIO pair"); + } + + SSL_set_bio(ssl_conn->ssl, ssl_conn->internal_bio, ssl_conn->internal_bio); + + /* Need to release SSL resources when the connection is destroyed. + * Assumes that the owning SSL_CTX is destroyed after cleanup + * of SSL. */ + apr_pool_cleanup_register(pool, ssl_conn, cleanup_ssl, apr_pool_cleanup_null); + + /* Remember the original socket. */ + ssl_conn->conn = conn->user_data_baton; + if (ssl_conn->conn == NULL) + fprintf(stderr, "socket null\n"); + + conn->user_data_baton = ssl_conn; + + svn_stream_set_baton(conn->in_stream, ssl_conn); + svn_stream_set_read(conn->in_stream, ssl_read_cb); + svn_stream_set_write(conn->in_stream, ssl_write_cb); + svn_stream_set_timeout(conn->in_stream, ssl_timeout_cb); + svn_stream_set_data_pending(conn->in_stream, ssl_data_pending_cb); + + /* gcc gives a warning about incompatible pointer type for the + * callback, but from the OpenSSL documentation it seems to be OK. + */ + /* + SSL_CTX_set_info_callback(ssl_conn->ssl, apps_ssl_info_callback); + */ + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_svn__ssl_accept(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + ssl_conn_t *ssl_conn = conn->user_data_baton; + assert(ssl_conn != NULL); + return do_ssl_operation(ssl_conn, SSL_accept, NULL, NULL, NULL, NULL); +} + +svn_error_t * +svn_ra_svn__ssl_connect(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + ssl_conn_t *ssl_conn = conn->user_data_baton; + assert(ssl_conn != NULL); + return do_ssl_operation(ssl_conn, SSL_connect, NULL, NULL, NULL, NULL); +} + +/* Frees the allocated SSL context. + * Assumes that this is called after freeing of SSL + * allocated by svn_ra_svn_create_conn. */ +static apr_status_t +destroy_ssl_ctx(void *data) +{ + SSL_CTX *ssl_ctx = data; + if (ssl_ctx != NULL) + SSL_CTX_free(ssl_ctx); + + return APR_SUCCESS; +} + +svn_error_t * +svn_ra_svn__init_ssl_ctx(void **ssl_ctx, apr_pool_t *pool) +{ + SSL_CTX *ctx; + + /* List of ciphers that we allow for SSL connections. */ + const char *cipher_list = "ALL:!LOW"; + + SSL_load_error_strings(); + SSL_library_init(); + + /* TODO : Seed the randum number generator (RNG) + * for those operating systems that does not have /dev/urandom. + */ + + ctx = SSL_CTX_new(SSLv23_client_method()); + if (ctx == NULL) + return svn_error_create(SVN_ERR_RA_SVN_SSL_INIT, NULL, + _("No SSL context created")); + + if (SSL_CTX_set_cipher_list(ctx, cipher_list) != 1) + return svn_error_create(SVN_ERR_RA_SVN_SSL_INIT, NULL, + _("Could not set cipher list for SSL")); + + *ssl_ctx = ctx; + + /* Needs to cleanup SSL_CTX at destruction of the session. + * Note that SSL must be released before SSL_CTX. We cleanup + * SSL by registering a cleanup function in svn_ra_svn_ssl_init. */ + apr_pool_cleanup_register(pool, ctx, destroy_ssl_ctx, + apr_pool_cleanup_null); + + return SVN_NO_ERROR; +} Index: subversion/libsvn_ra_svn/marshal.c =================================================================== --- subversion/libsvn_ra_svn/marshal.c (revision 11765) +++ subversion/libsvn_ra_svn/marshal.c (working copy) @@ -34,23 +34,176 @@ #include "svn_error.h" #include "svn_pools.h" #include "svn_ra_svn.h" +#include "svn_io.h" #include "svn_private_config.h" -#include "ra_svn.h" +#include "ra_svn_ssl.h" #define svn_iswhitespace(c) ((c) == ' ' || (c) == '\n') -/* --- SSL FORWARD DECLARATION --- */ +/* --- CONNECTION INITIALIZATION --- */ -/* Defined below. */ -static svn_error_t *do_ssl_operation(svn_ra_svn_conn_t *conn, - int (*hsfunc)(SSL *), - int (*rfunc)(SSL *, void *, int), - int (*wfunc)(SSL *, const void *, int), - char *buffer, int *size); +/* Baton for a file stream connection. */ +typedef struct { + apr_file_t *file; + apr_pool_t *pool; +} file_conn_t; -/* --- CONNECTION INITIALIZATION --- */ +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static void sock_timeout_cb(void *baton, + apr_interval_time_t interval) +{ + svn_ra_svn__sock_conn_t *conn = baton; + apr_socket_timeout_set(conn->sock, interval); +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static void file_timeout_cb(void *baton, + apr_interval_time_t interval) +{ + file_conn_t *conn = baton; + apr_file_pipe_timeout_set(conn->file, interval); +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_boolean_t sock_data_pending_cb(void *baton) +{ + svn_ra_svn__sock_conn_t *conn = baton; + apr_pollfd_t pfd; + int n; + + pfd.desc_type = APR_POLL_SOCKET; + pfd.desc.s = conn->sock; + pfd.p = conn->pool; + pfd.reqevents = APR_POLLIN; + return ((apr_poll(&pfd, 1, &n, 0) == APR_SUCCESS) && n); +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_boolean_t file_data_pending_cb(void *baton) +{ + file_conn_t *conn = baton; + apr_pollfd_t pfd; + int n; + + pfd.desc_type = APR_POLL_FILE; + pfd.desc.f = conn->file; + pfd.p = conn->pool; + pfd.reqevents = APR_POLLIN; + return ((apr_poll(&pfd, 1, &n, 0) == APR_SUCCESS) && n); +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_error_t *sock_read_cb(void *baton, + char *buffer, + apr_size_t *len) +{ + svn_ra_svn__sock_conn_t *conn = baton; + apr_status_t status; + + /* XXX : Why not always block on read, irrespective of any block handler? */ + + /* Always block on read. */ + apr_socket_timeout_set(conn->sock, -1); + status = apr_socket_recv(conn->sock, buffer, len); + apr_socket_timeout_set(conn->sock, 0); + if (status && !APR_STATUS_IS_EOF(status)) + return svn_error_wrap_apr(status, _("Can't read from connection")); + if (*len == 0) + return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, + _("Connection closed unexpectedly")); + return SVN_NO_ERROR; +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_error_t *file_read_cb(void *baton, + char *buffer, + apr_size_t *len) +{ + file_conn_t *conn = baton; + apr_status_t status = apr_file_read(conn->file, buffer, len); + if (status && !APR_STATUS_IS_EOF(status)) + return svn_error_wrap_apr(status, _("Can't read from connection")); + if (*len == 0) + return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, + _("Connection closed unexpectedly")); + return SVN_NO_ERROR; +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_error_t *sock_write_cb(void *baton, + const char *buffer, + apr_size_t *len) +{ + svn_ra_svn__sock_conn_t *conn = baton; + apr_status_t status = apr_socket_send(conn->sock, buffer, len); + if (status) + return svn_error_wrap_apr(status, _("Can't write to connection")); + return SVN_NO_ERROR; +} + +/* Callback for a svn_stream_t member of svn_ra_svn_conn_t */ +static svn_error_t *file_write_cb(void *baton, + const char *buffer, + apr_size_t *len) +{ + file_conn_t *conn = baton; + apr_status_t status = apr_file_write(conn->file, buffer, len); + if (status) + return svn_error_wrap_apr(status, _("Can't write to connection")); + return SVN_NO_ERROR; +} + +/* Setup the svn_stream_t members of conn to use the socket callbacks. */ +static void setup_sock_conn(svn_ra_svn_conn_t *conn, + apr_socket_t *sock, + apr_pool_t *pool) +{ + svn_ra_svn__sock_conn_t *sock_conn = apr_palloc(pool, sizeof(*sock_conn)); + sock_conn->sock = sock; + sock_conn->pool = pool; + + conn->user_data_baton = sock_conn; /* For usage in SSL. */ + conn->in_stream = svn_stream_empty(pool); + conn->out_stream = conn->in_stream; + + svn_stream_set_baton(conn->in_stream, sock_conn); + svn_stream_set_read(conn->in_stream, sock_read_cb); + svn_stream_set_write(conn->in_stream, sock_write_cb); + svn_stream_set_timeout(conn->in_stream, sock_timeout_cb); + svn_stream_set_data_pending(conn->in_stream, sock_data_pending_cb); +} + +/* Setup the svn_stream_t members of conn to use the file callbacks. */ +static void setup_file_conn(svn_ra_svn_conn_t *conn, + apr_file_t *in_file, + apr_file_t *out_file, + apr_pool_t *pool) +{ + file_conn_t *in_file_conn = apr_palloc(pool, sizeof(*in_file_conn)); + file_conn_t *out_file_conn = apr_palloc(pool, sizeof(*out_file_conn)); + + in_file_conn->file = in_file; + in_file_conn->pool = pool; + + out_file_conn->file = out_file; + out_file_conn->pool = pool; + + conn->user_data_baton = NULL; + conn->in_stream = svn_stream_empty(pool); + conn->out_stream = svn_stream_empty(pool); + + svn_stream_set_baton(conn->in_stream, in_file_conn); + svn_stream_set_read(conn->in_stream, file_read_cb); + svn_stream_set_timeout(conn->in_stream, file_timeout_cb); + svn_stream_set_data_pending(conn->in_stream, file_data_pending_cb); + + svn_stream_set_baton(conn->out_stream, out_file_conn); + svn_stream_set_write(conn->out_stream, file_write_cb); + svn_stream_set_timeout(conn->out_stream, file_timeout_cb); +} + svn_ra_svn_conn_t *svn_ra_svn_create_conn(apr_socket_t *sock, apr_file_t *in_file, apr_file_t *out_file, @@ -59,9 +212,6 @@ svn_ra_svn_conn_t *conn = apr_palloc(pool, sizeof(*conn)); assert((sock && !in_file && !out_file) || (!sock && in_file && out_file)); - conn->sock = sock; - conn->in_file = in_file; - conn->out_file = out_file; conn->read_ptr = conn->read_buf; conn->read_end = conn->read_buf; conn->write_pos = 0; @@ -69,11 +219,12 @@ conn->block_baton = NULL; conn->capabilities = apr_hash_make(pool); conn->pool = pool; - conn->use_ssl = FALSE; - conn->ssl = NULL; - conn->internal_bio = NULL; - conn->network_bio = NULL; + if (sock != NULL) + setup_sock_conn(conn, sock, pool); + else + setup_file_conn(conn, in_file, out_file, pool); + return conn; } @@ -111,40 +262,13 @@ conn->block_handler = handler; conn->block_baton = baton; - if (conn->sock) - apr_socket_timeout_set(conn->sock, interval); - else - apr_file_pipe_timeout_set(conn->out_file, interval); + svn_stream_timeout(conn->out_stream, interval); } svn_boolean_t svn_ra_svn__input_waiting(svn_ra_svn_conn_t *conn, apr_pool_t *pool) { - apr_pollfd_t pfd; - int n; - svn_error_t *err; - - if (conn->use_ssl) - { - /* Note thak SSL_pending may return number of bytes to read, - * even if the data is not application data. */ - err = do_ssl_operation(conn, SSL_pending, NULL, NULL, NULL, &n); - return (err || n <= 0) ? FALSE : TRUE; - } - - if (conn->sock) - { - pfd.desc_type = APR_POLL_SOCKET; - pfd.desc.s = conn->sock; - } - else - { - pfd.desc_type = APR_POLL_FILE; - pfd.desc.f = conn->in_file; - } - pfd.p = pool; - pfd.reqevents = APR_POLLIN; - return ((apr_poll(&pfd, 1, &n, 0) == APR_SUCCESS) && n); + return svn_stream_data_pending(conn->in_stream); } /* --- WRITE BUFFER MANAGEMENT --- */ @@ -163,49 +287,18 @@ return data + copylen; } -/* Write data to SSL. */ -static svn_error_t *writebuf_output_ssl(svn_ra_svn_conn_t *conn, - apr_pool_t *pool, const char *data, - apr_size_t len) -{ - svn_error_t *err = SVN_NO_ERROR; - apr_pool_t *subpool = NULL; - - if (len > 0) - SVN_ERR(do_ssl_operation(conn, NULL, NULL, SSL_write, (char *) data, &len)); - - subpool = svn_pool_create(pool); - if (conn->block_handler != NULL) - { - subpool = svn_pool_create(pool); - err = conn->block_handler(conn, subpool, conn->block_baton); - apr_pool_destroy(subpool); - } - - return err; -} - /* Write data to socket or output file as appropriate. */ static svn_error_t *writebuf_output(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *data, apr_size_t len) { const char *end = data + len; - apr_status_t status; apr_size_t count; apr_pool_t *subpool = NULL; - if (conn->use_ssl) - return writebuf_output_ssl(conn, pool, data, len); - while (data < end) { count = end - data; - if (conn->sock) - status = apr_socket_send(conn->sock, data, &count); - else - status = apr_file_write(conn->out_file, data, &count); - if (status) - return svn_error_wrap_apr(status, _("Can't write to connection")); + SVN_ERR(svn_stream_write(conn->out_stream, data, &count)); if (count == 0) { if (!subpool) @@ -279,46 +372,11 @@ return data + copylen; } -/* Read data from SSL. */ -static svn_error_t *readbuf_input_ssl(svn_ra_svn_conn_t *conn, char *data, - apr_size_t *len) -{ - svn_error_t *err; - - if (conn->block_handler) - apr_socket_timeout_set(conn->sock, -1); - err = do_ssl_operation(conn, NULL, SSL_read, NULL, data, len); - if (conn->block_handler) - apr_socket_timeout_set(conn->sock, -1); - if (err) - return err; - if (*len == 0) - return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, - _("Connection closed unexpectedly")); - - return SVN_NO_ERROR; -} - -/* Read data from socket or input file as appropriate. */ +/* Read data from from input stream. */ static svn_error_t *readbuf_input(svn_ra_svn_conn_t *conn, char *data, apr_size_t *len) { - apr_status_t status; - - if (conn->use_ssl) - return readbuf_input_ssl(conn, data, len); - - /* Always block for reading. */ - if (conn->sock && conn->block_handler) - apr_socket_timeout_set(conn->sock, -1); - if (conn->sock) - status = apr_socket_recv(conn->sock, data, len); - else - status = apr_file_read(conn->in_file, data, len); - if (conn->sock && conn->block_handler) - apr_socket_timeout_set(conn->sock, 0); - if (status && !APR_STATUS_IS_EOF(status)) - return svn_error_wrap_apr(status, _("Can't read from connection")); + SVN_ERR(svn_stream_read(conn->in_stream, data, len)); if (*len == 0) return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, _("Connection closed unexpectedly")); @@ -959,218 +1017,13 @@ /* --- SSL FUNCTIONS --- */ -/* The interface layer between network and BIO-pair. The BIO-pair buffers - * the data to/from the TLS layer. Hence, at any time, there may be data - * in the buffer that must be written to the network. This writing has - * highest priority because the handshake might fail otherwise. - * Only then a read_request can be satisfied. - * Adapted from network_biopair_interop() in postfixtls patch by Lutz Jaenicke - * at http://www.aet.tu-cottbus.de/personen/jaenicke/postfix_tls/ */ -static svn_error_t *network_biopair_interop(svn_ra_svn_conn_t *conn) -{ - int want_write; - int num_write; - int write_pos; - int from_bio; - int want_read; - int num_read; - int to_bio; - apr_status_t status; - #define NETLAYER_BUFFERSIZE 8192 - char buffer[NETLAYER_BUFFERSIZE]; - - while ((want_write = BIO_ctrl_pending(conn->network_bio)) > 0) - { - if (want_write > NETLAYER_BUFFERSIZE) - want_write = NETLAYER_BUFFERSIZE; - from_bio = BIO_read(conn->network_bio, buffer, want_write); - - /* Write the complete contents of the buffer. Since TLS performs - * underlying handshaking, we cannot afford to leave the buffer - * unflushed, as we could run into a deadlock trap (the peer - * waiting for a final byte and we already waiting for his reply - * in read position). */ - write_pos = 0; - do { - num_write = from_bio - write_pos; - status = apr_socket_send(conn->sock, buffer + write_pos, &num_write); - if (status) - return svn_error_wrap_apr(status, _("Can't write to connection")); - write_pos += num_write; - } while (write_pos < from_bio); - } - - while ((want_read = BIO_ctrl_get_read_request(conn->network_bio)) > 0) - { - if (want_read > NETLAYER_BUFFERSIZE) - want_read = NETLAYER_BUFFERSIZE; - - num_read = want_read; - status = apr_socket_recv(conn->sock, buffer, &num_read); - if (status && !APR_STATUS_IS_EOF(status)) - return svn_error_wrap_apr(status, _("Can't read from connection")); - if (num_read == 0) - return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, - _("Connection closed unexpectedly")); - - to_bio = BIO_write(conn->network_bio, buffer, num_read); - if (to_bio != num_read) - return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, - _("Failed to read from SSL socket layer")); - } - - return SVN_NO_ERROR; -} - -/* Function to perform the handshake for SSL_accept(), SSL_connect(), - * and SSL_shutdown() and perform the SSL_read(), SSL_write() operations. - * Call the underlying network_biopair_interop-layer to make sure the - * write buffer is flushed after every operation (that did not fail with - * a fatal error). - * The value of *count will be the result of the underlying SSL_operation(), - * and for SSL_read/SSL_write this will be number of bytes read/written - * if the operation was successfull. - * - * Adapted from do_tls_operation() in postfixtls patch by Lutz Jaenicke - * at http://www.aet.tu-cottbus.de/personen/jaenicke/postfix_tls/ */ -static svn_error_t *do_ssl_operation(svn_ra_svn_conn_t *conn, - int (*hsfunc)(SSL *), - int (*rfunc)(SSL *, void *, int), - int (*wfunc)(SSL *, const void *, int), - char *buffer, int *count) -{ - int status; - int ssl_err; - int count_val; - int ret_status = -1; - svn_boolean_t done = FALSE; - - count_val = (count == NULL) ? 0 : *count; - - while (!done) - { - if (hsfunc) - status = hsfunc(conn->ssl); - else if (rfunc) - status = rfunc(conn->ssl, buffer, count_val); - else - status = wfunc(conn->ssl, (const char *)buffer, count_val); - ssl_err = SSL_get_error(conn->ssl, status); - - switch (ssl_err) - { - case SSL_ERROR_NONE: /* success */ - ret_status = status; - done = TRUE; /* no break, flush buffer before */ - /* leaving */ - case SSL_ERROR_WANT_WRITE: - case SSL_ERROR_WANT_READ: - SVN_ERR(network_biopair_interop(conn)); - break; - case SSL_ERROR_ZERO_RETURN: /* connection was closed cleanly */ - case SSL_ERROR_SYSCALL: - case SSL_ERROR_SSL: - default: - ret_status = status; - done = TRUE; - }; - }; - - if (count != NULL) - *count = ret_status; - - if (ret_status > 0) - return SVN_NO_ERROR; - else - return svn_error_create(SVN_ERR_RA_SVN_SSL_ERROR, NULL, - _("SSL network problem")); -} - -/* Releases the resources allocated by SSL. */ -static apr_status_t cleanup_ssl(void *data) -{ - svn_ra_svn_conn_t *conn = (svn_ra_svn_conn_t *) data; - - if (conn->ssl == NULL) - return APR_SUCCESS;; - - /* The connection has been setup between client and server, - * so we tell the other side that we are finished. */ - if (conn->use_ssl) - { - if (!do_ssl_operation(conn, SSL_shutdown, NULL, NULL, NULL, NULL)) - do_ssl_operation(conn, SSL_shutdown, NULL, NULL, NULL, NULL); - conn->use_ssl = FALSE; - } - - /* Implicitely frees conn->internal_bio. */ - SSL_free(conn->ssl); - conn->ssl = NULL; - conn->internal_bio = NULL; - - if (conn->network_bio) - { - BIO_free(conn->network_bio); - conn->network_bio = NULL; - } - - return APR_SUCCESS; -} - -/* Creates and initializes an SSL that is to be used for this connection. - * Internally, a BIO pair will be used to transfer data to/from Subversion - * and the network. - * - * Subversion | TLS-engine - * | | - * +----------> SSL_operations() - * | /\ || - * | || \/ - * | BIO-pair (internal_bio) - * +----------< BIO-pair (network_bio) - * | | - * socket | - */ svn_error_t *svn_ra_svn_ssl_init(svn_ra_svn_conn_t *conn, apr_pool_t *pool, void *ssl_ctx) { - SSL_CTX *ssl_context = (SSL_CTX *) ssl_ctx; - - /* Need to release SSL resources when the connection is destroyed. - * Assumes that the owning SSL_CTX is destroyed after cleanup - * of SSL. */ - apr_pool_cleanup_register(pool, conn, cleanup_ssl, apr_pool_cleanup_null); - - conn->ssl = SSL_new(ssl_context); - if (conn->ssl == NULL) - return svn_error_create(SVN_ERR_RA_SVN_SSL_INIT, NULL, - "Could not create a SSL from the SSL context"); - - if (!BIO_new_bio_pair(&conn->internal_bio, 8192, &conn->network_bio, 8192)) - return svn_error_create(SVN_ERR_RA_SVN_SSL_INIT, NULL, - "Could not create a a new BIO pair"); - - SSL_set_bio(conn->ssl, conn->internal_bio, conn->internal_bio); - - return SVN_NO_ERROR; + return svn_ra_svn__setup_ssl_conn(conn, ssl_ctx, pool); } svn_error_t *svn_ra_svn_ssl_accept(svn_ra_svn_conn_t *conn, apr_pool_t *pool) { - SVN_ERR(do_ssl_operation(conn, SSL_accept, NULL, NULL, NULL, NULL)); - - /* Now SSL has been setup, and can be used. */ - conn->use_ssl = TRUE; - - return SVN_NO_ERROR; + return svn_ra_svn__ssl_accept(conn, pool); } - -svn_error_t *svn_ra_svn_ssl_connect(svn_ra_svn_conn_t *conn, apr_pool_t *pool) -{ - SVN_ERR(do_ssl_operation(conn, SSL_connect, NULL, NULL, NULL, NULL)); - - /* Now SSL has been setup, and can be used. */ - conn->use_ssl = TRUE; - - return SVN_NO_ERROR; -} Index: subversion/libsvn_ra_svn/ra_svn.h =================================================================== --- subversion/libsvn_ra_svn/ra_svn.h (revision 11765) +++ subversion/libsvn_ra_svn/ra_svn.h (working copy) @@ -30,9 +30,6 @@ #include #include -#include -#include - /* Handler for blocked writes. */ typedef svn_error_t *(*ra_svn_block_handler_t)(svn_ra_svn_conn_t *conn, apr_pool_t *pool, @@ -42,10 +39,9 @@ /* This structure is opaque to the server. The client pokes at the * first few fields during setup and cleanup. */ struct svn_ra_svn_conn_st { - apr_socket_t *sock; /* NULL if using in_file/out_file */ - apr_file_t *in_file; - apr_file_t *out_file; - apr_proc_t *proc; /* Used by client.c when sock is NULL */ + svn_stream_t *in_stream; + svn_stream_t *out_stream; + void *user_data_baton; /* Used when the connection is a socket or SSL. */ char read_buf[4096]; char *read_ptr; char *read_end; @@ -57,12 +53,14 @@ void *block_baton; apr_hash_t *capabilities; apr_pool_t *pool; - svn_boolean_t use_ssl; /* Set to true after a SSL handshake is done */ - SSL *ssl; /* The SSL to use for this connection. */ - BIO *internal_bio; /* The Subversion/SSL side of a BIO pair. */ - BIO *network_bio; /* The network side of a BIO pair. */ }; +/* Baton for a socket stream connection. */ +typedef struct { + apr_socket_t *sock; + apr_pool_t *pool; +} svn_ra_svn__sock_conn_t; + /* Set a callback for blocked writes on conn. This handler may * perform reads on the connection in order to prevent deadlock due to * pipelining. If callback is NULL, the connection goes back to