summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Broerman <bbroerman@bbroerman.net>2015-01-28 00:04:20 -0500
committerBrad Broerman <bbroerman@bbroerman.net>2015-01-28 00:04:20 -0500
commitfd4641696cc67fedf494717b5e4d452019f04d6f (patch)
tree568e1143e34dd9d2274a88ec71c48b075dfa6e15
parent4c5995b1729b100b00707ddf32d072355dcc3ae8 (diff)
downloadphp-git-fd4641696cc67fedf494717b5e4d452019f04d6f.tar.gz
Updated with SSL fixes (backported from trunk)
-rw-r--r--ext/openssl/xp_ssl.c204
1 files changed, 159 insertions, 45 deletions
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index 6b74a2bb73..53c48beca7 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -40,6 +40,9 @@
int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC);
SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC);
int php_openssl_get_x509_list_id(void);
+static struct timeval subtract_timeval( struct timeval a, struct timeval b );
+static int compare_timeval( struct timeval a, struct timeval b );
+static size_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, size_t count TSRMLS_DC);
/* This implementation is very closely tied to the that of the native
* sockets implemented in the core.
@@ -171,69 +174,167 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init
return retry;
}
-
static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
{
+ return php_openssl_sockop_io( 0, stream, buf, count );
+}
+
+static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ return php_openssl_sockop_io( 1, stream, buf, count );
+}
+
+/**
+ * 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 didwrite;
+ 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;
- do {
- didwrite = SSL_write(sslsock->ssl_handle, buf, count);
+ /* 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;
+ }
- if (didwrite <= 0) {
- retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC);
- } else {
- break;
- }
- } while(retry);
+ /* 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);
- if (didwrite > 0) {
- php_stream_notify_progress_increment(stream->context, didwrite, 0);
- }
- } else {
- didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC);
+ /* gettimeofday is not monotonic; using it here is not strictly correct */
+ if (has_timeout) {
+ gettimeofday(&start_time, NULL);
}
- if (didwrite < 0) {
- didwrite = 0;
- }
+ /* Main IO loop. */
+ do {
+ struct timeval cur_time, elapsed_time, left_time;
- return didwrite;
-}
+ /* If we have a timeout to check, figure out how much time has elapsed since we started. */
+ if (has_timeout) {
+ gettimeofday(&cur_time, NULL);
-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;
+ /* Determine how much time we've taken so far. */
+ elapsed_time = subtract_timeval( cur_time, start_time );
- if (sslsock->ssl_active) {
- int retry = 1;
+ /* and return an error if we've taken too long. */
+ if (compare_timeval( 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;
+ }
+ }
- do {
+ /* Now, do the IO operation. Don't block if we can't complete... */
+ if (read) {
nr_bytes = SSL_read(sslsock->ssl_handle, buf, count);
+ if (sslsock->reneg && sslsock->reneg->should_close) {
+ /* renegotiation rate limiting triggered */
+ php_stream_xport_shutdown(stream, (stream_shutdown_t)SHUT_RDWR);
+ nr_bytes = 0;
+ stream->eof = 1;
+ break;
+ }
+ } else {
+ nr_bytes = SSL_write(sslsock->ssl_handle, buf, count);
+ }
+
+ /* Now, how much time until we time out? */
+ if (has_timeout) {
+ left_time = subtract_timeval( *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) {
+
+ /* 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 we get this (the above doesn't check) then we'll retry as well. */
+ if (errno == EAGAIN && err == SSL_ERROR_WANT_READ && read) {
+ retry = 1;
+ }
+ if (errno == EAGAIN && SSL_ERROR_WANT_WRITE && read == 0) {
+ retry = 1;
+ }
+
+ /* 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 we didn't get any error, then let's return it to PHP. */
+ if (err == SSL_ERROR_NONE)
break;
+
+ /* 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);
}
+
+ /* 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
- {
+ } 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;
}
@@ -241,6 +342,31 @@ static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t coun
return nr_bytes;
}
+struct timeval subtract_timeval( struct timeval a, struct timeval b )
+{
+ struct timeval difference;
+
+ 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 difference;
+}
+
+int compare_timeval( 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)
{
@@ -492,16 +618,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 = subtract_timeval( 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 (compare_timeval( elapsed_time, *timeout) > 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout");
return -1;
}
@@ -517,12 +636,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 = subtract_timeval( *timeout, elapsed_time );
}
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
(POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL);