From 277efaffaba80bd0052587d8b445ec84a2c8c885 Mon Sep 17 00:00:00 2001 From: Brad Broerman Date: Sun, 20 Jan 2013 12:48:24 -0500 Subject: [PATCH 1/4] Updated to fix timeouts on blocking I/O Read and write on blocking SSL based IO will now obey the configured timeout. --- ext/openssl/xp_ssl.c | 1755 +++++++++++++++++++++++------------------- 1 file changed, 951 insertions(+), 804 deletions(-) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index c6f4e952ac2d5..6f9a45ae81e09 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2013 The PHP Group | + | Copyright (c) 1997-2012 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | @@ -47,17 +47,17 @@ int php_openssl_get_x509_list_id(void); * */ typedef struct _php_openssl_netstream_data_t { - php_netstream_data_t s; - SSL *ssl_handle; - SSL_CTX *ctx; - struct timeval connect_timeout; - int enable_on_connect; - int is_client; - int ssl_active; - php_stream_xport_crypt_method_t method; - char *sni; - unsigned state_set:1; - unsigned _spare:31; + php_netstream_data_t s; + SSL *ssl_handle; + SSL_CTX *ctx; + struct timeval connect_timeout; + int enable_on_connect; + int is_client; + int ssl_active; + php_stream_xport_crypt_method_t method; + char *sni; + unsigned state_set:1; + unsigned _spare:31; } php_openssl_netstream_data_t; php_stream_ops php_openssl_socket_ops; @@ -66,885 +66,1031 @@ php_stream_ops php_openssl_socket_ops; * in an error condition arising from a network connection problem */ static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC) { - if (stream->wrapperdata && stream->wrapper && strcasecmp(stream->wrapper->wops->label, "HTTP") == 0) { - /* the wrapperdata is an array zval containing the headers */ - zval **tmp; + if (stream->wrapperdata && stream->wrapper && strcasecmp(stream->wrapper->wops->label, "HTTP") == 0) { + /* the wrapperdata is an array zval containing the headers */ + zval **tmp; -#define SERVER_MICROSOFT_IIS "Server: Microsoft-IIS" +#define SERVER_MICROSOFT_IIS "Server: Microsoft-IIS" #define SERVER_GOOGLE "Server: GFE/" - - zend_hash_internal_pointer_reset(Z_ARRVAL_P(stream->wrapperdata)); - while (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(stream->wrapperdata), (void**)&tmp)) { - - if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_MICROSOFT_IIS, sizeof(SERVER_MICROSOFT_IIS)-1) == 0) { - return 1; - } else if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_GOOGLE, sizeof(SERVER_GOOGLE)-1) == 0) { - return 1; - } - - zend_hash_move_forward(Z_ARRVAL_P(stream->wrapperdata)); - } - } - return 0; + + zend_hash_internal_pointer_reset(Z_ARRVAL_P(stream->wrapperdata)); + while (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(stream->wrapperdata), (void**)&tmp)) { + + if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_MICROSOFT_IIS, sizeof(SERVER_MICROSOFT_IIS)-1) == 0) { + return 1; + } else if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_GOOGLE, sizeof(SERVER_GOOGLE)-1) == 0) { + return 1; + } + + zend_hash_move_forward(Z_ARRVAL_P(stream->wrapperdata)); + } + } + return 0; } static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int err = SSL_get_error(sslsock->ssl_handle, nr_bytes); - char esbuf[512]; - smart_str ebuf = {0}; - unsigned long ecode; - int retry = 1; - - switch(err) { - case SSL_ERROR_ZERO_RETURN: - /* SSL terminated (but socket may still be active) */ - retry = 0; - break; - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* re-negotiation, or perhaps the SSL layer needs more - * packets: retry in next iteration */ - errno = EAGAIN; - retry = is_init ? 1 : sslsock->s.is_blocked; - break; - case SSL_ERROR_SYSCALL: - if (ERR_peek_error() == 0) { - if (nr_bytes == 0) { - if (!is_http_stream_talking_to_iis(stream TSRMLS_CC) && ERR_get_error() != 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL: fatal protocol error"); - } - SSL_set_shutdown(sslsock->ssl_handle, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); - stream->eof = 1; - retry = 0; - } else { - char *estr = php_socket_strerror(php_socket_errno(), NULL, 0); - - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL: %s", estr); - - efree(estr); - retry = 0; - } - break; - } - - - /* fall through */ - default: - /* some other error */ - ecode = ERR_get_error(); - - switch (ERR_GET_REASON(ecode)) { - case SSL_R_NO_SHARED_CIPHER: - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL_R_NO_SHARED_CIPHER: no suitable shared cipher could be used. This could be because the server is missing an SSL certificate (local_cert context option)"); - retry = 0; - break; - - default: - do { - /* NULL is automatically added */ - ERR_error_string_n(ecode, esbuf, sizeof(esbuf)); - if (ebuf.c) { - smart_str_appendc(&ebuf, '\n'); - } - smart_str_appends(&ebuf, esbuf); - } while ((ecode = ERR_get_error()) != 0); - - smart_str_0(&ebuf); - - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL operation failed with code %d. %s%s", - err, - ebuf.c ? "OpenSSL Error messages:\n" : "", - ebuf.c ? ebuf.c : ""); - if (ebuf.c) { - smart_str_free(&ebuf); - } - } - - retry = 0; - errno = 0; - } - return retry; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int err = SSL_get_error(sslsock->ssl_handle, nr_bytes); + char esbuf[512]; + smart_str ebuf = {0}; + unsigned long ecode; + int retry = 1; + + switch(err) { + case SSL_ERROR_ZERO_RETURN: + /* SSL terminated (but socket may still be active) */ + retry = 0; + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* re-negotiation, or perhaps the SSL layer needs more + * packets: retry in next iteration */ + errno = EAGAIN; + retry = is_init ? 1 : sslsock->s.is_blocked; + break; + case SSL_ERROR_SYSCALL: + if (ERR_peek_error() == 0) { + if (nr_bytes == 0) { + if (!is_http_stream_talking_to_iis(stream TSRMLS_CC) && ERR_get_error() != 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL: fatal protocol error"); + } + SSL_set_shutdown(sslsock->ssl_handle, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); + stream->eof = 1; + retry = 0; + } else { + char *estr = php_socket_strerror(php_socket_errno(), NULL, 0); + + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL: %s", estr); + + efree(estr); + retry = 0; + } + break; + } + + + /* fall through */ + default: + /* some other error */ + ecode = ERR_get_error(); + + switch (ERR_GET_REASON(ecode)) { + case SSL_R_NO_SHARED_CIPHER: + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL_R_NO_SHARED_CIPHER: no suitable shared cipher could be used. This could be because the server is missing an SSL certificate (local_cert context option)"); + retry = 0; + break; + + default: + do { + /* NULL is automatically added */ + ERR_error_string_n(ecode, esbuf, sizeof(esbuf)); + if (ebuf.c) { + smart_str_appendc(&ebuf, '\n'); + } + smart_str_appends(&ebuf, esbuf); + } while ((ecode = ERR_get_error()) != 0); + + smart_str_0(&ebuf); + + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL operation failed with code %d. %s%s", + err, + ebuf.c ? "OpenSSL Error messages:\n" : "", + ebuf.c ? ebuf.c : ""); + if (ebuf.c) { + smart_str_free(&ebuf); + } + } + + retry = 0; + errno = 0; + } + return retry; } static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int didwrite; - - if (sslsock->ssl_active) { - int retry = 1; - - do { - didwrite = SSL_write(sslsock->ssl_handle, buf, count); - - if (didwrite <= 0) { - retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC); - } else { - break; - } - } while(retry); - - if (didwrite > 0) { - php_stream_notify_progress_increment(stream->context, didwrite, 0); - } - } else { - didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); - } - - if (didwrite < 0) { - didwrite = 0; - } - - return didwrite; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int didwrite; + + if (sslsock->ssl_active) { + int retry = 1; + struct timeval start_time, + *timeout; + int blocked = sslsock->s.is_blocked, + has_timeout = 0; + + if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { + sslsock->s.is_blocked = 0; + } + + timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; + has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); + + /* gettimeofday is not monotonic; using it here is not strictly correct */ + if (has_timeout) { + gettimeofday(&start_time, NULL); + } + + do { + struct timeval cur_time, + elapsed_time; + + if (has_timeout) { + gettimeofday(&cur_time, NULL); + elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; + elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; + if (cur_time.tv_usec < start_time.tv_usec) { + elapsed_time.tv_sec -= 1L; + elapsed_time.tv_usec += 1000000L; + } + + if (elapsed_time.tv_sec > timeout->tv_sec || + (elapsed_time.tv_sec == timeout->tv_sec && + elapsed_time.tv_usec > timeout->tv_usec)) { + if( blocked ) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; + } + + return -1; + } + } + + didwrite = SSL_write(sslsock->ssl_handle, buf, count); + + struct timeval left_time; + + if (has_timeout) { + left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; + left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; + if (timeout->tv_usec < elapsed_time.tv_usec) { + left_time.tv_sec -= 1L; + left_time.tv_usec += 1000000L; + } + } + + if (didwrite <= 0) { + int err = SSL_get_error(sslsock->ssl_handle, didwrite ); + retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC); + + if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; + + if( retry ) { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); + } + } else { + int err = SSL_get_error(sslsock->ssl_handle, didwrite ); + + if( err == SSL_ERROR_NONE ) + break; + + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); + } + } while(retry); + + if( blocked ) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; + } + + if (didwrite > 0) { + php_stream_notify_progress_increment(stream->context, didwrite, 0); + } + } else { + didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); + } + + if (didwrite < 0) { + didwrite = 0; + } + + return didwrite; } static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) -{ - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int nr_bytes = 0; - - if (sslsock->ssl_active) { - int retry = 1; - - do { - nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); - - if (nr_bytes <= 0) { - retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC); - stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); - - } else { - /* we got the data */ - break; - } - } while (retry); - - if (nr_bytes > 0) { - php_stream_notify_progress_increment(stream->context, nr_bytes, 0); - } - } - else - { - nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); - } - - if (nr_bytes < 0) { - nr_bytes = 0; - } - - return nr_bytes; +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int nr_bytes = 0; + + if (sslsock->ssl_active) { + int retry = 1; + struct timeval start_time, + *timeout; + int blocked = sslsock->s.is_blocked, + has_timeout = 0; + + if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { + sslsock->s.is_blocked = 0; + } + + timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; + has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); + + /* gettimeofday is not monotonic; using it here is not strictly correct */ + if (has_timeout) { + gettimeofday(&start_time, NULL); + } + + do { + struct timeval cur_time, + elapsed_time; + + if (has_timeout) { + gettimeofday(&cur_time, NULL); + elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; + elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; + if (cur_time.tv_usec < start_time.tv_usec) { + elapsed_time.tv_sec -= 1L; + elapsed_time.tv_usec += 1000000L; + } + + if (elapsed_time.tv_sec > timeout->tv_sec || + (elapsed_time.tv_sec == timeout->tv_sec && + elapsed_time.tv_usec > timeout->tv_usec)) { + + if( blocked ) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; + } + + return -1; + } + } + + nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); + + struct timeval left_time; + + if (has_timeout) { + left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; + left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; + if (timeout->tv_usec < elapsed_time.tv_usec) { + left_time.tv_sec -= 1L; + left_time.tv_usec += 1000000L; + } + } + + if (nr_bytes <= 0) { + + int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); + + retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC); + + if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; + + stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); + + if( retry ) { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); + } + + } else { + /* we got the data */ + int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); + + if( err == SSL_ERROR_NONE ) + break; + + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); + } + + } while (retry); + + if (nr_bytes > 0) { + php_stream_notify_progress_increment(stream->context, nr_bytes, 0); + } + + if( blocked ) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; + } + } + else + { + nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); + } + + if (nr_bytes < 0) { + nr_bytes = 0; + } + + return nr_bytes; } static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; #ifdef PHP_WIN32 - int n; + int n; #endif - if (close_handle) { - if (sslsock->ssl_active) { - SSL_shutdown(sslsock->ssl_handle); - sslsock->ssl_active = 0; - } - if (sslsock->ssl_handle) { - SSL_free(sslsock->ssl_handle); - sslsock->ssl_handle = NULL; - } - if (sslsock->ctx) { - SSL_CTX_free(sslsock->ctx); - sslsock->ctx = NULL; - } + if (close_handle) { + if (sslsock->ssl_active) { + SSL_shutdown(sslsock->ssl_handle); + sslsock->ssl_active = 0; + } + if (sslsock->ssl_handle) { + SSL_free(sslsock->ssl_handle); + sslsock->ssl_handle = NULL; + } + if (sslsock->ctx) { + SSL_CTX_free(sslsock->ctx); + sslsock->ctx = NULL; + } #ifdef PHP_WIN32 - if (sslsock->s.socket == -1) - sslsock->s.socket = SOCK_ERR; + if (sslsock->s.socket == -1) + sslsock->s.socket = SOCK_ERR; #endif - if (sslsock->s.socket != SOCK_ERR) { + if (sslsock->s.socket != SOCK_ERR) { #ifdef PHP_WIN32 - /* prevent more data from coming in */ - shutdown(sslsock->s.socket, SHUT_RD); - - /* try to make sure that the OS sends all data before we close the connection. - * Essentially, we are waiting for the socket to become writeable, which means - * that all pending data has been sent. - * We use a small timeout which should encourage the OS to send the data, - * but at the same time avoid hanging indefintely. - * */ - do { - n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500); - } while (n == -1 && php_socket_errno() == EINTR); + /* prevent more data from coming in */ + shutdown(sslsock->s.socket, SHUT_RD); + + /* try to make sure that the OS sends all data before we close the connection. + * Essentially, we are waiting for the socket to become writeable, which means + * that all pending data has been sent. + * We use a small timeout which should encourage the OS to send the data, + * but at the same time avoid hanging indefintely. + * */ + do { + n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500); + } while (n == -1 && php_socket_errno() == EINTR); #endif - closesocket(sslsock->s.socket); - sslsock->s.socket = SOCK_ERR; - } - } - - if (sslsock->sni) { - pefree(sslsock->sni, php_stream_is_persistent(stream)); - } - pefree(sslsock, php_stream_is_persistent(stream)); - - return 0; + closesocket(sslsock->s.socket); + sslsock->s.socket = SOCK_ERR; + } + } + + if (sslsock->sni) { + pefree(sslsock->sni, php_stream_is_persistent(stream)); + } + pefree(sslsock, php_stream_is_persistent(stream)); + + return 0; } static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC) { - return php_stream_socket_ops.flush(stream TSRMLS_CC); + return php_stream_socket_ops.flush(stream TSRMLS_CC); } static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) { - return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC); + return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC); } static inline int php_openssl_setup_crypto(php_stream *stream, - php_openssl_netstream_data_t *sslsock, - php_stream_xport_crypto_param *cparam - TSRMLS_DC) + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + TSRMLS_DC) { - SSL_METHOD *method; - long ssl_ctx_options = SSL_OP_ALL; - - if (sslsock->ssl_handle) { - if (sslsock->s.is_blocked) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream"); - return -1; - } else { - return 0; - } - } - - /* need to do slightly different things, based on client/server method, - * so lets remember which method was selected */ - - switch (cparam->inputs.method) { - case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: - sslsock->is_client = 1; - method = SSLv23_client_method(); - break; - case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: + SSL_METHOD *method; + long ssl_ctx_options = SSL_OP_ALL; + + if (sslsock->ssl_handle) { + if (sslsock->s.is_blocked) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream"); + return -1; + } else { + return 0; + } + } + + /* need to do slightly different things, based on client/server method, + * so lets remember which method was selected */ + + switch (cparam->inputs.method) { + case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: + sslsock->is_client = 1; + method = SSLv23_client_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: #ifdef OPENSSL_NO_SSL2 - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); - return -1; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); + return -1; #else - sslsock->is_client = 1; - method = SSLv2_client_method(); - break; + sslsock->is_client = 1; + method = SSLv2_client_method(); + break; #endif - case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: - sslsock->is_client = 1; - method = SSLv3_client_method(); - break; - case STREAM_CRYPTO_METHOD_TLS_CLIENT: - sslsock->is_client = 1; - method = TLSv1_client_method(); - break; - case STREAM_CRYPTO_METHOD_SSLv23_SERVER: - sslsock->is_client = 0; - method = SSLv23_server_method(); - break; - case STREAM_CRYPTO_METHOD_SSLv3_SERVER: - sslsock->is_client = 0; - method = SSLv3_server_method(); - break; - case STREAM_CRYPTO_METHOD_SSLv2_SERVER: + case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: + sslsock->is_client = 1; + method = SSLv3_client_method(); + break; + case STREAM_CRYPTO_METHOD_TLS_CLIENT: + sslsock->is_client = 1; + method = TLSv1_client_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv23_SERVER: + sslsock->is_client = 0; + method = SSLv23_server_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv3_SERVER: + sslsock->is_client = 0; + method = SSLv3_server_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv2_SERVER: #ifdef OPENSSL_NO_SSL2 - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); - return -1; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); + return -1; #else - sslsock->is_client = 0; - method = SSLv2_server_method(); - break; + sslsock->is_client = 0; + method = SSLv2_server_method(); + break; #endif - case STREAM_CRYPTO_METHOD_TLS_SERVER: - sslsock->is_client = 0; - method = TLSv1_server_method(); - break; - default: - return -1; + case STREAM_CRYPTO_METHOD_TLS_SERVER: + sslsock->is_client = 0; + method = TLSv1_server_method(); + break; + default: + return -1; - } + } - sslsock->ctx = SSL_CTX_new(method); - if (sslsock->ctx == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context"); - return -1; - } + sslsock->ctx = SSL_CTX_new(method); + if (sslsock->ctx == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context"); + return -1; + } #if OPENSSL_VERSION_NUMBER >= 0x0090605fL - ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; #endif - SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options); + SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options); #if OPENSSL_VERSION_NUMBER >= 0x0090806fL - { - zval **val; - - if (stream->context && SUCCESS == php_stream_context_get_option( - stream->context, "ssl", "no_ticket", &val) && - zval_is_true(*val)) { - SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_TICKET); - } - } + { + zval **val; + + if (stream->context && SUCCESS == php_stream_context_get_option( + stream->context, "ssl", "no_ticket", &val) && + zval_is_true(*val)) { + SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_TICKET); + } + } #endif - sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC); - if (sslsock->ssl_handle == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle"); - SSL_CTX_free(sslsock->ctx); - sslsock->ctx = NULL; - return -1; - } - - if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { - handle_ssl_error(stream, 0, 1 TSRMLS_CC); - } - - if (cparam->inputs.session) { - if (cparam->inputs.session->ops != &php_openssl_socket_ops) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream"); - } else if (((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied SSL session stream is not initialized"); - } else { - SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle); - } - } - return 0; + sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC); + if (sslsock->ssl_handle == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle"); + SSL_CTX_free(sslsock->ctx); + sslsock->ctx = NULL; + return -1; + } + + if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { + handle_ssl_error(stream, 0, 1 TSRMLS_CC); + } + + if (cparam->inputs.session) { + if (cparam->inputs.session->ops != &php_openssl_socket_ops) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream"); + } else if (((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied SSL session stream is not initialized"); + } else { + SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle); + } + } + return 0; } static inline int php_openssl_enable_crypto(php_stream *stream, - php_openssl_netstream_data_t *sslsock, - php_stream_xport_crypto_param *cparam - TSRMLS_DC) + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + TSRMLS_DC) { - int n, retry = 1; + int n, retry = 1; - if (cparam->inputs.activate && !sslsock->ssl_active) { - struct timeval start_time, - *timeout; - int blocked = sslsock->s.is_blocked, - has_timeout = 0; + if (cparam->inputs.activate && !sslsock->ssl_active) { + struct timeval start_time, + *timeout; + int blocked = sslsock->s.is_blocked, + has_timeout = 0; #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) - if (sslsock->is_client && sslsock->sni) { - SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->sni); - } + if (sslsock->is_client && sslsock->sni) { + SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->sni); + } #endif - if (!sslsock->state_set) { - if (sslsock->is_client) { - SSL_set_connect_state(sslsock->ssl_handle); - } else { - SSL_set_accept_state(sslsock->ssl_handle); - } - sslsock->state_set = 1; - } - - if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { - sslsock->s.is_blocked = 0; - } - - timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; - has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); - /* gettimeofday is not monotonic; using it here is not strictly correct */ - if (has_timeout) { - gettimeofday(&start_time, NULL); - } - - do { - struct timeval cur_time, - elapsed_time; - - if (sslsock->is_client) { - n = SSL_connect(sslsock->ssl_handle); - } else { - n = SSL_accept(sslsock->ssl_handle); - } - - if (has_timeout) { - gettimeofday(&cur_time, NULL); - elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; - elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; - if (cur_time.tv_usec < start_time.tv_usec) { - elapsed_time.tv_sec -= 1L; - elapsed_time.tv_usec += 1000000L; - } - - if (elapsed_time.tv_sec > timeout->tv_sec || - (elapsed_time.tv_sec == timeout->tv_sec && - elapsed_time.tv_usec > timeout->tv_usec)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout"); - return -1; - } - } - - if (n <= 0) { - /* in case of SSL_ERROR_WANT_READ/WRITE, do not retry in non-blocking mode */ - retry = handle_ssl_error(stream, n, blocked TSRMLS_CC); - if (retry) { - /* wait until something interesting happens in the socket. It may be a - * timeout. Also consider the unlikely of possibility of a write block */ - int err = SSL_get_error(sslsock->ssl_handle, n); - struct timeval left_time; - - if (has_timeout) { - left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; - left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; - if (timeout->tv_usec < elapsed_time.tv_usec) { - left_time.tv_sec -= 1L; - left_time.tv_usec += 1000000L; - } - } - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? - (POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL); - } - } else { - retry = 0; - } - } while (retry); - - if (sslsock->s.is_blocked != blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) { - sslsock->s.is_blocked = blocked; - } - - if (n == 1) { - X509 *peer_cert; - - peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle); - - if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) { - SSL_shutdown(sslsock->ssl_handle); - n = -1; - } else { - sslsock->ssl_active = 1; - - /* allow the script to capture the peer cert - * and/or the certificate chain */ - if (stream->context) { - zval **val, *zcert; - - if (SUCCESS == php_stream_context_get_option( - stream->context, "ssl", - "capture_peer_cert", &val) && - zval_is_true(*val)) { - MAKE_STD_ZVAL(zcert); - ZVAL_RESOURCE(zcert, zend_list_insert(peer_cert, - php_openssl_get_x509_list_id() TSRMLS_CC)); - php_stream_context_set_option(stream->context, - "ssl", "peer_certificate", - zcert); - peer_cert = NULL; - FREE_ZVAL(zcert); - } - - if (SUCCESS == php_stream_context_get_option( - stream->context, "ssl", - "capture_peer_cert_chain", &val) && - zval_is_true(*val)) { - zval *arr; - STACK_OF(X509) *chain; - - MAKE_STD_ZVAL(arr); - chain = SSL_get_peer_cert_chain( - sslsock->ssl_handle); - - if (chain && sk_X509_num(chain) > 0) { - int i; - array_init(arr); - - for (i = 0; i < sk_X509_num(chain); i++) { - X509 *mycert = X509_dup( - sk_X509_value(chain, i)); - MAKE_STD_ZVAL(zcert); - ZVAL_RESOURCE(zcert, - zend_list_insert(mycert, - php_openssl_get_x509_list_id() TSRMLS_CC)); - add_next_index_zval(arr, zcert); - } - - } else { - ZVAL_NULL(arr); - } - - php_stream_context_set_option(stream->context, - "ssl", "peer_certificate_chain", - arr); - zval_dtor(arr); - efree(arr); - } - } - } - - if (peer_cert) { - X509_free(peer_cert); - } - } else { - n = errno == EAGAIN ? 0 : -1; - } - - return n; - - } else if (!cparam->inputs.activate && sslsock->ssl_active) { - /* deactivate - common for server/client */ - SSL_shutdown(sslsock->ssl_handle); - sslsock->ssl_active = 0; - } - return -1; + if (!sslsock->state_set) { + if (sslsock->is_client) { + SSL_set_connect_state(sslsock->ssl_handle); + } else { + SSL_set_accept_state(sslsock->ssl_handle); + } + sslsock->state_set = 1; + } + + if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { + sslsock->s.is_blocked = 0; + } + + timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; + has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); + /* gettimeofday is not monotonic; using it here is not strictly correct */ + if (has_timeout) { + gettimeofday(&start_time, NULL); + } + + do { + struct timeval cur_time, + elapsed_time; + + if (sslsock->is_client) { + n = SSL_connect(sslsock->ssl_handle); + } else { + n = SSL_accept(sslsock->ssl_handle); + } + + if (has_timeout) { + gettimeofday(&cur_time, NULL); + elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; + elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; + if (cur_time.tv_usec < start_time.tv_usec) { + elapsed_time.tv_sec -= 1L; + elapsed_time.tv_usec += 1000000L; + } + + if (elapsed_time.tv_sec > timeout->tv_sec || + (elapsed_time.tv_sec == timeout->tv_sec && + elapsed_time.tv_usec > timeout->tv_usec)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout"); + return -1; + } + } + + if (n <= 0) { + /* in case of SSL_ERROR_WANT_READ/WRITE, do not retry in non-blocking mode */ + retry = handle_ssl_error(stream, n, blocked TSRMLS_CC); + if (retry) { + /* wait until something interesting happens in the socket. It may be a + * timeout. Also consider the unlikely of possibility of a write block */ + int err = SSL_get_error(sslsock->ssl_handle, n); + struct timeval left_time; + + if (has_timeout) { + left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; + left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; + if (timeout->tv_usec < elapsed_time.tv_usec) { + left_time.tv_sec -= 1L; + left_time.tv_usec += 1000000L; + } + } + + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL); + } + } else { + retry = 0; + } + } while (retry); + + if (sslsock->s.is_blocked != blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) { + sslsock->s.is_blocked = blocked; + } + + if (n == 1) { + X509 *peer_cert; + + peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle); + + if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) { + SSL_shutdown(sslsock->ssl_handle); + n = -1; + } else { + sslsock->ssl_active = 1; + + /* allow the script to capture the peer cert + * and/or the certificate chain */ + if (stream->context) { + zval **val, *zcert; + + if (SUCCESS == php_stream_context_get_option( + stream->context, "ssl", + "capture_peer_cert", &val) && + zval_is_true(*val)) { + MAKE_STD_ZVAL(zcert); + ZVAL_RESOURCE(zcert, zend_list_insert(peer_cert, + php_openssl_get_x509_list_id() TSRMLS_CC)); + php_stream_context_set_option(stream->context, + "ssl", "peer_certificate", + zcert); + peer_cert = NULL; + FREE_ZVAL(zcert); + } + + if (SUCCESS == php_stream_context_get_option( + stream->context, "ssl", + "capture_peer_cert_chain", &val) && + zval_is_true(*val)) { + zval *arr; + STACK_OF(X509) *chain; + + MAKE_STD_ZVAL(arr); + chain = SSL_get_peer_cert_chain( + sslsock->ssl_handle); + + if (chain && sk_X509_num(chain) > 0) { + int i; + array_init(arr); + + for (i = 0; i < sk_X509_num(chain); i++) { + X509 *mycert = X509_dup( + sk_X509_value(chain, i)); + MAKE_STD_ZVAL(zcert); + ZVAL_RESOURCE(zcert, + zend_list_insert(mycert, + php_openssl_get_x509_list_id() TSRMLS_CC)); + add_next_index_zval(arr, zcert); + } + + } else { + ZVAL_NULL(arr); + } + + php_stream_context_set_option(stream->context, + "ssl", "peer_certificate_chain", + arr); + zval_dtor(arr); + efree(arr); + } + } + } + + if (peer_cert) { + X509_free(peer_cert); + } + } else { + n = errno == EAGAIN ? 0 : -1; + } + + return n; + + } else if (!cparam->inputs.activate && sslsock->ssl_active) { + /* deactivate - common for server/client */ + SSL_shutdown(sslsock->ssl_handle); + sslsock->ssl_active = 0; + } + return -1; } static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_netstream_data_t *sock, - php_stream_xport_param *xparam STREAMS_DC TSRMLS_DC) + php_stream_xport_param *xparam STREAMS_DC TSRMLS_DC) { - int clisock; - - xparam->outputs.client = NULL; - - clisock = php_network_accept_incoming(sock->s.socket, - xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, - xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL, - xparam->want_addr ? &xparam->outputs.addr : NULL, - xparam->want_addr ? &xparam->outputs.addrlen : NULL, - xparam->inputs.timeout, - xparam->want_errortext ? &xparam->outputs.error_text : NULL, - &xparam->outputs.error_code - TSRMLS_CC); - - if (clisock >= 0) { - php_openssl_netstream_data_t *clisockdata; - - clisockdata = emalloc(sizeof(*clisockdata)); - - if (clisockdata == NULL) { - closesocket(clisock); - /* technically a fatal error */ - } else { - /* copy underlying tcp fields */ - memset(clisockdata, 0, sizeof(*clisockdata)); - memcpy(clisockdata, sock, sizeof(clisockdata->s)); - - clisockdata->s.socket = clisock; - - xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); - if (xparam->outputs.client) { - xparam->outputs.client->context = stream->context; - if (stream->context) { - zend_list_addref(stream->context->rsrc_id); - } - } - } - - if (xparam->outputs.client && sock->enable_on_connect) { - /* apply crypto */ - switch (sock->method) { - case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_SSLv23_SERVER; - break; - case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_SSLv2_SERVER; - break; - case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_SSLv3_SERVER; - break; - case STREAM_CRYPTO_METHOD_TLS_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_TLS_SERVER; - break; - default: - break; - } - - clisockdata->method = sock->method; - - if (php_stream_xport_crypto_setup(xparam->outputs.client, clisockdata->method, - NULL TSRMLS_CC) < 0 || php_stream_xport_crypto_enable( - xparam->outputs.client, 1 TSRMLS_CC) < 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); - - php_stream_close(xparam->outputs.client); - xparam->outputs.client = NULL; - xparam->outputs.returncode = -1; - } - } - } - - return xparam->outputs.client == NULL ? -1 : 0; + int clisock; + + xparam->outputs.client = NULL; + + clisock = php_network_accept_incoming(sock->s.socket, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL, + xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &xparam->outputs.error_code + TSRMLS_CC); + + if (clisock >= 0) { + php_openssl_netstream_data_t *clisockdata; + + clisockdata = emalloc(sizeof(*clisockdata)); + + if (clisockdata == NULL) { + closesocket(clisock); + /* technically a fatal error */ + } else { + /* copy underlying tcp fields */ + memset(clisockdata, 0, sizeof(*clisockdata)); + memcpy(clisockdata, sock, sizeof(clisockdata->s)); + + clisockdata->s.socket = clisock; + + xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); + if (xparam->outputs.client) { + xparam->outputs.client->context = stream->context; + if (stream->context) { + zend_list_addref(stream->context->rsrc_id); + } + } + } + + if (xparam->outputs.client && sock->enable_on_connect) { + /* apply crypto */ + switch (sock->method) { + case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: + sock->method = STREAM_CRYPTO_METHOD_SSLv23_SERVER; + break; + case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: + sock->method = STREAM_CRYPTO_METHOD_SSLv2_SERVER; + break; + case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: + sock->method = STREAM_CRYPTO_METHOD_SSLv3_SERVER; + break; + case STREAM_CRYPTO_METHOD_TLS_CLIENT: + sock->method = STREAM_CRYPTO_METHOD_TLS_SERVER; + break; + default: + break; + } + + clisockdata->method = sock->method; + + if (php_stream_xport_crypto_setup(xparam->outputs.client, clisockdata->method, + NULL TSRMLS_CC) < 0 || php_stream_xport_crypto_enable( + xparam->outputs.client, 1 TSRMLS_CC) < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); + + php_stream_close(xparam->outputs.client); + xparam->outputs.client = NULL; + xparam->outputs.returncode = -1; + } + } + } + + return xparam->outputs.client == NULL ? -1 : 0; } static int php_openssl_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - php_stream_xport_crypto_param *cparam = (php_stream_xport_crypto_param *)ptrparam; - php_stream_xport_param *xparam = (php_stream_xport_param *)ptrparam; - - switch (option) { - case PHP_STREAM_OPTION_CHECK_LIVENESS: - { - struct timeval tv; - char buf; - int alive = 1; - - if (value == -1) { - if (sslsock->s.timeout.tv_sec == -1) { - tv.tv_sec = FG(default_socket_timeout); - tv.tv_usec = 0; - } else { - tv = sslsock->connect_timeout; - } - } else { - tv.tv_sec = value; - tv.tv_usec = 0; - } - - if (sslsock->s.socket == -1) { - alive = 0; - } else if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) { - if (sslsock->ssl_active) { - int n; - - do { - n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf)); - if (n <= 0) { - int err = SSL_get_error(sslsock->ssl_handle, n); - - if (err == SSL_ERROR_SYSCALL) { - alive = php_socket_errno() == EAGAIN; - break; - } - - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { - /* re-negotiate */ - continue; - } - - /* any other problem is a fatal error */ - alive = 0; - } - /* either peek succeeded or there was an error; we - * have set the alive flag appropriately */ - break; - } while (1); - } else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) { - alive = 0; - } - } - return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; - } - - case PHP_STREAM_OPTION_CRYPTO_API: - - switch(cparam->op) { - - case STREAM_XPORT_CRYPTO_OP_SETUP: - cparam->outputs.returncode = php_openssl_setup_crypto(stream, sslsock, cparam TSRMLS_CC); - return PHP_STREAM_OPTION_RETURN_OK; - break; - case STREAM_XPORT_CRYPTO_OP_ENABLE: - cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam TSRMLS_CC); - return PHP_STREAM_OPTION_RETURN_OK; - break; - default: - /* fall through */ - break; - } - - break; - - case PHP_STREAM_OPTION_XPORT_API: - switch(xparam->op) { - - case STREAM_XPORT_OP_CONNECT: - case STREAM_XPORT_OP_CONNECT_ASYNC: - /* TODO: Async connects need to check the enable_on_connect option when - * we notice that the connect has actually been established */ - php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); - - if ((sslsock->enable_on_connect) && - ((xparam->outputs.returncode == 0) || - (xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && - xparam->outputs.returncode == 1 && xparam->outputs.error_code == EINPROGRESS))) - { - if (php_stream_xport_crypto_setup(stream, sslsock->method, NULL TSRMLS_CC) < 0 || - php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); - xparam->outputs.returncode = -1; - } - } - return PHP_STREAM_OPTION_RETURN_OK; - - case STREAM_XPORT_OP_ACCEPT: - /* we need to copy the additional fields that the underlying tcp transport - * doesn't know about */ - xparam->outputs.returncode = php_openssl_tcp_sockop_accept(stream, sslsock, xparam STREAMS_CC TSRMLS_CC); - - - return PHP_STREAM_OPTION_RETURN_OK; - - default: - /* fall through */ - break; - } - } - - return php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + php_stream_xport_crypto_param *cparam = (php_stream_xport_crypto_param *)ptrparam; + php_stream_xport_param *xparam = (php_stream_xport_param *)ptrparam; + + switch (option) { + case PHP_STREAM_OPTION_CHECK_LIVENESS: + { + struct timeval tv; + char buf; + int alive = 1; + + if (value == -1) { + if (sslsock->s.timeout.tv_sec == -1) { + tv.tv_sec = FG(default_socket_timeout); + tv.tv_usec = 0; + } else { + tv = sslsock->connect_timeout; + } + } else { + tv.tv_sec = value; + tv.tv_usec = 0; + } + + if (sslsock->s.socket == -1) { + alive = 0; + } else if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) { + if (sslsock->ssl_active) { + int n; + + do { + n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf)); + if (n <= 0) { + int err = SSL_get_error(sslsock->ssl_handle, n); + + if (err == SSL_ERROR_SYSCALL) { + alive = php_socket_errno() == EAGAIN; + break; + } + + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + /* re-negotiate */ + continue; + } + + /* any other problem is a fatal error */ + alive = 0; + } + /* either peek succeeded or there was an error; we + * have set the alive flag appropriately */ + break; + } while (1); + } else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) { + alive = 0; + } + } + return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } + + case PHP_STREAM_OPTION_CRYPTO_API: + + switch(cparam->op) { + + case STREAM_XPORT_CRYPTO_OP_SETUP: + cparam->outputs.returncode = php_openssl_setup_crypto(stream, sslsock, cparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + break; + case STREAM_XPORT_CRYPTO_OP_ENABLE: + cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + break; + default: + /* fall through */ + break; + } + + break; + + case PHP_STREAM_OPTION_XPORT_API: + switch(xparam->op) { + + case STREAM_XPORT_OP_CONNECT: + case STREAM_XPORT_OP_CONNECT_ASYNC: + /* TODO: Async connects need to check the enable_on_connect option when + * we notice that the connect has actually been established */ + php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); + + if ((sslsock->enable_on_connect) && + ((xparam->outputs.returncode == 0) || + (xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && + xparam->outputs.returncode == 1 && xparam->outputs.error_code == EINPROGRESS))) + { + if (php_stream_xport_crypto_setup(stream, sslsock->method, NULL TSRMLS_CC) < 0 || + php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); + xparam->outputs.returncode = -1; + } + } + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_ACCEPT: + /* we need to copy the additional fields that the underlying tcp transport + * doesn't know about */ + xparam->outputs.returncode = php_openssl_tcp_sockop_accept(stream, sslsock, xparam STREAMS_CC TSRMLS_CC); + + + return PHP_STREAM_OPTION_RETURN_OK; + + default: + /* fall through */ + break; + } + } + + return php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); } static int php_openssl_sockop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - - switch(castas) { - case PHP_STREAM_AS_STDIO: - if (sslsock->ssl_active) { - return FAILURE; - } - if (ret) { - *ret = fdopen(sslsock->s.socket, stream->mode); - if (*ret) { - return SUCCESS; - } - return FAILURE; - } - return SUCCESS; - - case PHP_STREAM_AS_FD_FOR_SELECT: - if (ret) { - *(int *)ret = sslsock->s.socket; - } - return SUCCESS; - - case PHP_STREAM_AS_FD: - case PHP_STREAM_AS_SOCKETD: - if (sslsock->ssl_active) { - return FAILURE; - } - if (ret) { - *(int *)ret = sslsock->s.socket; - } - return SUCCESS; - default: - return FAILURE; - } + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + + switch(castas) { + case PHP_STREAM_AS_STDIO: + if (sslsock->ssl_active) { + return FAILURE; + } + if (ret) { + *ret = fdopen(sslsock->s.socket, stream->mode); + if (*ret) { + return SUCCESS; + } + return FAILURE; + } + return SUCCESS; + + case PHP_STREAM_AS_FD_FOR_SELECT: + if (ret) { + *(int *)ret = sslsock->s.socket; + } + return SUCCESS; + + case PHP_STREAM_AS_FD: + case PHP_STREAM_AS_SOCKETD: + if (sslsock->ssl_active) { + return FAILURE; + } + if (ret) { + *(int *)ret = sslsock->s.socket; + } + return SUCCESS; + default: + return FAILURE; + } } php_stream_ops php_openssl_socket_ops = { - php_openssl_sockop_write, php_openssl_sockop_read, - php_openssl_sockop_close, php_openssl_sockop_flush, - "tcp_socket/ssl", - NULL, /* seek */ - php_openssl_sockop_cast, - php_openssl_sockop_stat, - php_openssl_sockop_set_option, + php_openssl_sockop_write, php_openssl_sockop_read, + php_openssl_sockop_close, php_openssl_sockop_flush, + "tcp_socket/ssl", + NULL, /* seek */ + php_openssl_sockop_cast, + php_openssl_sockop_stat, + php_openssl_sockop_set_option, }; static char * get_sni(php_stream_context *ctx, char *resourcename, long resourcenamelen, int is_persistent TSRMLS_DC) { - php_url *url; - - if (ctx) { - zval **val = NULL; - - if (php_stream_context_get_option(ctx, "ssl", "SNI_enabled", &val) == SUCCESS && !zend_is_true(*val)) { - return NULL; - } - if (php_stream_context_get_option(ctx, "ssl", "SNI_server_name", &val) == SUCCESS) { - convert_to_string_ex(val); - return pestrdup(Z_STRVAL_PP(val), is_persistent); - } - } - - if (!resourcename) { - return NULL; - } - - url = php_url_parse_ex(resourcename, resourcenamelen); - if (!url) { - return NULL; - } - - if (url->host) { - const char * host = url->host; - char * sni = NULL; - size_t len = strlen(host); - - /* skip trailing dots */ - while (len && host[len-1] == '.') { - --len; - } - - if (len) { - sni = pestrndup(host, len, is_persistent); - } - - php_url_free(url); - return sni; - } - - php_url_free(url); - return NULL; + php_url *url; + + if (ctx) { + zval **val = NULL; + + if (php_stream_context_get_option(ctx, "ssl", "SNI_enabled", &val) == SUCCESS && !zend_is_true(*val)) { + return NULL; + } + if (php_stream_context_get_option(ctx, "ssl", "SNI_server_name", &val) == SUCCESS) { + convert_to_string_ex(val); + return pestrdup(Z_STRVAL_PP(val), is_persistent); + } + } + + if (!resourcename) { + return NULL; + } + + url = php_url_parse_ex(resourcename, resourcenamelen); + if (!url) { + return NULL; + } + + if (url->host) { + const char * host = url->host; + char * sni = NULL; + size_t len = strlen(host); + + /* skip trailing dots */ + while (len && host[len-1] == '.') { + --len; + } + + if (len) { + sni = pestrndup(host, len, is_persistent); + } + + php_url_free(url); + return sni; + } + + php_url_free(url); + return NULL; } php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, - char *resourcename, long resourcenamelen, - const char *persistent_id, int options, int flags, - struct timeval *timeout, - php_stream_context *context STREAMS_DC TSRMLS_DC) + char *resourcename, long resourcenamelen, + const char *persistent_id, int options, int flags, + struct timeval *timeout, + php_stream_context *context STREAMS_DC TSRMLS_DC) { - php_stream *stream = NULL; - php_openssl_netstream_data_t *sslsock = NULL; - - sslsock = pemalloc(sizeof(php_openssl_netstream_data_t), persistent_id ? 1 : 0); - memset(sslsock, 0, sizeof(*sslsock)); - - sslsock->s.is_blocked = 1; - /* this timeout is used by standard stream funcs, therefor it should use the default value */ - sslsock->s.timeout.tv_sec = FG(default_socket_timeout); - sslsock->s.timeout.tv_usec = 0; - - /* use separate timeout for our private funcs */ - sslsock->connect_timeout.tv_sec = timeout->tv_sec; - sslsock->connect_timeout.tv_usec = timeout->tv_usec; - - /* we don't know the socket until we have determined if we are binding or - * connecting */ - sslsock->s.socket = -1; - - /* Initialize context as NULL */ - sslsock->ctx = NULL; - - stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+"); - - if (stream == NULL) { - pefree(sslsock, persistent_id ? 1 : 0); - return NULL; - } - - sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id TSRMLS_CC); - - if (strncmp(proto, "ssl", protolen) == 0) { - sslsock->enable_on_connect = 1; - sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; - } else if (strncmp(proto, "sslv2", protolen) == 0) { + php_stream *stream = NULL; + php_openssl_netstream_data_t *sslsock = NULL; + + sslsock = pemalloc(sizeof(php_openssl_netstream_data_t), persistent_id ? 1 : 0); + memset(sslsock, 0, sizeof(*sslsock)); + + sslsock->s.is_blocked = 1; + /* this timeout is used by standard stream funcs, therefor it should use the default value */ + sslsock->s.timeout.tv_sec = FG(default_socket_timeout); + sslsock->s.timeout.tv_usec = 0; + + /* use separate timeout for our private funcs */ + sslsock->connect_timeout.tv_sec = timeout->tv_sec; + sslsock->connect_timeout.tv_usec = timeout->tv_usec; + + /* we don't know the socket until we have determined if we are binding or + * connecting */ + sslsock->s.socket = -1; + + /* Initialize context as NULL */ + sslsock->ctx = NULL; + + stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+"); + + if (stream == NULL) { + pefree(sslsock, persistent_id ? 1 : 0); + return NULL; + } + + sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id TSRMLS_CC); + + if (strncmp(proto, "ssl", protolen) == 0) { + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + } else if (strncmp(proto, "sslv2", protolen) == 0) { #ifdef OPENSSL_NO_SSL2 - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); - return NULL; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); + return NULL; #else - sslsock->enable_on_connect = 1; - sslsock->method = STREAM_CRYPTO_METHOD_SSLv2_CLIENT; + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_SSLv2_CLIENT; #endif - } else if (strncmp(proto, "sslv3", protolen) == 0) { - sslsock->enable_on_connect = 1; - sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT; - } else if (strncmp(proto, "tls", protolen) == 0) { - sslsock->enable_on_connect = 1; - sslsock->method = STREAM_CRYPTO_METHOD_TLS_CLIENT; - } - - return stream; + } else if (strncmp(proto, "sslv3", protolen) == 0) { + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT; + } else if (strncmp(proto, "tls", protolen) == 0) { + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + } + + return stream; } @@ -957,3 +1103,4 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ + From fae955a1ae8705f39abc7df545cc9361c79e656c Mon Sep 17 00:00:00 2001 From: Brad Broerman Date: Mon, 21 Jan 2013 11:35:58 -0500 Subject: [PATCH 2/4] Updated to fix async reads/writes. --- ext/openssl/xp_ssl.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 6f9a45ae81e09..6445bcef7e962 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -240,7 +240,7 @@ static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; - if( retry ) { + if( retry && blocked ) { php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); } @@ -249,9 +249,10 @@ static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size if( err == SSL_ERROR_NONE ) break; - - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? - (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); + if( blocked ) { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); + } } } while(retry); @@ -347,7 +348,7 @@ static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t coun stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); - if( retry ) { + if( retry && blocked ) { php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); } @@ -358,9 +359,10 @@ static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t coun if( err == SSL_ERROR_NONE ) break; - - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? - (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); + if( blocked ) { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); + } } } while (retry); @@ -373,9 +375,7 @@ static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t coun php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); sslsock->s.is_blocked = 1; } - } - else - { + } else { nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); } @@ -1104,3 +1104,4 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, * vim<600: noet sw=4 ts=4 */ + From 40bebe54f9eb9ed604cb9730c85768e3f42fd554 Mon Sep 17 00:00:00 2001 From: Brad Broerman Date: Thu, 24 Jan 2013 20:22:01 -0500 Subject: [PATCH 3/4] Fixed whitespace, tabbing, etc. Updated to meet existing style, and PHP coding standards. --- ext/openssl/xp_ssl.c | 1840 +++++++++++++++++++++--------------------- 1 file changed, 918 insertions(+), 922 deletions(-) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 6445bcef7e962..1385136249ce4 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2012 The PHP Group | + | Copyright (c) 1997-2013 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | @@ -48,16 +48,16 @@ int php_openssl_get_x509_list_id(void); typedef struct _php_openssl_netstream_data_t { php_netstream_data_t s; - SSL *ssl_handle; - SSL_CTX *ctx; - struct timeval connect_timeout; - int enable_on_connect; - int is_client; - int ssl_active; - php_stream_xport_crypt_method_t method; - char *sni; - unsigned state_set:1; - unsigned _spare:31; + SSL *ssl_handle; + SSL_CTX *ctx; + struct timeval connect_timeout; + int enable_on_connect; + int is_client; + int ssl_active; + php_stream_xport_crypt_method_t method; + char *sni; + unsigned state_set:1; + unsigned _spare:31; } php_openssl_netstream_data_t; php_stream_ops php_openssl_socket_ops; @@ -66,1031 +66,1028 @@ php_stream_ops php_openssl_socket_ops; * in an error condition arising from a network connection problem */ static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC) { - if (stream->wrapperdata && stream->wrapper && strcasecmp(stream->wrapper->wops->label, "HTTP") == 0) { - /* the wrapperdata is an array zval containing the headers */ - zval **tmp; + if (stream->wrapperdata && stream->wrapper && strcasecmp(stream->wrapper->wops->label, "HTTP") == 0) { + /* the wrapperdata is an array zval containing the headers */ + zval **tmp; -#define SERVER_MICROSOFT_IIS "Server: Microsoft-IIS" +#define SERVER_MICROSOFT_IIS "Server: Microsoft-IIS" #define SERVER_GOOGLE "Server: GFE/" - - zend_hash_internal_pointer_reset(Z_ARRVAL_P(stream->wrapperdata)); - while (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(stream->wrapperdata), (void**)&tmp)) { - - if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_MICROSOFT_IIS, sizeof(SERVER_MICROSOFT_IIS)-1) == 0) { - return 1; - } else if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_GOOGLE, sizeof(SERVER_GOOGLE)-1) == 0) { - return 1; - } - - zend_hash_move_forward(Z_ARRVAL_P(stream->wrapperdata)); - } - } - return 0; + + zend_hash_internal_pointer_reset(Z_ARRVAL_P(stream->wrapperdata)); + while (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(stream->wrapperdata), (void**)&tmp)) { + + if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_MICROSOFT_IIS, sizeof(SERVER_MICROSOFT_IIS)-1) == 0) { + return 1; + } else if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_GOOGLE, sizeof(SERVER_GOOGLE)-1) == 0) { + return 1; + } + + zend_hash_move_forward(Z_ARRVAL_P(stream->wrapperdata)); + } + } + return 0; } static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int err = SSL_get_error(sslsock->ssl_handle, nr_bytes); - char esbuf[512]; - smart_str ebuf = {0}; - unsigned long ecode; - int retry = 1; - - switch(err) { - case SSL_ERROR_ZERO_RETURN: - /* SSL terminated (but socket may still be active) */ - retry = 0; - break; - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* re-negotiation, or perhaps the SSL layer needs more - * packets: retry in next iteration */ - errno = EAGAIN; - retry = is_init ? 1 : sslsock->s.is_blocked; - break; - case SSL_ERROR_SYSCALL: - if (ERR_peek_error() == 0) { - if (nr_bytes == 0) { - if (!is_http_stream_talking_to_iis(stream TSRMLS_CC) && ERR_get_error() != 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL: fatal protocol error"); - } - SSL_set_shutdown(sslsock->ssl_handle, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); - stream->eof = 1; - retry = 0; - } else { - char *estr = php_socket_strerror(php_socket_errno(), NULL, 0); - - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL: %s", estr); - - efree(estr); - retry = 0; - } - break; - } - - - /* fall through */ - default: - /* some other error */ - ecode = ERR_get_error(); - - switch (ERR_GET_REASON(ecode)) { - case SSL_R_NO_SHARED_CIPHER: - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL_R_NO_SHARED_CIPHER: no suitable shared cipher could be used. This could be because the server is missing an SSL certificate (local_cert context option)"); - retry = 0; - break; - - default: - do { - /* NULL is automatically added */ - ERR_error_string_n(ecode, esbuf, sizeof(esbuf)); - if (ebuf.c) { - smart_str_appendc(&ebuf, '\n'); - } - smart_str_appends(&ebuf, esbuf); - } while ((ecode = ERR_get_error()) != 0); - - smart_str_0(&ebuf); - - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL operation failed with code %d. %s%s", - err, - ebuf.c ? "OpenSSL Error messages:\n" : "", - ebuf.c ? ebuf.c : ""); - if (ebuf.c) { - smart_str_free(&ebuf); - } - } - - retry = 0; - errno = 0; - } - return retry; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int err = SSL_get_error(sslsock->ssl_handle, nr_bytes); + char esbuf[512]; + smart_str ebuf = {0}; + unsigned long ecode; + int retry = 1; + + switch(err) { + case SSL_ERROR_ZERO_RETURN: + /* SSL terminated (but socket may still be active) */ + retry = 0; + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* re-negotiation, or perhaps the SSL layer needs more + * packets: retry in next iteration */ + errno = EAGAIN; + retry = is_init ? 1 : sslsock->s.is_blocked; + break; + case SSL_ERROR_SYSCALL: + if (ERR_peek_error() == 0) { + if (nr_bytes == 0) { + if (!is_http_stream_talking_to_iis(stream TSRMLS_CC) && ERR_get_error() != 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL: fatal protocol error"); + } + SSL_set_shutdown(sslsock->ssl_handle, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); + stream->eof = 1; + retry = 0; + } else { + char *estr = php_socket_strerror(php_socket_errno(), NULL, 0); + + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL: %s", estr); + + efree(estr); + retry = 0; + } + break; + } + + + /* fall through */ + default: + /* some other error */ + ecode = ERR_get_error(); + + switch (ERR_GET_REASON(ecode)) { + case SSL_R_NO_SHARED_CIPHER: + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL_R_NO_SHARED_CIPHER: no suitable shared cipher could be used. This could be because the server is missing an SSL certificate (local_cert context option)"); + retry = 0; + break; + + default: + do { + /* NULL is automatically added */ + ERR_error_string_n(ecode, esbuf, sizeof(esbuf)); + if (ebuf.c) { + smart_str_appendc(&ebuf, '\n'); + } + smart_str_appends(&ebuf, esbuf); + } while ((ecode = ERR_get_error()) != 0); + + smart_str_0(&ebuf); + + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL operation failed with code %d. %s%s", + err, + ebuf.c ? "OpenSSL Error messages:\n" : "", + ebuf.c ? ebuf.c : ""); + if (ebuf.c) { + smart_str_free(&ebuf); + } + } + + retry = 0; + errno = 0; + } + return retry; } static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int didwrite; - - if (sslsock->ssl_active) { - int retry = 1; - struct timeval start_time, - *timeout; - int blocked = sslsock->s.is_blocked, - has_timeout = 0; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int didwrite; + + if (sslsock->ssl_active) { + int retry = 1; + struct timeval start_time, + *timeout; + int blocked = sslsock->s.is_blocked, + has_timeout = 0; - if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { - sslsock->s.is_blocked = 0; - } + if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { + sslsock->s.is_blocked = 0; + } - timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; - has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); + timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; + has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); - /* gettimeofday is not monotonic; using it here is not strictly correct */ - if (has_timeout) { - gettimeofday(&start_time, NULL); - } - - do { - struct timeval cur_time, - elapsed_time; + /* gettimeofday is not monotonic; using it here is not strictly correct */ + if (has_timeout) { + gettimeofday(&start_time, NULL); + } + + do { + struct timeval cur_time, + elapsed_time; - if (has_timeout) { - gettimeofday(&cur_time, NULL); - elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; - elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; - if (cur_time.tv_usec < start_time.tv_usec) { - elapsed_time.tv_sec -= 1L; - elapsed_time.tv_usec += 1000000L; - } + if (has_timeout) { + gettimeofday(&cur_time, NULL); + elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; + elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; + if (cur_time.tv_usec < start_time.tv_usec) { + elapsed_time.tv_sec -= 1L; + elapsed_time.tv_usec += 1000000L; + } - if (elapsed_time.tv_sec > timeout->tv_sec || - (elapsed_time.tv_sec == timeout->tv_sec && - elapsed_time.tv_usec > timeout->tv_usec)) { - if( blocked ) { - php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); - sslsock->s.is_blocked = 1; - } + if (elapsed_time.tv_sec > timeout->tv_sec || + (elapsed_time.tv_sec == timeout->tv_sec && + elapsed_time.tv_usec > timeout->tv_usec)) { + if( blocked ) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; + } - return -1; - } - } + return -1; + } + } - didwrite = SSL_write(sslsock->ssl_handle, buf, count); + didwrite = SSL_write(sslsock->ssl_handle, buf, count); - struct timeval left_time; + struct timeval left_time; - if (has_timeout) { - left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; - left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; - if (timeout->tv_usec < elapsed_time.tv_usec) { - left_time.tv_sec -= 1L; - left_time.tv_usec += 1000000L; - } - } + if (has_timeout) { + left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; + left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; + if (timeout->tv_usec < elapsed_time.tv_usec) { + left_time.tv_sec -= 1L; + left_time.tv_usec += 1000000L; + } + } - if (didwrite <= 0) { - int err = SSL_get_error(sslsock->ssl_handle, didwrite ); - retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC); + if (didwrite <= 0) { + int err = SSL_get_error(sslsock->ssl_handle, didwrite ); + retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC); - if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; + if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; - if( retry && blocked ) { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? - (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); - } - } else { - int err = SSL_get_error(sslsock->ssl_handle, didwrite ); + if( retry && blocked ) { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); + } + } else { + int err = SSL_get_error(sslsock->ssl_handle, didwrite ); - if( err == SSL_ERROR_NONE ) - break; - if( blocked ) { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? - (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); - } - } - } while(retry); - - if( blocked ) { - php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); - sslsock->s.is_blocked = 1; - } - - if (didwrite > 0) { - php_stream_notify_progress_increment(stream->context, didwrite, 0); - } - } else { - didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); - } - - if (didwrite < 0) { - didwrite = 0; - } - - return didwrite; + if( err == SSL_ERROR_NONE ) + break; + if( blocked ) { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); + } + } + } while(retry); + + if( blocked ) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; + } + + if (didwrite > 0) { + php_stream_notify_progress_increment(stream->context, didwrite, 0); + } + } else { + didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); + } + + if (didwrite < 0) { + didwrite = 0; + } + + return didwrite; } static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) -{ - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int nr_bytes = 0; - - if (sslsock->ssl_active) { - int retry = 1; - struct timeval start_time, - *timeout; - int blocked = sslsock->s.is_blocked, - has_timeout = 0; - - if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { - sslsock->s.is_blocked = 0; - } +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int nr_bytes = 0; + + if (sslsock->ssl_active) { + int retry = 1; + struct timeval start_time, + *timeout; + int blocked = sslsock->s.is_blocked, + has_timeout = 0; - timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; - has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); + if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { + sslsock->s.is_blocked = 0; + } - /* gettimeofday is not monotonic; using it here is not strictly correct */ - if (has_timeout) { - gettimeofday(&start_time, NULL); - } + timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; + has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); - do { - struct timeval cur_time, - elapsed_time; + /* gettimeofday is not monotonic; using it here is not strictly correct */ + if (has_timeout) { + gettimeofday(&start_time, NULL); + } + + do { + struct timeval cur_time, + elapsed_time; - if (has_timeout) { - gettimeofday(&cur_time, NULL); - elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; - elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; - if (cur_time.tv_usec < start_time.tv_usec) { - elapsed_time.tv_sec -= 1L; - elapsed_time.tv_usec += 1000000L; - } + if (has_timeout) { + gettimeofday(&cur_time, NULL); + elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; + elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; + if (cur_time.tv_usec < start_time.tv_usec) { + elapsed_time.tv_sec -= 1L; + elapsed_time.tv_usec += 1000000L; + } - if (elapsed_time.tv_sec > timeout->tv_sec || - (elapsed_time.tv_sec == timeout->tv_sec && - elapsed_time.tv_usec > timeout->tv_usec)) { - - if( blocked ) { - php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); - sslsock->s.is_blocked = 1; - } + if (elapsed_time.tv_sec > timeout->tv_sec || + (elapsed_time.tv_sec == timeout->tv_sec && + elapsed_time.tv_usec > timeout->tv_usec)) { + if( blocked ) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; + } - return -1; - } - } - - nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); + return -1; + } + } - struct timeval left_time; + nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); + + struct timeval left_time; - if (has_timeout) { - left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; - left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; - if (timeout->tv_usec < elapsed_time.tv_usec) { - left_time.tv_sec -= 1L; - left_time.tv_usec += 1000000L; - } - } + if (has_timeout) { + left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; + left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; + if (timeout->tv_usec < elapsed_time.tv_usec) { + left_time.tv_sec -= 1L; + left_time.tv_usec += 1000000L; + } + } - if (nr_bytes <= 0) { + if (nr_bytes <= 0) { - int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); + int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); - retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC); - - if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; + retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC); - stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); + if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; - if( retry && blocked ) { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? - (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); - } + stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); + + if( retry && blocked ) { + php_pollfd_for( sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); + } - } else { - /* we got the data */ - int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); - - if( err == SSL_ERROR_NONE ) - break; - if( blocked ) { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? - (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); - } - } + } else { + /* we got the data */ + int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); + + if( err == SSL_ERROR_NONE ) + break; + if( blocked ) { + php_pollfd_for( sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); + } + } + } while (retry); + + if (nr_bytes > 0) { + php_stream_notify_progress_increment(stream->context, nr_bytes, 0); + } - } while (retry); - - if (nr_bytes > 0) { - php_stream_notify_progress_increment(stream->context, nr_bytes, 0); - } - - if( blocked ) { - php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); - sslsock->s.is_blocked = 1; - } - } else { - nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); - } - - if (nr_bytes < 0) { - nr_bytes = 0; - } - - return nr_bytes; + if( blocked ) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; + } + } else { + nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); + } + + if (nr_bytes < 0) { + nr_bytes = 0; + } + + return nr_bytes; } static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; #ifdef PHP_WIN32 - int n; + int n; #endif - if (close_handle) { - if (sslsock->ssl_active) { - SSL_shutdown(sslsock->ssl_handle); - sslsock->ssl_active = 0; - } - if (sslsock->ssl_handle) { - SSL_free(sslsock->ssl_handle); - sslsock->ssl_handle = NULL; - } - if (sslsock->ctx) { - SSL_CTX_free(sslsock->ctx); - sslsock->ctx = NULL; - } + if (close_handle) { + if (sslsock->ssl_active) { + SSL_shutdown(sslsock->ssl_handle); + sslsock->ssl_active = 0; + } + if (sslsock->ssl_handle) { + SSL_free(sslsock->ssl_handle); + sslsock->ssl_handle = NULL; + } + if (sslsock->ctx) { + SSL_CTX_free(sslsock->ctx); + sslsock->ctx = NULL; + } #ifdef PHP_WIN32 - if (sslsock->s.socket == -1) - sslsock->s.socket = SOCK_ERR; + if (sslsock->s.socket == -1) + sslsock->s.socket = SOCK_ERR; #endif - if (sslsock->s.socket != SOCK_ERR) { + if (sslsock->s.socket != SOCK_ERR) { #ifdef PHP_WIN32 - /* prevent more data from coming in */ - shutdown(sslsock->s.socket, SHUT_RD); - - /* try to make sure that the OS sends all data before we close the connection. - * Essentially, we are waiting for the socket to become writeable, which means - * that all pending data has been sent. - * We use a small timeout which should encourage the OS to send the data, - * but at the same time avoid hanging indefintely. - * */ - do { - n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500); - } while (n == -1 && php_socket_errno() == EINTR); + /* prevent more data from coming in */ + shutdown(sslsock->s.socket, SHUT_RD); + + /* try to make sure that the OS sends all data before we close the connection. + * Essentially, we are waiting for the socket to become writeable, which means + * that all pending data has been sent. + * We use a small timeout which should encourage the OS to send the data, + * but at the same time avoid hanging indefintely. + * */ + do { + n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500); + } while (n == -1 && php_socket_errno() == EINTR); #endif - closesocket(sslsock->s.socket); - sslsock->s.socket = SOCK_ERR; - } - } - - if (sslsock->sni) { - pefree(sslsock->sni, php_stream_is_persistent(stream)); - } - pefree(sslsock, php_stream_is_persistent(stream)); - - return 0; + closesocket(sslsock->s.socket); + sslsock->s.socket = SOCK_ERR; + } + } + + if (sslsock->sni) { + pefree(sslsock->sni, php_stream_is_persistent(stream)); + } + pefree(sslsock, php_stream_is_persistent(stream)); + + return 0; } static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC) { - return php_stream_socket_ops.flush(stream TSRMLS_CC); + return php_stream_socket_ops.flush(stream TSRMLS_CC); } static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) { - return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC); + return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC); } static inline int php_openssl_setup_crypto(php_stream *stream, - php_openssl_netstream_data_t *sslsock, - php_stream_xport_crypto_param *cparam - TSRMLS_DC) + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + TSRMLS_DC) { - SSL_METHOD *method; - long ssl_ctx_options = SSL_OP_ALL; - - if (sslsock->ssl_handle) { - if (sslsock->s.is_blocked) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream"); - return -1; - } else { - return 0; - } - } - - /* need to do slightly different things, based on client/server method, - * so lets remember which method was selected */ - - switch (cparam->inputs.method) { - case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: - sslsock->is_client = 1; - method = SSLv23_client_method(); - break; - case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: + SSL_METHOD *method; + long ssl_ctx_options = SSL_OP_ALL; + + if (sslsock->ssl_handle) { + if (sslsock->s.is_blocked) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream"); + return -1; + } else { + return 0; + } + } + + /* need to do slightly different things, based on client/server method, + * so lets remember which method was selected */ + + switch (cparam->inputs.method) { + case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: + sslsock->is_client = 1; + method = SSLv23_client_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: #ifdef OPENSSL_NO_SSL2 - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); - return -1; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); + return -1; #else - sslsock->is_client = 1; - method = SSLv2_client_method(); - break; + sslsock->is_client = 1; + method = SSLv2_client_method(); + break; #endif - case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: - sslsock->is_client = 1; - method = SSLv3_client_method(); - break; - case STREAM_CRYPTO_METHOD_TLS_CLIENT: - sslsock->is_client = 1; - method = TLSv1_client_method(); - break; - case STREAM_CRYPTO_METHOD_SSLv23_SERVER: - sslsock->is_client = 0; - method = SSLv23_server_method(); - break; - case STREAM_CRYPTO_METHOD_SSLv3_SERVER: - sslsock->is_client = 0; - method = SSLv3_server_method(); - break; - case STREAM_CRYPTO_METHOD_SSLv2_SERVER: + case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: + sslsock->is_client = 1; + method = SSLv3_client_method(); + break; + case STREAM_CRYPTO_METHOD_TLS_CLIENT: + sslsock->is_client = 1; + method = TLSv1_client_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv23_SERVER: + sslsock->is_client = 0; + method = SSLv23_server_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv3_SERVER: + sslsock->is_client = 0; + method = SSLv3_server_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv2_SERVER: #ifdef OPENSSL_NO_SSL2 - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); - return -1; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); + return -1; #else - sslsock->is_client = 0; - method = SSLv2_server_method(); - break; + sslsock->is_client = 0; + method = SSLv2_server_method(); + break; #endif - case STREAM_CRYPTO_METHOD_TLS_SERVER: - sslsock->is_client = 0; - method = TLSv1_server_method(); - break; - default: - return -1; + case STREAM_CRYPTO_METHOD_TLS_SERVER: + sslsock->is_client = 0; + method = TLSv1_server_method(); + break; + default: + return -1; - } + } - sslsock->ctx = SSL_CTX_new(method); - if (sslsock->ctx == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context"); - return -1; - } + sslsock->ctx = SSL_CTX_new(method); + if (sslsock->ctx == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context"); + return -1; + } #if OPENSSL_VERSION_NUMBER >= 0x0090605fL - ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; #endif - SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options); + SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options); #if OPENSSL_VERSION_NUMBER >= 0x0090806fL - { - zval **val; - - if (stream->context && SUCCESS == php_stream_context_get_option( - stream->context, "ssl", "no_ticket", &val) && - zval_is_true(*val)) { - SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_TICKET); - } - } + { + zval **val; + + if (stream->context && SUCCESS == php_stream_context_get_option( + stream->context, "ssl", "no_ticket", &val) && + zval_is_true(*val)) { + SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_TICKET); + } + } #endif - sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC); - if (sslsock->ssl_handle == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle"); - SSL_CTX_free(sslsock->ctx); - sslsock->ctx = NULL; - return -1; - } - - if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { - handle_ssl_error(stream, 0, 1 TSRMLS_CC); - } - - if (cparam->inputs.session) { - if (cparam->inputs.session->ops != &php_openssl_socket_ops) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream"); - } else if (((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied SSL session stream is not initialized"); - } else { - SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle); - } - } - return 0; + sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC); + if (sslsock->ssl_handle == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle"); + SSL_CTX_free(sslsock->ctx); + sslsock->ctx = NULL; + return -1; + } + + if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { + handle_ssl_error(stream, 0, 1 TSRMLS_CC); + } + + if (cparam->inputs.session) { + if (cparam->inputs.session->ops != &php_openssl_socket_ops) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream"); + } else if (((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied SSL session stream is not initialized"); + } else { + SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle); + } + } + return 0; } static inline int php_openssl_enable_crypto(php_stream *stream, - php_openssl_netstream_data_t *sslsock, - php_stream_xport_crypto_param *cparam - TSRMLS_DC) + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + TSRMLS_DC) { - int n, retry = 1; + int n, retry = 1; - if (cparam->inputs.activate && !sslsock->ssl_active) { - struct timeval start_time, - *timeout; - int blocked = sslsock->s.is_blocked, - has_timeout = 0; + if (cparam->inputs.activate && !sslsock->ssl_active) { + struct timeval start_time, + *timeout; + int blocked = sslsock->s.is_blocked, + has_timeout = 0; #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) - if (sslsock->is_client && sslsock->sni) { - SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->sni); - } + if (sslsock->is_client && sslsock->sni) { + SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->sni); + } #endif - if (!sslsock->state_set) { - if (sslsock->is_client) { - SSL_set_connect_state(sslsock->ssl_handle); - } else { - SSL_set_accept_state(sslsock->ssl_handle); - } - sslsock->state_set = 1; - } - - if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { - sslsock->s.is_blocked = 0; - } - - timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; - has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); - /* gettimeofday is not monotonic; using it here is not strictly correct */ - if (has_timeout) { - gettimeofday(&start_time, NULL); - } - - do { - struct timeval cur_time, - elapsed_time; - - if (sslsock->is_client) { - n = SSL_connect(sslsock->ssl_handle); - } else { - n = SSL_accept(sslsock->ssl_handle); - } - - if (has_timeout) { - gettimeofday(&cur_time, NULL); - elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; - elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; - if (cur_time.tv_usec < start_time.tv_usec) { - elapsed_time.tv_sec -= 1L; - elapsed_time.tv_usec += 1000000L; - } - - if (elapsed_time.tv_sec > timeout->tv_sec || - (elapsed_time.tv_sec == timeout->tv_sec && - elapsed_time.tv_usec > timeout->tv_usec)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout"); - return -1; - } - } - - if (n <= 0) { - /* in case of SSL_ERROR_WANT_READ/WRITE, do not retry in non-blocking mode */ - retry = handle_ssl_error(stream, n, blocked TSRMLS_CC); - if (retry) { - /* wait until something interesting happens in the socket. It may be a - * timeout. Also consider the unlikely of possibility of a write block */ - int err = SSL_get_error(sslsock->ssl_handle, n); - struct timeval left_time; - - if (has_timeout) { - left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; - left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; - if (timeout->tv_usec < elapsed_time.tv_usec) { - left_time.tv_sec -= 1L; - left_time.tv_usec += 1000000L; - } - } - - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? - (POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL); - } - } else { - retry = 0; - } - } while (retry); - - if (sslsock->s.is_blocked != blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) { - sslsock->s.is_blocked = blocked; - } - - if (n == 1) { - X509 *peer_cert; - - peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle); - - if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) { - SSL_shutdown(sslsock->ssl_handle); - n = -1; - } else { - sslsock->ssl_active = 1; - - /* allow the script to capture the peer cert - * and/or the certificate chain */ - if (stream->context) { - zval **val, *zcert; - - if (SUCCESS == php_stream_context_get_option( - stream->context, "ssl", - "capture_peer_cert", &val) && - zval_is_true(*val)) { - MAKE_STD_ZVAL(zcert); - ZVAL_RESOURCE(zcert, zend_list_insert(peer_cert, - php_openssl_get_x509_list_id() TSRMLS_CC)); - php_stream_context_set_option(stream->context, - "ssl", "peer_certificate", - zcert); - peer_cert = NULL; - FREE_ZVAL(zcert); - } - - if (SUCCESS == php_stream_context_get_option( - stream->context, "ssl", - "capture_peer_cert_chain", &val) && - zval_is_true(*val)) { - zval *arr; - STACK_OF(X509) *chain; - - MAKE_STD_ZVAL(arr); - chain = SSL_get_peer_cert_chain( - sslsock->ssl_handle); - - if (chain && sk_X509_num(chain) > 0) { - int i; - array_init(arr); - - for (i = 0; i < sk_X509_num(chain); i++) { - X509 *mycert = X509_dup( - sk_X509_value(chain, i)); - MAKE_STD_ZVAL(zcert); - ZVAL_RESOURCE(zcert, - zend_list_insert(mycert, - php_openssl_get_x509_list_id() TSRMLS_CC)); - add_next_index_zval(arr, zcert); - } - - } else { - ZVAL_NULL(arr); - } - - php_stream_context_set_option(stream->context, - "ssl", "peer_certificate_chain", - arr); - zval_dtor(arr); - efree(arr); - } - } - } - - if (peer_cert) { - X509_free(peer_cert); - } - } else { - n = errno == EAGAIN ? 0 : -1; - } - - return n; - - } else if (!cparam->inputs.activate && sslsock->ssl_active) { - /* deactivate - common for server/client */ - SSL_shutdown(sslsock->ssl_handle); - sslsock->ssl_active = 0; - } - return -1; + if (!sslsock->state_set) { + if (sslsock->is_client) { + SSL_set_connect_state(sslsock->ssl_handle); + } else { + SSL_set_accept_state(sslsock->ssl_handle); + } + sslsock->state_set = 1; + } + + if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { + sslsock->s.is_blocked = 0; + } + + timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; + has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); + /* gettimeofday is not monotonic; using it here is not strictly correct */ + if (has_timeout) { + gettimeofday(&start_time, NULL); + } + + do { + struct timeval cur_time, + elapsed_time; + + if (sslsock->is_client) { + n = SSL_connect(sslsock->ssl_handle); + } else { + n = SSL_accept(sslsock->ssl_handle); + } + + if (has_timeout) { + gettimeofday(&cur_time, NULL); + elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; + elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; + if (cur_time.tv_usec < start_time.tv_usec) { + elapsed_time.tv_sec -= 1L; + elapsed_time.tv_usec += 1000000L; + } + + if (elapsed_time.tv_sec > timeout->tv_sec || + (elapsed_time.tv_sec == timeout->tv_sec && + elapsed_time.tv_usec > timeout->tv_usec)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout"); + return -1; + } + } + + if (n <= 0) { + /* in case of SSL_ERROR_WANT_READ/WRITE, do not retry in non-blocking mode */ + retry = handle_ssl_error(stream, n, blocked TSRMLS_CC); + if (retry) { + /* wait until something interesting happens in the socket. It may be a + * timeout. Also consider the unlikely of possibility of a write block */ + int err = SSL_get_error(sslsock->ssl_handle, n); + struct timeval left_time; + + if (has_timeout) { + left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; + left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; + if (timeout->tv_usec < elapsed_time.tv_usec) { + left_time.tv_sec -= 1L; + left_time.tv_usec += 1000000L; + } + } + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL); + } + } else { + retry = 0; + } + } while (retry); + + if (sslsock->s.is_blocked != blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) { + sslsock->s.is_blocked = blocked; + } + + if (n == 1) { + X509 *peer_cert; + + peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle); + + if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) { + SSL_shutdown(sslsock->ssl_handle); + n = -1; + } else { + sslsock->ssl_active = 1; + + /* allow the script to capture the peer cert + * and/or the certificate chain */ + if (stream->context) { + zval **val, *zcert; + + if (SUCCESS == php_stream_context_get_option( + stream->context, "ssl", + "capture_peer_cert", &val) && + zval_is_true(*val)) { + MAKE_STD_ZVAL(zcert); + ZVAL_RESOURCE(zcert, zend_list_insert(peer_cert, + php_openssl_get_x509_list_id() TSRMLS_CC)); + php_stream_context_set_option(stream->context, + "ssl", "peer_certificate", + zcert); + peer_cert = NULL; + FREE_ZVAL(zcert); + } + + if (SUCCESS == php_stream_context_get_option( + stream->context, "ssl", + "capture_peer_cert_chain", &val) && + zval_is_true(*val)) { + zval *arr; + STACK_OF(X509) *chain; + + MAKE_STD_ZVAL(arr); + chain = SSL_get_peer_cert_chain( + sslsock->ssl_handle); + + if (chain && sk_X509_num(chain) > 0) { + int i; + array_init(arr); + + for (i = 0; i < sk_X509_num(chain); i++) { + X509 *mycert = X509_dup( + sk_X509_value(chain, i)); + MAKE_STD_ZVAL(zcert); + ZVAL_RESOURCE(zcert, + zend_list_insert(mycert, + php_openssl_get_x509_list_id() TSRMLS_CC)); + add_next_index_zval(arr, zcert); + } + + } else { + ZVAL_NULL(arr); + } + + php_stream_context_set_option(stream->context, + "ssl", "peer_certificate_chain", + arr); + zval_dtor(arr); + efree(arr); + } + } + } + + if (peer_cert) { + X509_free(peer_cert); + } + } else { + n = errno == EAGAIN ? 0 : -1; + } + + return n; + + } else if (!cparam->inputs.activate && sslsock->ssl_active) { + /* deactivate - common for server/client */ + SSL_shutdown(sslsock->ssl_handle); + sslsock->ssl_active = 0; + } + return -1; } static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_netstream_data_t *sock, - php_stream_xport_param *xparam STREAMS_DC TSRMLS_DC) + php_stream_xport_param *xparam STREAMS_DC TSRMLS_DC) { - int clisock; - - xparam->outputs.client = NULL; - - clisock = php_network_accept_incoming(sock->s.socket, - xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, - xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL, - xparam->want_addr ? &xparam->outputs.addr : NULL, - xparam->want_addr ? &xparam->outputs.addrlen : NULL, - xparam->inputs.timeout, - xparam->want_errortext ? &xparam->outputs.error_text : NULL, - &xparam->outputs.error_code - TSRMLS_CC); - - if (clisock >= 0) { - php_openssl_netstream_data_t *clisockdata; - - clisockdata = emalloc(sizeof(*clisockdata)); - - if (clisockdata == NULL) { - closesocket(clisock); - /* technically a fatal error */ - } else { - /* copy underlying tcp fields */ - memset(clisockdata, 0, sizeof(*clisockdata)); - memcpy(clisockdata, sock, sizeof(clisockdata->s)); - - clisockdata->s.socket = clisock; - - xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); - if (xparam->outputs.client) { - xparam->outputs.client->context = stream->context; - if (stream->context) { - zend_list_addref(stream->context->rsrc_id); - } - } - } - - if (xparam->outputs.client && sock->enable_on_connect) { - /* apply crypto */ - switch (sock->method) { - case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_SSLv23_SERVER; - break; - case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_SSLv2_SERVER; - break; - case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_SSLv3_SERVER; - break; - case STREAM_CRYPTO_METHOD_TLS_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_TLS_SERVER; - break; - default: - break; - } - - clisockdata->method = sock->method; - - if (php_stream_xport_crypto_setup(xparam->outputs.client, clisockdata->method, - NULL TSRMLS_CC) < 0 || php_stream_xport_crypto_enable( - xparam->outputs.client, 1 TSRMLS_CC) < 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); - - php_stream_close(xparam->outputs.client); - xparam->outputs.client = NULL; - xparam->outputs.returncode = -1; - } - } - } - - return xparam->outputs.client == NULL ? -1 : 0; + int clisock; + + xparam->outputs.client = NULL; + + clisock = php_network_accept_incoming(sock->s.socket, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL, + xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &xparam->outputs.error_code + TSRMLS_CC); + + if (clisock >= 0) { + php_openssl_netstream_data_t *clisockdata; + + clisockdata = emalloc(sizeof(*clisockdata)); + + if (clisockdata == NULL) { + closesocket(clisock); + /* technically a fatal error */ + } else { + /* copy underlying tcp fields */ + memset(clisockdata, 0, sizeof(*clisockdata)); + memcpy(clisockdata, sock, sizeof(clisockdata->s)); + + clisockdata->s.socket = clisock; + + xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); + if (xparam->outputs.client) { + xparam->outputs.client->context = stream->context; + if (stream->context) { + zend_list_addref(stream->context->rsrc_id); + } + } + } + + if (xparam->outputs.client && sock->enable_on_connect) { + /* apply crypto */ + switch (sock->method) { + case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: + sock->method = STREAM_CRYPTO_METHOD_SSLv23_SERVER; + break; + case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: + sock->method = STREAM_CRYPTO_METHOD_SSLv2_SERVER; + break; + case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: + sock->method = STREAM_CRYPTO_METHOD_SSLv3_SERVER; + break; + case STREAM_CRYPTO_METHOD_TLS_CLIENT: + sock->method = STREAM_CRYPTO_METHOD_TLS_SERVER; + break; + default: + break; + } + + clisockdata->method = sock->method; + + if (php_stream_xport_crypto_setup(xparam->outputs.client, clisockdata->method, + NULL TSRMLS_CC) < 0 || php_stream_xport_crypto_enable( + xparam->outputs.client, 1 TSRMLS_CC) < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); + + php_stream_close(xparam->outputs.client); + xparam->outputs.client = NULL; + xparam->outputs.returncode = -1; + } + } + } + + return xparam->outputs.client == NULL ? -1 : 0; } static int php_openssl_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - php_stream_xport_crypto_param *cparam = (php_stream_xport_crypto_param *)ptrparam; - php_stream_xport_param *xparam = (php_stream_xport_param *)ptrparam; - - switch (option) { - case PHP_STREAM_OPTION_CHECK_LIVENESS: - { - struct timeval tv; - char buf; - int alive = 1; - - if (value == -1) { - if (sslsock->s.timeout.tv_sec == -1) { - tv.tv_sec = FG(default_socket_timeout); - tv.tv_usec = 0; - } else { - tv = sslsock->connect_timeout; - } - } else { - tv.tv_sec = value; - tv.tv_usec = 0; - } - - if (sslsock->s.socket == -1) { - alive = 0; - } else if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) { - if (sslsock->ssl_active) { - int n; - - do { - n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf)); - if (n <= 0) { - int err = SSL_get_error(sslsock->ssl_handle, n); - - if (err == SSL_ERROR_SYSCALL) { - alive = php_socket_errno() == EAGAIN; - break; - } - - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { - /* re-negotiate */ - continue; - } - - /* any other problem is a fatal error */ - alive = 0; - } - /* either peek succeeded or there was an error; we - * have set the alive flag appropriately */ - break; - } while (1); - } else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) { - alive = 0; - } - } - return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; - } - - case PHP_STREAM_OPTION_CRYPTO_API: - - switch(cparam->op) { - - case STREAM_XPORT_CRYPTO_OP_SETUP: - cparam->outputs.returncode = php_openssl_setup_crypto(stream, sslsock, cparam TSRMLS_CC); - return PHP_STREAM_OPTION_RETURN_OK; - break; - case STREAM_XPORT_CRYPTO_OP_ENABLE: - cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam TSRMLS_CC); - return PHP_STREAM_OPTION_RETURN_OK; - break; - default: - /* fall through */ - break; - } - - break; - - case PHP_STREAM_OPTION_XPORT_API: - switch(xparam->op) { - - case STREAM_XPORT_OP_CONNECT: - case STREAM_XPORT_OP_CONNECT_ASYNC: - /* TODO: Async connects need to check the enable_on_connect option when - * we notice that the connect has actually been established */ - php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); - - if ((sslsock->enable_on_connect) && - ((xparam->outputs.returncode == 0) || - (xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && - xparam->outputs.returncode == 1 && xparam->outputs.error_code == EINPROGRESS))) - { - if (php_stream_xport_crypto_setup(stream, sslsock->method, NULL TSRMLS_CC) < 0 || - php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); - xparam->outputs.returncode = -1; - } - } - return PHP_STREAM_OPTION_RETURN_OK; - - case STREAM_XPORT_OP_ACCEPT: - /* we need to copy the additional fields that the underlying tcp transport - * doesn't know about */ - xparam->outputs.returncode = php_openssl_tcp_sockop_accept(stream, sslsock, xparam STREAMS_CC TSRMLS_CC); - - - return PHP_STREAM_OPTION_RETURN_OK; - - default: - /* fall through */ - break; - } - } - - return php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + php_stream_xport_crypto_param *cparam = (php_stream_xport_crypto_param *)ptrparam; + php_stream_xport_param *xparam = (php_stream_xport_param *)ptrparam; + + switch (option) { + case PHP_STREAM_OPTION_CHECK_LIVENESS: + { + struct timeval tv; + char buf; + int alive = 1; + + if (value == -1) { + if (sslsock->s.timeout.tv_sec == -1) { + tv.tv_sec = FG(default_socket_timeout); + tv.tv_usec = 0; + } else { + tv = sslsock->connect_timeout; + } + } else { + tv.tv_sec = value; + tv.tv_usec = 0; + } + + if (sslsock->s.socket == -1) { + alive = 0; + } else if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) { + if (sslsock->ssl_active) { + int n; + + do { + n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf)); + if (n <= 0) { + int err = SSL_get_error(sslsock->ssl_handle, n); + + if (err == SSL_ERROR_SYSCALL) { + alive = php_socket_errno() == EAGAIN; + break; + } + + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + /* re-negotiate */ + continue; + } + + /* any other problem is a fatal error */ + alive = 0; + } + /* either peek succeeded or there was an error; we + * have set the alive flag appropriately */ + break; + } while (1); + } else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) { + alive = 0; + } + } + return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } + + case PHP_STREAM_OPTION_CRYPTO_API: + + switch(cparam->op) { + + case STREAM_XPORT_CRYPTO_OP_SETUP: + cparam->outputs.returncode = php_openssl_setup_crypto(stream, sslsock, cparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + break; + case STREAM_XPORT_CRYPTO_OP_ENABLE: + cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + break; + default: + /* fall through */ + break; + } + + break; + + case PHP_STREAM_OPTION_XPORT_API: + switch(xparam->op) { + + case STREAM_XPORT_OP_CONNECT: + case STREAM_XPORT_OP_CONNECT_ASYNC: + /* TODO: Async connects need to check the enable_on_connect option when + * we notice that the connect has actually been established */ + php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); + + if ((sslsock->enable_on_connect) && + ((xparam->outputs.returncode == 0) || + (xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && + xparam->outputs.returncode == 1 && xparam->outputs.error_code == EINPROGRESS))) + { + if (php_stream_xport_crypto_setup(stream, sslsock->method, NULL TSRMLS_CC) < 0 || + php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); + xparam->outputs.returncode = -1; + } + } + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_ACCEPT: + /* we need to copy the additional fields that the underlying tcp transport + * doesn't know about */ + xparam->outputs.returncode = php_openssl_tcp_sockop_accept(stream, sslsock, xparam STREAMS_CC TSRMLS_CC); + + + return PHP_STREAM_OPTION_RETURN_OK; + + default: + /* fall through */ + break; + } + } + + return php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); } static int php_openssl_sockop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - - switch(castas) { - case PHP_STREAM_AS_STDIO: - if (sslsock->ssl_active) { - return FAILURE; - } - if (ret) { - *ret = fdopen(sslsock->s.socket, stream->mode); - if (*ret) { - return SUCCESS; - } - return FAILURE; - } - return SUCCESS; - - case PHP_STREAM_AS_FD_FOR_SELECT: - if (ret) { - *(int *)ret = sslsock->s.socket; - } - return SUCCESS; - - case PHP_STREAM_AS_FD: - case PHP_STREAM_AS_SOCKETD: - if (sslsock->ssl_active) { - return FAILURE; - } - if (ret) { - *(int *)ret = sslsock->s.socket; - } - return SUCCESS; - default: - return FAILURE; - } + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + + switch(castas) { + case PHP_STREAM_AS_STDIO: + if (sslsock->ssl_active) { + return FAILURE; + } + if (ret) { + *ret = fdopen(sslsock->s.socket, stream->mode); + if (*ret) { + return SUCCESS; + } + return FAILURE; + } + return SUCCESS; + + case PHP_STREAM_AS_FD_FOR_SELECT: + if (ret) { + *(int *)ret = sslsock->s.socket; + } + return SUCCESS; + + case PHP_STREAM_AS_FD: + case PHP_STREAM_AS_SOCKETD: + if (sslsock->ssl_active) { + return FAILURE; + } + if (ret) { + *(int *)ret = sslsock->s.socket; + } + return SUCCESS; + default: + return FAILURE; + } } php_stream_ops php_openssl_socket_ops = { - php_openssl_sockop_write, php_openssl_sockop_read, - php_openssl_sockop_close, php_openssl_sockop_flush, - "tcp_socket/ssl", - NULL, /* seek */ - php_openssl_sockop_cast, - php_openssl_sockop_stat, - php_openssl_sockop_set_option, + php_openssl_sockop_write, php_openssl_sockop_read, + php_openssl_sockop_close, php_openssl_sockop_flush, + "tcp_socket/ssl", + NULL, /* seek */ + php_openssl_sockop_cast, + php_openssl_sockop_stat, + php_openssl_sockop_set_option, }; static char * get_sni(php_stream_context *ctx, char *resourcename, long resourcenamelen, int is_persistent TSRMLS_DC) { - php_url *url; - - if (ctx) { - zval **val = NULL; - - if (php_stream_context_get_option(ctx, "ssl", "SNI_enabled", &val) == SUCCESS && !zend_is_true(*val)) { - return NULL; - } - if (php_stream_context_get_option(ctx, "ssl", "SNI_server_name", &val) == SUCCESS) { - convert_to_string_ex(val); - return pestrdup(Z_STRVAL_PP(val), is_persistent); - } - } - - if (!resourcename) { - return NULL; - } - - url = php_url_parse_ex(resourcename, resourcenamelen); - if (!url) { - return NULL; - } - - if (url->host) { - const char * host = url->host; - char * sni = NULL; - size_t len = strlen(host); - - /* skip trailing dots */ - while (len && host[len-1] == '.') { - --len; - } - - if (len) { - sni = pestrndup(host, len, is_persistent); - } - - php_url_free(url); - return sni; - } - - php_url_free(url); - return NULL; + php_url *url; + + if (ctx) { + zval **val = NULL; + + if (php_stream_context_get_option(ctx, "ssl", "SNI_enabled", &val) == SUCCESS && !zend_is_true(*val)) { + return NULL; + } + if (php_stream_context_get_option(ctx, "ssl", "SNI_server_name", &val) == SUCCESS) { + convert_to_string_ex(val); + return pestrdup(Z_STRVAL_PP(val), is_persistent); + } + } + + if (!resourcename) { + return NULL; + } + + url = php_url_parse_ex(resourcename, resourcenamelen); + if (!url) { + return NULL; + } + + if (url->host) { + const char * host = url->host; + char * sni = NULL; + size_t len = strlen(host); + + /* skip trailing dots */ + while (len && host[len-1] == '.') { + --len; + } + + if (len) { + sni = pestrndup(host, len, is_persistent); + } + + php_url_free(url); + return sni; + } + + php_url_free(url); + return NULL; } php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, - char *resourcename, long resourcenamelen, - const char *persistent_id, int options, int flags, - struct timeval *timeout, - php_stream_context *context STREAMS_DC TSRMLS_DC) + char *resourcename, long resourcenamelen, + const char *persistent_id, int options, int flags, + struct timeval *timeout, + php_stream_context *context STREAMS_DC TSRMLS_DC) { - php_stream *stream = NULL; - php_openssl_netstream_data_t *sslsock = NULL; - - sslsock = pemalloc(sizeof(php_openssl_netstream_data_t), persistent_id ? 1 : 0); - memset(sslsock, 0, sizeof(*sslsock)); - - sslsock->s.is_blocked = 1; - /* this timeout is used by standard stream funcs, therefor it should use the default value */ - sslsock->s.timeout.tv_sec = FG(default_socket_timeout); - sslsock->s.timeout.tv_usec = 0; - - /* use separate timeout for our private funcs */ - sslsock->connect_timeout.tv_sec = timeout->tv_sec; - sslsock->connect_timeout.tv_usec = timeout->tv_usec; - - /* we don't know the socket until we have determined if we are binding or - * connecting */ - sslsock->s.socket = -1; - - /* Initialize context as NULL */ - sslsock->ctx = NULL; - - stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+"); - - if (stream == NULL) { - pefree(sslsock, persistent_id ? 1 : 0); - return NULL; - } - - sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id TSRMLS_CC); - - if (strncmp(proto, "ssl", protolen) == 0) { - sslsock->enable_on_connect = 1; - sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; - } else if (strncmp(proto, "sslv2", protolen) == 0) { + php_stream *stream = NULL; + php_openssl_netstream_data_t *sslsock = NULL; + + sslsock = pemalloc(sizeof(php_openssl_netstream_data_t), persistent_id ? 1 : 0); + memset(sslsock, 0, sizeof(*sslsock)); + + sslsock->s.is_blocked = 1; + /* this timeout is used by standard stream funcs, therefor it should use the default value */ + sslsock->s.timeout.tv_sec = FG(default_socket_timeout); + sslsock->s.timeout.tv_usec = 0; + + /* use separate timeout for our private funcs */ + sslsock->connect_timeout.tv_sec = timeout->tv_sec; + sslsock->connect_timeout.tv_usec = timeout->tv_usec; + + /* we don't know the socket until we have determined if we are binding or + * connecting */ + sslsock->s.socket = -1; + + /* Initialize context as NULL */ + sslsock->ctx = NULL; + + stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+"); + + if (stream == NULL) { + pefree(sslsock, persistent_id ? 1 : 0); + return NULL; + } + + sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id TSRMLS_CC); + + if (strncmp(proto, "ssl", protolen) == 0) { + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + } else if (strncmp(proto, "sslv2", protolen) == 0) { #ifdef OPENSSL_NO_SSL2 - php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); - return NULL; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); + return NULL; #else - sslsock->enable_on_connect = 1; - sslsock->method = STREAM_CRYPTO_METHOD_SSLv2_CLIENT; + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_SSLv2_CLIENT; #endif - } else if (strncmp(proto, "sslv3", protolen) == 0) { - sslsock->enable_on_connect = 1; - sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT; - } else if (strncmp(proto, "tls", protolen) == 0) { - sslsock->enable_on_connect = 1; - sslsock->method = STREAM_CRYPTO_METHOD_TLS_CLIENT; - } - - return stream; + } else if (strncmp(proto, "sslv3", protolen) == 0) { + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT; + } else if (strncmp(proto, "tls", protolen) == 0) { + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + } + + return stream; } @@ -1104,4 +1101,3 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, * vim<600: noet sw=4 ts=4 */ - From e4c967a02887f474beb67fc1c6cd46fa46d6316a Mon Sep 17 00:00:00 2001 From: Brad Broerman Date: Sun, 24 Feb 2013 09:54:39 -0500 Subject: [PATCH 4/4] Refactored read / write and enabe Per requests from users, I refactored the read / write methods and pulled out some of the common code between the new refactored method and php_openssl_enable_crypto(). Personally, I think that too much factoring can reduce readability, but it was specifically asked for. --- ext/openssl/xp_ssl.c | 331 ++++++++++++++++++++----------------------- 1 file changed, 151 insertions(+), 180 deletions(-) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 1385136249ce4..b13226d6806f7 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -47,7 +47,7 @@ int php_openssl_get_x509_list_id(void); * */ typedef struct _php_openssl_netstream_data_t { - php_netstream_data_t s; + php_netstream_data_t s; SSL *ssl_handle; SSL_CTX *ctx; struct timeval connect_timeout; @@ -172,218 +172,190 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init } -static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) +static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int didwrite; - - if (sslsock->ssl_active) { - int retry = 1; - struct timeval start_time, - *timeout; - int blocked = sslsock->s.is_blocked, - has_timeout = 0; - - if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { - sslsock->s.is_blocked = 0; - } - - timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; - has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); - - /* gettimeofday is not monotonic; using it here is not strictly correct */ - if (has_timeout) { - gettimeofday(&start_time, NULL); - } - - do { - struct timeval cur_time, - elapsed_time; - - if (has_timeout) { - gettimeofday(&cur_time, NULL); - elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; - elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; - if (cur_time.tv_usec < start_time.tv_usec) { - elapsed_time.tv_sec -= 1L; - elapsed_time.tv_usec += 1000000L; - } - - if (elapsed_time.tv_sec > timeout->tv_sec || - (elapsed_time.tv_sec == timeout->tv_sec && - elapsed_time.tv_usec > timeout->tv_usec)) { - if( blocked ) { - php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); - sslsock->s.is_blocked = 1; - } - - return -1; - } - } - - didwrite = SSL_write(sslsock->ssl_handle, buf, count); - - struct timeval left_time; - - if (has_timeout) { - left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; - left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; - if (timeout->tv_usec < elapsed_time.tv_usec) { - left_time.tv_sec -= 1L; - left_time.tv_usec += 1000000L; - } - } - - if (didwrite <= 0) { - int err = SSL_get_error(sslsock->ssl_handle, didwrite ); - retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC); - - if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; - - if( retry && blocked ) { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? - (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); - } - } else { - int err = SSL_get_error(sslsock->ssl_handle, didwrite ); - - if( err == SSL_ERROR_NONE ) - break; - if( blocked ) { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? - (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); - } - } - } while(retry); - - if( blocked ) { - php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); - sslsock->s.is_blocked = 1; - } - - if (didwrite > 0) { - php_stream_notify_progress_increment(stream->context, didwrite, 0); - } - } else { - didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); - } + return php_openssl_sockop_io( true, stream, buf, count ); +} - if (didwrite < 0) { - didwrite = 0; - } - - return didwrite; +static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) +{ + return php_openssl_sockop_io( false, stream, buf, count ); } -static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) +/** + * Factored out common functionality (blocking, timeout, loop management) for read and write. + * Perform IO (read or write) to an SSL socket. If we have a timeout, we switch to non-blocking mode + * for the duration of the operation, using select to do our waits. If we time out, or we have an error + * report that back to PHP + * + */ +static size_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, size_t count TSRMLS_DC) { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int nr_bytes = 0; - - if (sslsock->ssl_active) { - int retry = 1; - struct timeval start_time, - *timeout; - int blocked = sslsock->s.is_blocked, - has_timeout = 0; - + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int nr_bytes = 0; + + /* Only do this if SSL is active. */ + if (sslsock->ssl_active) { + int retry = 1; + struct timeval start_time, + *timeout; + int blocked = sslsock->s.is_blocked, + has_timeout = 0; + + /* Begin by making the socket non-blocking. This allows us to check the timeout. */ if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { sslsock->s.is_blocked = 0; } - + + /* Get the timeout value (and make sure we are to check it. */ timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout; has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec); - + /* gettimeofday is not monotonic; using it here is not strictly correct */ if (has_timeout) { gettimeofday(&start_time, NULL); } + /* Main IO loop. */ do { - struct timeval cur_time, - elapsed_time; - + struct timeval cur_time, + elapsed_time; + + /* If we have a timeout to check, figure out how much time has elapsed since we started. */ if (has_timeout) { gettimeofday(&cur_time, NULL); - elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; - elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; - if (cur_time.tv_usec < start_time.tv_usec) { - elapsed_time.tv_sec -= 1L; - elapsed_time.tv_usec += 1000000L; - } - - if (elapsed_time.tv_sec > timeout->tv_sec || - (elapsed_time.tv_sec == timeout->tv_sec && - elapsed_time.tv_usec > timeout->tv_usec)) { - if( blocked ) { + + /* Determine how much time we've taken so far. */ + elapsed_time = subtractTimeval( curr_time, start_time ); + + /* and return an error if we've taken too long. */ + if (compareTimeval( elapsed_time, timeout) > 0 ) { + /* If the socket was originally blocking, set it back. */ + if (blocked) { php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); sslsock->s.is_blocked = 1; } - return -1; } } - - nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); + /* Now, do the IO operation. Don't block if we can't complete... */ + if (read) { + nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); + } else { + nr_bytes = SSL_write(sslsock->ssl_handle, buf, count); + } + + /* Now, how much time until we time out? */ struct timeval left_time; - if (has_timeout) { - left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; - left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; - if (timeout->tv_usec < elapsed_time.tv_usec) { - left_time.tv_sec -= 1L; - left_time.tv_usec += 1000000L; - } + left_time = subtractTimeval( timeout, elapsed_time ); } - + + /* If we didn't do anything on the last loop (or an error) check to see if we should retry or exit. */ if (nr_bytes <= 0) { - int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); - + /* Get the error code from SSL, and check to see if it's an error or not. */ + int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC); - if( errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) retry = 1; + /* If we get this (the above doesn't check) then we'll retry as well. */ + if (errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) { + retry = 1; + } - stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); - - if( retry && blocked ) { - php_pollfd_for( sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? - (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); - } - + /* Also, on reads, we may get this condition on an EOF. We should check properly. */ + if (read) { + stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); + } + + /* Now, if we have to wait some time, and we're supposed to be blocking, wait for the socket to become + * available. Now, php_pollfd_for uses select to wait up to our time_left value only... + */ + if (retry && blocked) { + if (read) { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); + } else { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); + } + } } else { - /* we got the data */ + /* Else, if we got bytes back, check for possible errors. */ int err = SSL_get_error(sslsock->ssl_handle, nr_bytes ); - if( err == SSL_ERROR_NONE ) + /* If we didn't get any error, then let's return it to PHP. */ + if (err == SSL_ERROR_NONE) break; - if( blocked ) { - php_pollfd_for( sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? - (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); - } + + /* Otherwise, we need to wait again (up to time_left or we get an error) */ + if (blocked) + if (read) { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); + } else { + php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); + } } + /* Finally, we keep going until we got data, and an SSL_ERROR_NONE, unless we had an error. */ } while (retry); + /* Tell PHP if we read / wrote bytes. */ if (nr_bytes > 0) { php_stream_notify_progress_increment(stream->context, nr_bytes, 0); } - - if( blocked ) { - php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); - sslsock->s.is_blocked = 1; + + /* And if we were originally supposed to be blocking, let's reset the socket to that. */ + if (blocked) { + php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC); + sslsock->s.is_blocked = 1; } - } else { - nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); - } + } else { + /* + * This block is if we had no timeout... We will just sit and wait forever on the IO operation. + */ + if (read) { + nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); + } else { + nr_bytes = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); + } + } + + /* PHP doesn't expect a negative return. */ + if (nr_bytes < 0) { + nr_bytes = 0; + } + + return nr_bytes; +} + +struct timeval subtractTimeval( struct timeval a, struct timeval b ) +{ + timeval difference; - if (nr_bytes < 0) { - nr_bytes = 0; + difference.tv_sec = a.tv_sec - b.tv_sec; + difference.tv_usec = a.tv_usec - b.tv_usec; + + if (a.tv_usec < b.tv_usec) { + b.tv_sec -= 1L; + b.tv_usec += 1000000L; } - return nr_bytes; + return difference; } +int compareTimeval( struct timeval a, struct timeval b ) +{ + if (a.tv_sec > b.tv_sec || (a.tv_sec == b.tv_sec && a.tv_usec > b.tv_usec) ) { + return 1; + } else if( a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec ) { + return 0; + } else { + return -1; + } +} static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC) { @@ -538,6 +510,18 @@ static inline int php_openssl_setup_crypto(php_stream *stream, } #endif +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + { + zval **val; + + if (stream->context && SUCCESS == php_stream_context_get_option( + stream->context, "ssl", "disable_compression", &val) && + zval_is_true(*val)) { + SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_COMPRESSION); + } + } +#endif + sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC); if (sslsock->ssl_handle == NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle"); @@ -613,16 +597,9 @@ static inline int php_openssl_enable_crypto(php_stream *stream, if (has_timeout) { gettimeofday(&cur_time, NULL); - elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec; - elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec; - if (cur_time.tv_usec < start_time.tv_usec) { - elapsed_time.tv_sec -= 1L; - elapsed_time.tv_usec += 1000000L; - } + elapsed_time = subtractTimeval( cur_time, start_time ); - if (elapsed_time.tv_sec > timeout->tv_sec || - (elapsed_time.tv_sec == timeout->tv_sec && - elapsed_time.tv_usec > timeout->tv_usec)) { + if (compareTimeval( elapsed_time, timeout) > 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout"); return -1; } @@ -638,12 +615,7 @@ static inline int php_openssl_enable_crypto(php_stream *stream, struct timeval left_time; if (has_timeout) { - left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec; - left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec; - if (timeout->tv_usec < elapsed_time.tv_usec) { - left_time.tv_sec -= 1L; - left_time.tv_usec += 1000000L; - } + left_time = subtractTimeval( timeout, elapsed_time ); } php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL); @@ -1100,4 +1072,3 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ -