php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Return to Bug #41631
Patch ssl_read_timeout-5.4.32.patch revision 2014-09-29 18:44 UTC by askalski at synacor dot com
revision 2014-09-26 18:38 UTC by askalski at synacor dot com
Patch bug41631.patch revision 2014-09-19 15:13 UTC by rdlowrey@php.net

Patch ssl_read_timeout-5.4.32.patch for OpenSSL related Bug #41631

Patch version 2014-09-29 18:44 UTC

Return to Bug #41631 | Download this patch
This patch renders other patches obsolete

Obsolete patches:

Patch Revisions:

Developer: askalski@synacor.com

diff --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:
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Apr 19 15:01:28 2024 UTC