php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login | |
Patch ssl_read_timeout-5.4.32.patch for OpenSSL related Bug #41631Patch version 2014-09-29 18:44 UTC Return to Bug #41631 | Download this patchThis patch renders other patches obsolete Obsolete patches: Patch Revisions:Developer: askalski@synacor.comdiff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 3082c83..c2b2276 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -18,6 +18,22 @@ /* $Id$ */ +/* Refurbished by askalski@synacor.com to support socket timeouts. + * + * All (almost all) of the OpenSSL I/O calls are now made in nonblocking + * mode, and retries are done until a deadline timeout. + * + * Because of the all-or-nothing semantics of SSL_write, any unfinished + * writes will be buffered internally, and a successful write will be + * returned to the caller in the event of a timeout. A flush operation + * can be used to ensure all bytes are written out to the wire. Subsequent + * I/O operations (read, write, shutdown) will first attempt to flush + * a previously unfinished SSL_write before proceeding. + * + * Andrew Skalski <askalski@synacor.com> + * 2011-11-10 + */ + #include "php.h" #include "ext/standard/file.h" #include "ext/standard/url.h" @@ -28,6 +44,7 @@ #include <openssl/ssl.h> #include <openssl/x509.h> #include <openssl/err.h> +#include <assert.h> #ifdef PHP_WIN32 #include "win32/time.h" @@ -41,11 +58,17 @@ int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stre SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC); int php_openssl_get_x509_list_id(void); +static int php_openssl_wait(php_netstream_data_t *sock, int poll_flags, struct timeval *timeout TSRMLS_DC); +static int php_openssl_SSL_read(php_stream *stream, char *buf, size_t count, struct timeval *timeout, int was_blocked TSRMLS_DC); +static int php_openssl_SSL_peek_ok(php_stream *stream, struct timeval *timeout, int was_blocked TSRMLS_DC); +static int php_openssl_SSL_write(php_stream *stream, const char *buf, size_t count, struct timeval *timeout, int was_blocked TSRMLS_DC); +static int php_openssl_SSL_finish_write(php_stream *stream, struct timeval *timeout, int was_blocked TSRMLS_DC); +static int php_openssl_SSL_shutdown(php_stream *stream, struct timeval *timeout, int was_blocked TSRMLS_DC); + /* This implementation is very closely tied to the that of the native * sockets implemented in the core. * Don't try this technique in other extensions! - * */ - + */ typedef struct _php_openssl_netstream_data_t { php_netstream_data_t s; SSL *ssl_handle; @@ -58,10 +81,13 @@ typedef struct _php_openssl_netstream_data_t { char *sni; unsigned state_set:1; unsigned _spare:31; + char *incomplete_write; + int incomplete_write_len; } php_openssl_netstream_data_t; php_stream_ops php_openssl_socket_ops; +/* {{{ is_http_stream_talking_to_iis */ /* it doesn't matter that we do some hash traversal here, since it is done only * in an error condition arising from a network connection problem */ static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC) @@ -87,27 +113,49 @@ static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC) } return 0; } +/* }}} */ + +/* {{{ BEGIN_NONBLOCK / END_NONBLOCK */ +#define BEGIN_NONBLOCK() \ + { \ + int was_blocked = sslsock->s.is_blocked; \ + if (was_blocked) { \ + if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) { \ + sslsock->s.is_blocked = 0; \ + } \ + } -static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init TSRMLS_DC) +#define END_NONBLOCK() \ + if (was_blocked != sslsock->s.is_blocked) { \ + if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, was_blocked TSRMLS_CC)) { \ + sslsock->s.is_blocked = was_blocked; \ + } \ + } \ + } +/* }}} */ + +/* {{{ handle_ssl_error */ +static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool was_blocked 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; + int retry = 0; 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; + if (sslsock->s.is_blocked || was_blocked) { + retry = err; + } break; case SSL_ERROR_SYSCALL: if (ERR_peek_error() == 0) { @@ -118,7 +166,6 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init } 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); @@ -126,7 +173,6 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init "SSL: %s", estr); efree(estr); - retry = 0; } break; } @@ -140,7 +186,6 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init 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: @@ -165,92 +210,86 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init } } - retry = 0; errno = 0; } return retry; } +/* }}} */ - +/* {{{ php_openssl_sockop_write */ 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; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; + struct timeval timeout; + int nr_bytes = 0; - do { - didwrite = SSL_write(sslsock->ssl_handle, buf, count); + if (!sslsock->ssl_active) { + return php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); + } - if (didwrite <= 0) { - retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC); - } else { - break; - } - } while(retry); + BEGIN_NONBLOCK() + timeout = sslsock->s.timeout; + if (!sslsock->incomplete_write || php_openssl_SSL_finish_write(stream, &timeout, was_blocked TSRMLS_CC) > 0) { + nr_bytes = php_openssl_SSL_write(stream, buf, count, &timeout, was_blocked TSRMLS_CC); + } + END_NONBLOCK() - if (didwrite > 0) { - php_stream_notify_progress_increment(stream->context, didwrite, 0); - } + if (nr_bytes > 0) { + php_stream_notify_progress_increment(stream->context, nr_bytes, 0); } else { - didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); + nr_bytes = 0; } - if (didwrite < 0) { - didwrite = 0; - } - - return didwrite; + return nr_bytes; } +/* }}} */ +/* {{{ php_openssl_sockop_read */ 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; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; + struct timeval timeout; 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); - } + if (!sslsock->ssl_active) { + return php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); } - else - { - nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); + + BEGIN_NONBLOCK() + timeout = sslsock->s.timeout; + if (!sslsock->incomplete_write || php_openssl_SSL_finish_write(stream, &timeout, was_blocked TSRMLS_CC) > 0) { + nr_bytes = php_openssl_SSL_read(stream, buf, count, &timeout, was_blocked TSRMLS_CC); } + END_NONBLOCK() - if (nr_bytes < 0) { + if (nr_bytes > 0) { + php_stream_notify_progress_increment(stream->context, nr_bytes, 0); + } else { nr_bytes = 0; } return nr_bytes; } +/* }}} */ - +/* {{{ php_openssl_sockop_close */ 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; + struct timeval timeout; #ifdef PHP_WIN32 int n; #endif + if (close_handle) { if (sslsock->ssl_active) { - SSL_shutdown(sslsock->ssl_handle); + BEGIN_NONBLOCK() + timeout = sslsock->s.timeout; + if (sslsock->incomplete_write) { + php_openssl_SSL_finish_write(stream, &timeout, was_blocked TSRMLS_CC); + } + php_openssl_SSL_shutdown(stream, &timeout, was_blocked TSRMLS_CC); + END_NONBLOCK(); sslsock->ssl_active = 0; } if (sslsock->ssl_handle) { @@ -261,6 +300,11 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_ SSL_CTX_free(sslsock->ctx); sslsock->ctx = NULL; } + if (sslsock->incomplete_write) { + efree(sslsock->incomplete_write); + sslsock->incomplete_write = NULL; + sslsock->incomplete_write_len = 0; + } #ifdef PHP_WIN32 if (sslsock->s.socket == -1) sslsock->s.socket = SOCK_ERR; @@ -289,21 +333,43 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_ pefree(sslsock->sni, php_stream_is_persistent(stream)); } pefree(sslsock, php_stream_is_persistent(stream)); - + return 0; } +/* }}} */ +/* {{{ php_openssl_sockop_flush */ static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC) { - return php_stream_socket_ops.flush(stream TSRMLS_CC); + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; + struct timeval timeout; + int retval = 0; + + if (!sslsock->ssl_active) { + return php_stream_socket_ops.flush(stream TSRMLS_CC); + } + + if (sslsock->incomplete_write) { + BEGIN_NONBLOCK() + timeout = sslsock->s.timeout; + if (php_openssl_SSL_finish_write(stream, &timeout, was_blocked TSRMLS_CC) <= 0) { + retval = EOF; + } + END_NONBLOCK() + } + + return retval; } +/* }}} */ +/* {{{ php_openssl_sockop_stat */ 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); } +/* }}} */ - +/* {{{ php_openssl_setup_crypto */ static inline int php_openssl_setup_crypto(php_stream *stream, php_openssl_netstream_data_t *sslsock, php_stream_xport_crypto_param *cparam @@ -407,6 +473,9 @@ static inline int php_openssl_setup_crypto(php_stream *stream, } #endif + /* Incomplete writes are copied before they are retried */ + SSL_CTX_set_mode(sslsock->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + 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"); @@ -416,7 +485,7 @@ static inline int php_openssl_setup_crypto(php_stream *stream, } if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { - handle_ssl_error(stream, 0, 1 TSRMLS_CC); + handle_ssl_error(stream, 0, 0 TSRMLS_CC); } if (cparam->inputs.session) { @@ -430,19 +499,19 @@ static inline int php_openssl_setup_crypto(php_stream *stream, } return 0; } +/* }}} */ +/* {{{ php_openssl_enable_crypto */ static inline int php_openssl_enable_crypto(php_stream *stream, php_openssl_netstream_data_t *sslsock, php_stream_xport_crypto_param *cparam TSRMLS_DC) { - int n, retry = 1; + int n, retry; if (cparam->inputs.activate && !sslsock->ssl_active) { - struct timeval start_time, - *timeout; - int blocked = sslsock->s.is_blocked, - has_timeout = 0; + struct timeval tv, *timeout = NULL; + int was_blocked = sslsock->s.is_blocked; #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) if (sslsock->is_client && sslsock->sni) { @@ -462,68 +531,46 @@ static inline int php_openssl_enable_crypto(php_stream *stream, 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); + + if (!sslsock->s.is_blocked) { + tv = sslsock->is_client ? sslsock->connect_timeout : sslsock->s.timeout; + if (tv.tv_sec != -1) { + timeout = &tv; + } } - - do { - struct timeval cur_time, - elapsed_time = {0}; - + + while (1) { 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) { + break; } - 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; + /* in case of SSL_ERROR_WANT_READ/WRITE, do not retry in non-blocking mode */ + retry = handle_ssl_error(stream, n, was_blocked TSRMLS_CC); + if (!retry) { + break; } - } while (retry); - if (sslsock->s.is_blocked != blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) { - sslsock->s.is_blocked = blocked; + /* wait until something interesting happens in the socket. It may be a + * timeout. Also consider the unlikely of possibility of a write block + */ + n = php_pollfd_for(sslsock->s.socket, (retry == SSL_ERROR_WANT_READ) ? + (POLLIN|POLLPRI) : POLLOUT, timeout); + if (n == 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout"); + errno = 0; + n = -1; + break; + } + } + + if (sslsock->s.is_blocked != was_blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, was_blocked TSRMLS_CC)) { + sslsock->s.is_blocked = was_blocked; } if (n == 1) { @@ -532,9 +579,12 @@ static inline int php_openssl_enable_crypto(php_stream *stream, peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle); if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) { + // omitting timeout here for complexity reasons; this unidirectional SSL_shutdown + // immediately follows an accept/connect, so the tcp send buffer will have plenty + // of space SSL_shutdown(sslsock->ssl_handle); n = -1; - } else { + } else { sslsock->ssl_active = 1; /* allow the script to capture the peer cert @@ -610,7 +660,9 @@ static inline int php_openssl_enable_crypto(php_stream *stream, } return -1; } +/* }}} */ +/* {{{ php_openssl_tcp_sockop_accept */ 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) { @@ -684,12 +736,15 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_ } } } - + return xparam->outputs.client == NULL ? -1 : 0; } +/* }}} */ + +/* {{{ php_openssl_sockop_set_option */ 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_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; @@ -714,33 +769,17 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val if (sslsock->s.socket == -1) { alive = 0; + } else if (sslsock->ssl_active) { + BEGIN_NONBLOCK() + errno = 0; + if ((sslsock->incomplete_write && php_openssl_SSL_finish_write(stream, &tv, was_blocked TSRMLS_CC) <= 0) || + php_openssl_SSL_peek_ok(stream, &tv, was_blocked TSRMLS_CC) <= 0) + { + alive = (errno == EAGAIN); + } + END_NONBLOCK() } 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) { + if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) { alive = 0; } } @@ -748,7 +787,6 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val } case PHP_STREAM_OPTION_CRYPTO_API: - switch(cparam->op) { case STREAM_XPORT_CRYPTO_OP_SETUP: @@ -804,12 +842,14 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val return php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); } +/* }}} */ +/* {{{ php_openssl_sockop_cast */ 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; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; - switch(castas) { + switch (castas) { case PHP_STREAM_AS_STDIO: if (sslsock->ssl_active) { return FAILURE; @@ -842,7 +882,165 @@ static int php_openssl_sockop_cast(php_stream *stream, int castas, void **ret TS return FAILURE; } } +/* }}} */ +/* {{{ php_openssl_wait */ +static int php_openssl_wait(php_netstream_data_t *sock, int poll_flags, struct timeval *timeout TSRMLS_DC) +{ + struct timeval *ptimeout = (timeout && timeout->tv_sec != -1) ? timeout : NULL; + + sock->timeout_event = 0; + while (1) { + int n = php_pollfd_for(sock->socket, poll_flags, ptimeout); + if (n == 0) { + sock->timeout_event = 1; + } + if (n >= 0) { + break; + } + if (php_socket_errno() != EINTR) { + break; + } + } + + return !sock->timeout_event; +} +/* }}} */ + +/* {{{ php_openssl_SSL_read */ +static int php_openssl_SSL_read(php_stream *stream, char *buf, size_t count, struct timeval *timeout, int was_blocked TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; + int nr_bytes, retry; + + assert(sslsock->incomplete_write == NULL); + + while ((nr_bytes = SSL_read(sslsock->ssl_handle, buf, count)) <= 0) { + retry = handle_ssl_error(stream, nr_bytes, was_blocked TSRMLS_CC); + if (retry == SSL_ERROR_WANT_WRITE) { + if (!php_openssl_wait(&sslsock->s, POLLOUT, timeout TSRMLS_CC)) { + break; + } + } else if (retry == SSL_ERROR_WANT_READ) { + if (!php_openssl_wait(&sslsock->s, PHP_POLLREADABLE, timeout TSRMLS_CC)) { + break; + } + } else { + /* EAGAIN check is relevant for nonblocking stream */ + stream->eof = (errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); + break; + } + } + + return nr_bytes; +} +/* }}} */ + +/* {{{ php_openssl_SSL_peek_ok */ +static int php_openssl_SSL_peek_ok(php_stream *stream, struct timeval *timeout, int was_blocked TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; + int nr_bytes, retry; + char buf; + + assert(sslsock->incomplete_write == NULL); + + while ((nr_bytes = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf))) <= 0) { + retry = handle_ssl_error(stream, nr_bytes, was_blocked TSRMLS_CC); + if (retry == SSL_ERROR_WANT_WRITE) { + if (!php_openssl_wait(&sslsock->s, POLLOUT, timeout TSRMLS_CC)) { + break; + } + } else if (retry == SSL_ERROR_WANT_READ) { + if (!php_openssl_wait(&sslsock->s, PHP_POLLREADABLE, timeout TSRMLS_CC)) { + break; + } + } else { + /* EAGAIN check is relevant for nonblocking stream */ + stream->eof = (errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); + break; + } + } + + return nr_bytes; +} +/* }}} */ + +/* {{{ php_openssl_SSL_write */ +static int php_openssl_SSL_write(php_stream *stream, const char *buf, size_t count, struct timeval *timeout, int was_blocked TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; + int nr_bytes, retry; + + assert(sslsock->incomplete_write == NULL || buf == sslsock->incomplete_write); + + while ((nr_bytes = SSL_write(sslsock->ssl_handle, (void *) buf, count)) <= 0 && + (retry = handle_ssl_error(stream, nr_bytes, was_blocked TSRMLS_CC))) + { + if (retry == SSL_ERROR_WANT_WRITE) { + if (!php_openssl_wait(&sslsock->s, POLLOUT, timeout TSRMLS_CC)) { + break; + } + } else if (retry == SSL_ERROR_WANT_READ) { + if (!php_openssl_wait(&sslsock->s, PHP_POLLREADABLE, timeout TSRMLS_CC)) { + break; + } + } + } + + if ((nr_bytes < 0 && errno == EAGAIN) && buf != sslsock->incomplete_write) { + /* incomplete write - save the buffer for retry */ + sslsock->incomplete_write = emalloc(count); + sslsock->incomplete_write_len = count; + memcpy(sslsock->incomplete_write, buf, count); + nr_bytes = count; + } else if ((nr_bytes >= 0 || errno != EAGAIN) && buf == sslsock->incomplete_write) { + /* incomplete write finally finished */ + efree(sslsock->incomplete_write); + sslsock->incomplete_write = NULL; + sslsock->incomplete_write_len = 0; + } + + return nr_bytes; +} +/* }}} */ + +/* {{{ php_openssl_SSL_finish_write */ +static int php_openssl_SSL_finish_write(php_stream *stream, struct timeval *timeout, int was_blocked TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; + + assert(sslsock->incomplete_write != NULL); + + return php_openssl_SSL_write(stream, sslsock->incomplete_write, sslsock->incomplete_write_len, timeout, was_blocked TSRMLS_CC); +} +/* }}} */ + +/* {{{ php_openssl_SSL_shutdown */ +static int php_openssl_SSL_shutdown(php_stream *stream, struct timeval *timeout, int was_blocked TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *) stream->abstract; + int retval, retry; + + while ((retval = SSL_shutdown(sslsock->ssl_handle)) <= 0 && + (retry = handle_ssl_error(stream, retval, was_blocked TSRMLS_CC))) + { + if (retry == SSL_ERROR_WANT_WRITE) { + if (!php_openssl_wait(&sslsock->s, POLLOUT, timeout TSRMLS_CC)) { + break; + } + } else if (retry == SSL_ERROR_WANT_READ) { + if (!php_openssl_wait(&sslsock->s, PHP_POLLREADABLE, timeout TSRMLS_CC)) { + break; + } + } + } + + return retval; +} +/* }}} */ + +/* {{{ struct php_openssl_socket_ops */ php_stream_ops php_openssl_socket_ops = { php_openssl_sockop_write, php_openssl_sockop_read, php_openssl_sockop_close, php_openssl_sockop_flush, @@ -852,9 +1050,11 @@ php_stream_ops php_openssl_socket_ops = { 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) { - +/* {{{ get_sni */ +static char * get_sni(php_stream_context *ctx, char *resourcename, long resourcenamelen, int is_persistent TSRMLS_DC) +{ php_url *url; if (ctx) { @@ -899,7 +1099,9 @@ static char * get_sni(php_stream_context *ctx, char *resourcename, long resource php_url_free(url); return NULL; } +/* }}} */ +/* {{{ php_openssl_ssl_socket_factory */ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, char *resourcename, long resourcenamelen, const char *persistent_id, int options, int flags, @@ -927,7 +1129,11 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, /* Initialize context as NULL */ sslsock->ctx = NULL; - + + /* Initialize the write buffer as NULL */ + sslsock->incomplete_write = NULL; + sslsock->incomplete_write_len = 0; + stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+"); if (stream == NULL) { @@ -958,8 +1164,7 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, return stream; } - - +/* }}} */ /* * Local variables: |
Copyright © 2001-2025 The PHP Group All rights reserved. |
Last updated: Sat Feb 01 07:01:31 2025 UTC |