[php-src] master: ext/openssl: convert stream errors to new stream error API (#22318)

Gina Peter Banyard via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Gina Peter Banyard (Girgias)
Committer: GitHub (web-flow)
Pusher: bukka
Date: 2026-06-28T19:54:51+02:00

Commit: https://github.com/php/php-src/commit/51b934d445074fdae28a744d2b094297136137cd
Raw diff: https://github.com/php/php-src/commit/51b934d445074fdae28a744d2b094297136137cd.diff

ext/openssl: convert stream errors to new stream error API (#22318)

Convert the ext/openssl stream messages to the new stream error API so they
honour the context error_mode/store like the other converted streams.

All messages emitted while a stream error operation is open must go through
the new API, otherwise mixing immediate php_error_docref() with buffered
reporting reorders the output. This required emitting "Accept failed" via
php_stream_warn() (adding StreamErrorCode::AcceptFailed) and threading a
php_stream* through php_openssl_check_path_ex() (NULL for non-stream callers).

Fingerprint changes: refactor php_openssl_x509_fingerprint_cmp() into an
'is equal' variant using zend_string; make php_openssl_x509_fingerprint_match()
warn on all failure cases to avoid double warnings; thread a php_stream* into
php_openssl_x509_fingerprint() so it reports through the operation, and demote
its internal "Could not generate signature" failure from E_ERROR to a warning.

Co-authored-by: Jakub Zelenka <[email protected]>

Changed paths:
  M  ext/openssl/openssl.c
  M  ext/openssl/openssl_backend_common.c
  M  ext/openssl/php_openssl.h
  M  ext/openssl/php_openssl_backend.h
  M  ext/openssl/tests/ServerClientTestCase.inc
  M  ext/openssl/tests/bug68920.phpt
  M  ext/openssl/xp_ssl.c
  M  ext/standard/streamsfuncs.c
  M  ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt
  M  main/streams/stream_errors.stub.php
  M  main/streams/stream_errors_arginfo.h
  M  main/streams/stream_errors_decl.h


Diff:

diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index 6b29471b2486..1e63eb1381f6 100644
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -651,7 +651,8 @@ static void php_openssl_check_path_error(uint32_t arg_num, int type, const char
 /* openssl file path check extended */
 bool php_openssl_check_path_ex(
 		const char *file_path, size_t file_path_len, char *real_path, uint32_t arg_num,
-		bool contains_file_protocol, bool is_from_array, const char *option_name)
+		bool contains_file_protocol, bool is_from_array, const char *option_name,
+		php_stream *stream)
 {
 	const char *fs_file_path;
 	size_t fs_file_path_len;
@@ -686,8 +687,13 @@ bool php_openssl_check_path_ex(
 		if (arg_num == 0) {
 			const char *option_title = option_name ? option_name : "unknown";
 			const char *option_label = is_from_array ? "array item" : "option";
-			php_error_docref(NULL, E_WARNING, "Path for %s %s %s",
-					option_title, option_label, error_msg);
+			if (stream != NULL) {
+				php_stream_warn(stream, InvalidPath, "Path for %s %s %s",
+						option_title, option_label, error_msg);
+			} else {
+				php_error_docref(NULL, E_WARNING, "Path for %s %s %s",
+						option_title, option_label, error_msg);
+			}
 		} else if (is_from_array && option_name != NULL) {
 			php_openssl_check_path_error(
 					arg_num, error_type, "option %s array item %s", option_name, error_msg);
@@ -1294,7 +1300,7 @@ PHP_FUNCTION(openssl_x509_fingerprint)
 		RETURN_FALSE;
 	}
 
-	fingerprint = php_openssl_x509_fingerprint(cert, method, raw_output);
+	fingerprint = php_openssl_x509_fingerprint(cert, method, raw_output, NULL);
 	if (fingerprint) {
 		RETVAL_STR(fingerprint);
 	} else {
diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c
index ba9bccff98c0..8115a7c91b3a 100644
--- a/ext/openssl/openssl_backend_common.c
+++ b/ext/openssl/openssl_backend_common.c
@@ -307,7 +307,7 @@ int php_openssl_parse_config(struct php_x509_request * req, zval * optional_args
 
 	/* read in the oids */
 	str = php_openssl_conf_get_string(req->req_config, NULL, "oid_file");
-	if (str != NULL && php_openssl_check_path_ex(str, strlen(str), path, 0, false, false, "oid_file")) {
+	if (str != NULL && php_openssl_check_path_ex(str, strlen(str), path, 0, false, false, "oid_file", NULL)) {
 		BIO *oid_bio = BIO_new_file(path, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
 		if (oid_bio) {
 			OBJ_create_objects(oid_bio);
@@ -513,7 +513,7 @@ X509 *php_openssl_x509_from_str(
 	BIO *in;
 
 	if (ZSTR_LEN(cert_str) > 7 && memcmp(ZSTR_VAL(cert_str), "file://", sizeof("file://") - 1) == 0) {
-		if (!php_openssl_check_path_str_ex(cert_str, cert_path, arg_num, true, is_from_array, option_name)) {
+		if (!php_openssl_check_path_str_ex(cert_str, cert_path, arg_num, true, is_from_array, option_name, NULL)) {
 			return NULL;
 		}
 
@@ -582,7 +582,7 @@ X509 *php_openssl_x509_from_zval(
 	return cert;
 }
 
-zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool raw)
+zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool raw, php_stream *stream)
 {
 	unsigned char md[EVP_MAX_MD_SIZE];
 	const EVP_MD *mdtype;
@@ -590,12 +590,20 @@ zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool r
 	zend_string *ret;
 
 	if (!(mdtype = php_openssl_get_evp_md_by_name(method))) {
-		php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
+		if (stream != NULL) {
+			php_stream_warn(stream, Generic, "Unknown digest algorithm");
+		} else {
+			php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
+		}
 		return NULL;
 	} else if (!X509_digest(peer, mdtype, md, &n)) {
 		php_openssl_release_evp_md(mdtype);
 		php_openssl_store_errors();
-		php_error_docref(NULL, E_ERROR, "Could not generate signature");
+		if (stream != NULL) {
+			php_stream_warn(stream, EncodingFailed, "Could not generate signature");
+		} else {
+			php_error_docref(NULL, E_WARNING, "Could not generate signature");
+		}
 		return NULL;
 	}
 
@@ -792,7 +800,7 @@ X509_STORE *php_openssl_setup_verify(zval *calist, uint32_t arg_num)
 				return NULL;
 			}
 
-			if (!php_openssl_check_path_str_ex(str, file_path, arg_num, false, true, NULL)) {
+			if (!php_openssl_check_path_str_ex(str, file_path, arg_num, false, true, NULL, NULL)) {
 				zend_string_release(str);
 				continue;
 			}
diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h
index 20a04bbcd1a0..b88a6c59e4e9 100644
--- a/ext/openssl/php_openssl.h
+++ b/ext/openssl/php_openssl.h
@@ -102,34 +102,39 @@ void php_openssl_store_errors(void);
 void php_openssl_errors_set_mark(void);
 void php_openssl_errors_restore_mark(void);
 
-/* openssl file path extra */
+/* openssl file path extra
+ * When a non-NULL stream is passed and the path check fails for a stream context option
+ * (arg_num == 0), the warning is reported via the stream error API so it participates in
+ * any active stream error operation; otherwise it is emitted immediately. */
 bool php_openssl_check_path_ex(
 		const char *file_path, size_t file_path_len, char *real_path, uint32_t arg_num,
-		bool contains_file_protocol, bool is_from_array, const char *option_name);
+		bool contains_file_protocol, bool is_from_array, const char *option_name,
+		struct _php_stream *stream);
 
 /* openssl file path check */
 static inline bool php_openssl_check_path(
 		const char *file_path, size_t file_path_len, char *real_path, uint32_t arg_num)
 {
 	return php_openssl_check_path_ex(
-			file_path, file_path_len, real_path, arg_num, false, false, NULL);
+			file_path, file_path_len, real_path, arg_num, false, false, NULL, NULL);
 }
 
 /* openssl file path extra check with zend string */
 static inline bool php_openssl_check_path_str_ex(
 		zend_string *file_path, char *real_path, uint32_t arg_num,
-		bool contains_file_protocol, bool is_from_array, const char *option_name)
+		bool contains_file_protocol, bool is_from_array, const char *option_name,
+		struct _php_stream *stream)
 {
 	return php_openssl_check_path_ex(
 			ZSTR_VAL(file_path), ZSTR_LEN(file_path), real_path, arg_num, contains_file_protocol,
-			is_from_array, option_name);
+			is_from_array, option_name, stream);
 }
 
 /* openssl file path check with zend string */
 static inline bool php_openssl_check_path_str(
 		zend_string *file_path, char *real_path, uint32_t arg_num)
 {
-	return php_openssl_check_path_str_ex(file_path, real_path, arg_num, true, false, NULL);
+	return php_openssl_check_path_str_ex(file_path, real_path, arg_num, true, false, NULL, NULL);
 }
 
 PHP_OPENSSL_API zend_long php_openssl_cipher_iv_length(const char *method);
diff --git a/ext/openssl/php_openssl_backend.h b/ext/openssl/php_openssl_backend.h
index 2d37e594ea57..bd12a5fe312f 100644
--- a/ext/openssl/php_openssl_backend.h
+++ b/ext/openssl/php_openssl_backend.h
@@ -262,7 +262,8 @@ X509 *php_openssl_x509_from_param(
 X509 *php_openssl_x509_from_zval(
 		zval *val, bool *free_cert, uint32_t arg_num, bool is_from_array, const char *option_name);
 
-zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool raw);
+zend_string* php_openssl_x509_fingerprint(
+		X509 *peer, const char *method, bool raw, struct _php_stream *stream);
 
 int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension);
 
diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc
index c5db41d48417..b7e866954fcf 100644
--- a/ext/openssl/tests/ServerClientTestCase.inc
+++ b/ext/openssl/tests/ServerClientTestCase.inc
@@ -129,6 +129,10 @@ class ServerClientTestCase
     private function cleanupWorkerProcess($worker)
     {
         fclose($this->workerStdIn[$worker]);
+        /* Drain stdout to EOF before closing it, so late worker writes (e.g.
+         * buffered error output) don't fail and abort the worker mid-output. */
+        stream_set_blocking($this->workerStdOut[$worker], true);
+        stream_get_contents($this->workerStdOut[$worker]);
         fclose($this->workerStdOut[$worker]);
         proc_close($this->workerHandle[$worker]);
     }
diff --git a/ext/openssl/tests/bug68920.phpt b/ext/openssl/tests/bug68920.phpt
index 4abe12586b0b..a2d669e5b7d7 100644
--- a/ext/openssl/tests/bug68920.phpt
+++ b/ext/openssl/tests/bug68920.phpt
@@ -76,8 +76,6 @@ bool(false)
 
 Warning: stream_socket_client(): Invalid peer_fingerprint array; [algo => fingerprint] form required in %s on line %d
 
-Warning: stream_socket_client(): peer_fingerprint match failure in %s on line %d
-
 Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
 
 Warning: stream_socket_client(): Unable to connect to %s (Unknown error) in %s on line %d
@@ -85,8 +83,6 @@ bool(false)
 
 Warning: stream_socket_client(): Invalid peer_fingerprint array; [algo => fingerprint] form required in %s on line %d
 
-Warning: stream_socket_client(): peer_fingerprint match failure in %s on line %d
-
 Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
 
 Warning: stream_socket_client(): Unable to connect to %s (Unknown error) in %s on line %d
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index a154aa4572f7..bc483df617b1 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -145,7 +145,8 @@
 #endif
 
 extern php_stream* php_openssl_get_stream_from_ssl_handle(const SSL *ssl);
-extern zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool raw);
+extern zend_string* php_openssl_x509_fingerprint(
+		X509 *peer, const char *method, bool raw, php_stream *stream);
 extern int php_openssl_get_ssl_stream_data_index(void);
 static struct timeval php_openssl_subtract_timeval(struct timeval a, struct timeval b);
 static int php_openssl_compare_timeval(struct timeval a, struct timeval b);
@@ -281,16 +282,14 @@ static int php_openssl_handle_ssl_error(php_stream *stream, int nr_bytes, bool i
 			if (ERR_peek_error() == 0) {
 				if (nr_bytes == 0) {
 					if (!php_openssl_is_http_stream_talking_to_iis(stream) && ERR_get_error() != 0) {
-						php_error_docref(NULL, E_WARNING, "SSL: fatal protocol error");
+						php_stream_warn(stream, ProtocolError, "SSL: fatal protocol error");
 					}
 					SSL_set_shutdown(sslsock->ssl_handle, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
 					stream->eof = 1;
 					retry = false;
 				} else {
 					char *estr = php_socket_strerror(php_socket_errno(), NULL, 0);
-
-					php_error_docref(NULL, E_WARNING,
-							"SSL: %s", estr);
+					php_stream_warn(stream, ProtocolError, "SSL: %s", estr);
 
 					efree(estr);
 					retry = false;
@@ -305,7 +304,7 @@ static int php_openssl_handle_ssl_error(php_stream *stream, int nr_bytes, bool i
 
 			switch (ERR_GET_REASON(ecode)) {
 				case SSL_R_NO_SHARED_CIPHER:
-					php_error_docref(NULL, E_WARNING,
+					php_stream_warn(stream, ProtocolError,
 							"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)");
@@ -324,7 +323,7 @@ static int php_openssl_handle_ssl_error(php_stream *stream, int nr_bytes, bool i
 
 					smart_str_0(&ebuf);
 
-					php_error_docref(NULL, E_WARNING,
+					php_stream_warn(stream, ProtocolError,
 							"SSL operation failed with code %d. %s%s",
 							err,
 							ebuf.s ? "OpenSSL Error messages:\n" : "",
@@ -379,21 +378,19 @@ static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) /* {{{ */
 }
 /* }}} */
 
-static int php_openssl_x509_fingerprint_cmp(X509 *peer, const char *method, const char *expected)
+static bool php_openssl_x509_fingerprint_is_equal(php_stream *stream, X509 *peer, const char *method, const zend_string *expected)
 {
-	zend_string *fingerprint;
-	int result = -1;
-
-	fingerprint = php_openssl_x509_fingerprint(peer, method, false);
+	bool is_equal = false;
+	zend_string *fingerprint = php_openssl_x509_fingerprint(peer, method, false, stream);
 	if (fingerprint) {
-		result = strcasecmp(expected, ZSTR_VAL(fingerprint));
-		zend_string_release_ex(fingerprint, 0);
+		is_equal = zend_string_equals_ci(fingerprint, expected);
+		zend_string_release_ex(fingerprint, false);
 	}
 
-	return result;
+	return is_equal;
 }
 
-static bool php_openssl_x509_fingerprint_match(X509 *peer, zval *val)
+static bool php_openssl_x509_fingerprint_match(php_stream *stream, X509 *peer, const zval *val)
 {
 	if (Z_TYPE_P(val) == IS_STRING) {
 		const char *method = NULL;
@@ -408,29 +405,41 @@ static bool php_openssl_x509_fingerprint_match(X509 *peer, zval *val)
 				break;
 		}
 
-		return method && php_openssl_x509_fingerprint_cmp(peer, method, Z_STRVAL_P(val)) == 0;
+		if (UNEXPECTED(method == NULL)) {
+			php_stream_warn(stream, AuthFailed, "peer_fingerprint length doesn't match a md5 or sha1 hash");
+			return false;
+		}
+		if (!php_openssl_x509_fingerprint_is_equal(stream, peer, method, Z_STR_P(val))) {
+			php_stream_warn(stream, AuthFailed, "peer_fingerprint match failure");
+			return false;
+		}
+		return true;
 	} else if (Z_TYPE_P(val) == IS_ARRAY) {
 		zval *current;
 		zend_string *key;
 
 		if (!zend_hash_num_elements(Z_ARRVAL_P(val))) {
-			php_error_docref(NULL, E_WARNING, "Invalid peer_fingerprint array; [algo => fingerprint] form required");
+			// TODO: Should this be a ValueError (also must not be empty error)?
+			php_stream_warn(stream, Generic, "Invalid peer_fingerprint array; [algo => fingerprint] form required");
 			return false;
 		}
 
 		ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(val), key, current) {
 			if (key == NULL || Z_TYPE_P(current) != IS_STRING) {
-				php_error_docref(NULL, E_WARNING, "Invalid peer_fingerprint array; [algo => fingerprint] form required");
+				// TODO: Should this be a ValueError?
+				php_stream_warn(stream, Generic, "Invalid peer_fingerprint array; [algo => fingerprint] form required");
 				return false;
 			}
-			if (php_openssl_x509_fingerprint_cmp(peer, ZSTR_VAL(key), Z_STRVAL_P(current)) != 0) {
+			if (!php_openssl_x509_fingerprint_is_equal(stream, peer, ZSTR_VAL(key), Z_STR_P(current))) {
+				php_stream_warn(stream, AuthFailed, "peer_fingerprint match failure");
 				return false;
 			}
 		} ZEND_HASH_FOREACH_END();
 
 		return true;
 	} else {
-		php_error_docref(NULL, E_WARNING,
+		// TODO: Should this be a TypeError?
+		php_stream_warn(stream, Generic,
 			"Invalid peer_fingerprint value; fingerprint string or array of the form [algo => fingerprint] required");
 	}
 
@@ -555,7 +564,7 @@ static bool php_openssl_matches_san_list(X509 *peer, const char *subject_name) /
 }
 /* }}} */
 
-static bool php_openssl_matches_common_name(X509 *peer, const char *subject_name) /* {{{ */
+static bool php_openssl_matches_common_name(php_stream *stream, const X509 *peer, const char *subject_name) /* {{{ */
 {
 	char buf[1024];
 	X509_NAME *cert_name;
@@ -566,13 +575,13 @@ static bool php_openssl_matches_common_name(X509 *peer, const char *subject_name
 	cert_name_len = X509_NAME_get_text_by_NID(cert_name, NID_commonName, buf, sizeof(buf));
 
 	if (cert_name_len == -1) {
-		php_error_docref(NULL, E_WARNING, "Unable to locate peer certificate CN");
+		php_stream_warn(stream, NetworkRecvFailed, "Unable to locate peer certificate CN");
 	} else if ((size_t)cert_name_len != strlen(buf)) {
-		php_error_docref(NULL, E_WARNING, "Peer certificate CN=`%.*s' is malformed", cert_name_len, buf);
+		php_stream_warn(stream, AuthFailed, "Peer certificate CN=`%.*s' is malformed", cert_name_len, buf);
 	} else if (php_openssl_matches_wildcard_name(subject_name, buf)) {
 		is_match = true;
 	} else {
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, AuthFailed,
 			"Peer certificate CN=`%.*s' did not match expected CN=`%s'",
 			cert_name_len, buf, subject_name);
 	}
@@ -605,7 +614,7 @@ static zend_result php_openssl_apply_peer_verification_policy(SSL *ssl, X509 *pe
 	peer_fingerprint = val;
 
 	if ((must_verify_peer || must_verify_peer_name || must_verify_fingerprint) && peer == NULL) {
-		php_error_docref(NULL, E_WARNING, "Could not get peer certificate");
+		php_stream_warn(stream, NetworkRecvFailed, "Could not get peer certificate");
 		return FAILURE;
 	}
 
@@ -624,8 +633,7 @@ static zend_result php_openssl_apply_peer_verification_policy(SSL *ssl, X509 *pe
 				/* not allowed, so fall through */
 				ZEND_FALLTHROUGH;
 			default:
-				php_error_docref(NULL, E_WARNING,
-						"Could not verify peer: code:%d %s",
+				php_stream_warn(stream, AuthFailed, "Could not verify peer: code:%d %s",
 						err,
 						X509_verify_cert_error_string(err)
 				);
@@ -636,14 +644,11 @@ static zend_result php_openssl_apply_peer_verification_policy(SSL *ssl, X509 *pe
 	/* If a peer_fingerprint match is required this trumps peer and peer_name verification */
 	if (must_verify_fingerprint) {
 		if (Z_TYPE_P(peer_fingerprint) == IS_STRING || Z_TYPE_P(peer_fingerprint) == IS_ARRAY) {
-			if (!php_openssl_x509_fingerprint_match(peer, peer_fingerprint)) {
-				php_error_docref(NULL, E_WARNING,
-					"peer_fingerprint match failure"
-				);
+			if (!php_openssl_x509_fingerprint_match(stream, peer, peer_fingerprint)) {
 				return FAILURE;
 			}
 		} else {
-			php_error_docref(NULL, E_WARNING,
+			php_stream_warn(stream, AuthFailed,
 				"Expected peer fingerprint must be a string or an array"
 			);
 			return FAILURE;
@@ -662,7 +667,7 @@ static zend_result php_openssl_apply_peer_verification_policy(SSL *ssl, X509 *pe
 		if (peer_name) {
 			if (php_openssl_matches_san_list(peer, peer_name)) {
 				return SUCCESS;
-			} else if (php_openssl_matches_common_name(peer, peer_name)) {
+			} else if (php_openssl_matches_common_name(stream, peer, peer_name)) {
 				return SUCCESS;
 			} else {
 				return FAILURE;
@@ -725,7 +730,8 @@ static int php_openssl_win_cert_verify_callback(X509_STORE_CTX *x509_store_ctx,
 				err_code = e;
 			}
 
-			php_error_docref(NULL, E_WARNING, "Error encoding X509 certificate: %lu: %s", err_code, ERR_error_string(err_code, err_buf));
+			php_stream_warn(stream, EncodingFailed,
+				"Error encoding X509 certificate: %lu: %s", err_code, ERR_error_string(err_code, err_buf));
 			RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
 		}
 
@@ -734,7 +740,7 @@ static int php_openssl_win_cert_verify_callback(X509_STORE_CTX *x509_store_ctx,
 
 		if (cert_ctx == NULL) {
 			char *err = php_win_err();
-			php_error_docref(NULL, E_WARNING, "Error creating certificate context: %s", err);
+			php_stream_warn(stream, CreateFailed, "Error creating certificate context: %s", err);
 			php_win_err_free(err);
 			RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
 		}
@@ -758,7 +764,7 @@ static int php_openssl_win_cert_verify_callback(X509_STORE_CTX *x509_store_ctx,
 
 		if (!CertGetCertificateChain(NULL, cert_ctx, NULL, NULL, &chain_params, chain_flags, NULL, &cert_chain_ctx)) {
 			char *err = php_win_err();
-			php_error_docref(NULL, E_WARNING, "Error getting certificate chain: %s", err);
+			php_stream_warn(stream, NetworkRecvFailed, "Error getting certificate chain: %s", err);
 			php_win_err_free(err);
 			CertFreeCertificateContext(cert_ctx);
 			RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
@@ -801,7 +807,7 @@ static int php_openssl_win_cert_verify_callback(X509_STORE_CTX *x509_store_ctx,
 
 		if (!verify_result) {
 			char *err = php_win_err();
-			php_error_docref(NULL, E_WARNING, "Error verifying certificate chain policy: %s", err);
+			php_stream_warn(stream, AuthFailed, "Error verifying certificate chain policy: %s", err);
 			php_win_err_free(err);
 			RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
 		}
@@ -836,11 +842,12 @@ static long php_openssl_load_stream_cafile(X509_STORE *cert_store, const char *c
 	stream = php_stream_open_wrapper(cafile, "rb", 0, NULL);
 
 	if (stream == NULL) {
+		// TODO: no stream and no wrapper, cannot use php_stream_warn(stream, ReadFailed, ...) nor php_stream_wrapper_log_error()
 		php_error(E_WARNING, "failed loading cafile stream: `%s'", cafile);
 		return 0;
 	} else if (stream->wrapper->is_url) {
+		php_stream_warn(stream, PermissionDenied, "remote cafile streams are disabled for security purposes");
 		php_stream_close(stream);
-		php_error(E_WARNING, "remote cafile streams are disabled for security purposes");
 		return 0;
 	}
 
@@ -898,7 +905,7 @@ static long php_openssl_load_stream_cafile(X509_STORE *cert_store, const char *c
 	}
 
 	if (certs_added == 0) {
-		php_error(E_WARNING, "no valid certs found cafile stream: `%s'", cafile);
+		php_stream_warn(stream, DecodingFailed, "no valid certs found cafile stream: `%s'", cafile);
 	}
 
 	return certs_added;
@@ -924,7 +931,7 @@ static zend_result php_openssl_enable_peer_verification(SSL_CTX *ctx, php_stream
 		if (cert_names != NULL) {
 			SSL_CTX_set_client_CA_list(ctx, cert_names);
 		} else {
-			php_error(E_WARNING, "SSL: failed loading CA names from cafile");
+			php_stream_warn(stream, InvalidFormat, "SSL: failed loading CA names from cafile");
 			return FAILURE;
 		}
 	}
@@ -946,7 +953,7 @@ static zend_result php_openssl_enable_peer_verification(SSL_CTX *ctx, php_stream
 		SSL_CTX_set_cert_verify_callback(ctx, php_openssl_win_cert_verify_callback, (void *)stream);
 #else
 		if (sslsock->is_client && !SSL_CTX_set_default_verify_paths(ctx)) {
-			php_error_docref(NULL, E_WARNING,
+			php_stream_warn(stream, ProtocolError,
 				"Unable to set default verify locations and no CA settings specified");
 			return FAILURE;
 		}
@@ -980,13 +987,13 @@ static zend_result php_openssl_set_local_cert(SSL_CTX *ctx, php_stream *stream)
 
 		if (!php_openssl_check_path_ex(
 				certfile, certfile_len, resolved_path_buff, 0, false, false,
-				"local_cert in ssl stream context")) {
-			php_error_docref(NULL, E_WARNING, "Unable to get real path of certificate file `%s'", certfile);
+				"local_cert in ssl stream context", stream)) {
+			php_stream_warn(stream, NotFound, "Unable to get real path of certificate file `%s'", certfile);
 			return FAILURE;
 		}
 		/* a certificate to use for authentication */
 		if (SSL_CTX_use_certificate_chain_file(ctx, resolved_path_buff) != 1) {
-			php_error_docref(NULL, E_WARNING,
+			php_stream_warn(stream, WriteFailed,
 				"Unable to set local cert chain file `%s'; Check that your cafile/capath "
 				"settings include details of your certificate and its issuer",
 				certfile);
@@ -996,16 +1003,16 @@ static zend_result php_openssl_set_local_cert(SSL_CTX *ctx, php_stream *stream)
 		GET_VER_OPT_STRINGL("local_pk", private_key, private_key_len);
 		if (private_key && !php_openssl_check_path_ex(
 				private_key, private_key_len, resolved_path_buff, 0, false, false,
-				"local_pk in ssl stream context")) {
-			php_error_docref(NULL, E_WARNING, "Unable to get real path of private key file `%s'", private_key);
+				"local_pk in ssl stream context", stream)) {
+			php_stream_warn(stream, NotFound, "Unable to get real path of private key file `%s'", private_key);
 			return FAILURE;
 		}
 		if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff, SSL_FILETYPE_PEM) != 1) {
-			php_error_docref(NULL, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff);
+			php_stream_warn(stream, WriteFailed, "Unable to set private key file `%s'", resolved_path_buff);
 			return FAILURE;
 		}
 		if (!SSL_CTX_check_private_key(ctx)) {
-			php_error_docref(NULL, E_WARNING, "Private key does not match certificate!");
+			php_stream_warn(stream, PermissionDenied, "Private key does not match certificate!");
 		}
 	}
 
@@ -1140,7 +1147,7 @@ static void php_openssl_limit_handshake_reneg(const SSL *ssl) /* {{{ */
 			/* Closing the stream inside this callback would segfault! */
 			stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
 			if (FAILURE == call_user_function(NULL, NULL, val, &retval, 1, &param)) {
-				php_error(E_WARNING, "SSL: failed invoking reneg limit notification callback");
+				php_stream_warn(stream, UserspaceCallFailed, "SSL: failed invoking reneg limit notification callback");
 			}
 			stream->flags ^= PHP_STREAM_FLAG_NO_FCLOSE;
 
@@ -1151,7 +1158,7 @@ static void php_openssl_limit_handshake_reneg(const SSL *ssl) /* {{{ */
 
 			zval_ptr_dtor(&retval);
 		} else {
-			php_error_docref(NULL, E_WARNING,
+			php_stream_warn(stream, ProtocolError,
 				"SSL: client-initiated handshake rate limit exceeded by peer");
 		}
 	}
@@ -1224,7 +1231,7 @@ static zend_result php_openssl_set_server_dh_param(php_stream * stream, SSL_CTX
 	BIO *bio = BIO_new_file(Z_STRVAL_P(zdhpath), PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
 
 	if (bio == NULL) {
-		php_error_docref(NULL, E_WARNING, "Invalid dh_param");
+		php_stream_warn(stream, NotFound, "Invalid dh_param");
 		return FAILURE;
 	}
 
@@ -1233,12 +1240,12 @@ static zend_result php_openssl_set_server_dh_param(php_stream * stream, SSL_CTX
 	BIO_free(bio);
 
 	if (pkey == NULL) {
-		php_error_docref(NULL, E_WARNING, "Failed reading DH params");
+		php_stream_warn(stream, ReadFailed, "Failed reading DH params");
 		return FAILURE;
 	}
 
 	if (SSL_CTX_set0_tmp_dh_pkey(ctx, pkey) == 0) {
-		php_error_docref(NULL, E_WARNING, "Failed assigning DH params");
+		php_stream_warn(stream, WriteFailed, "Failed assigning DH params");
 		EVP_PKEY_free(pkey);
 		return FAILURE;
 	}
@@ -1247,12 +1254,12 @@ static zend_result php_openssl_set_server_dh_param(php_stream * stream, SSL_CTX
 	BIO_free(bio);
 
 	if (dh == NULL) {
-		php_error_docref(NULL, E_WARNING, "Failed reading DH params");
+		php_stream_warn(stream, ReadFailed, "Failed reading DH params");
 		return FAILURE;
 	}
 
 	if (SSL_CTX_set_tmp_dh(ctx, dh) == 0) {
-		php_error_docref(NULL, E_WARNING, "Failed assigning DH params");
+		php_stream_warn(stream, WriteFailed, "Failed assigning DH params");
 		DH_free(dh);
 		return FAILURE;
 	}
@@ -1271,7 +1278,8 @@ static zend_result php_openssl_set_server_specific_opts(php_stream *stream, SSL_
 
 	/* We now use php_openssl_tmp_rsa_cb to generate a key of appropriate size whenever necessary */
 	if (php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "rsa_key_size") != NULL) {
-		php_error_docref(NULL, E_WARNING, "rsa_key_size context option has been removed");
+		// TODO: Should this be E_DEPRECATED instead?
+		php_stream_warn(stream, Generic, "rsa_key_size context option has been removed");
 	}
 
 	if (php_openssl_set_server_dh_param(stream, ctx) == FAILURE) {
@@ -1326,18 +1334,18 @@ static int php_openssl_server_sni_callback(SSL *ssl_handle, int *al, void *arg)
 }
 /* }}} */
 
-static SSL_CTX *php_openssl_create_sni_server_ctx(char *cert_path, char *key_path)  /* {{{ */
+static SSL_CTX *php_openssl_create_sni_server_ctx(php_stream *stream, char *cert_path, char *key_path)  /* {{{ */
 {
 	/* The hello method is not inherited by SSL structs when assigning a new context
 	 * inside the SNI callback, so the just use SSLv23 */
 	SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
 	if (!ctx) {
-		php_error_docref(NULL, E_WARNING, "Failed to create the SSL context");
+		php_stream_warn(stream, CreateFailed, "Failed to create the SSL context");
 		return NULL;
 	}
 
 	if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) != 1) {
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, ReadFailed,
 			"Failed setting local cert chain file `%s'; " \
 			"check that your cafile/capath settings include " \
 			"details of your certificate and its issuer",
@@ -1346,7 +1354,7 @@ static SSL_CTX *php_openssl_create_sni_server_ctx(char *cert_path, char *key_pat
 		SSL_CTX_free(ctx);
 		return NULL;
 	} else if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) != 1) {
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, ReadFailed,
 			"Failed setting private key from file `%s'",
 			key_path
 		);
@@ -1380,7 +1388,8 @@ static zend_result php_openssl_enable_server_sni(
 	}
 
 	if (Z_TYPE_P(val) != IS_ARRAY) {
-		php_error_docref(NULL, E_WARNING,
+		// TODO: should this be a TypeError?
+		php_stream_warn(stream, Generic,
 			"SNI_server_certs requires an array mapping host names to cert paths"
 		);
 		return FAILURE;
@@ -1388,7 +1397,8 @@ static zend_result php_openssl_enable_server_sni(
 
 	sslsock->sni_cert_count = zend_hash_num_elements(Z_ARRVAL_P(val));
 	if (sslsock->sni_cert_count == 0) {
-		php_error_docref(NULL, E_WARNING,
+		// TODO: should this be a ValueError?
+		php_stream_warn(stream, Generic,
 			"SNI_server_certs host cert array must not be empty"
 		);
 		return FAILURE;
@@ -1403,7 +1413,8 @@ static zend_result php_openssl_enable_server_sni(
 		(void) key_index;
 
 		if (!key) {
-			php_error_docref(NULL, E_WARNING,
+			// TODO: should this be a ValueError?
+			php_stream_warn(stream, Generic,
 				"SNI_server_certs array requires string host name keys"
 			);
 			return FAILURE;
@@ -1418,7 +1429,8 @@ static zend_result php_openssl_enable_server_sni(
 
 			local_cert = zend_hash_str_find(Z_ARRVAL_P(current), "local_cert", sizeof("local_cert")-1);
 			if (local_cert == NULL) {
-				php_error_docref(NULL, E_WARNING,
+				// TODO: should this be a ValueError?
+				php_stream_warn(stream, Generic,
 					"local_cert not present in the array"
 				);
 				return FAILURE;
@@ -1430,8 +1442,8 @@ static zend_result php_openssl_enable_server_sni(
 			}
 			if (!php_openssl_check_path_str_ex(
 					local_cert_str, resolved_cert_path_buff, 0, false, false,
-					"SNI_server_certs local_cert in ssl stream context")) {
-				php_error_docref(NULL, E_WARNING,
+					"SNI_server_certs local_cert in ssl stream context", stream)) {
+				php_stream_warn(stream, OpenFailed,
 					"Failed setting local cert chain file `%s'; could not open file",
 					ZSTR_VAL(local_cert_str)
 				);
@@ -1442,7 +1454,8 @@ static zend_result php_openssl_enable_server_sni(
 
 			local_pk = zend_hash_str_find(Z_ARRVAL_P(current), "local_pk", sizeof("local_pk")-1);
 			if (local_pk == NULL) {
-				php_error_docref(NULL, E_WARNING,
+				// TODO: should this be a ValueError?
+				php_stream_warn(stream, Generic,
 					"local_pk not present in the array"
 				);
 				return FAILURE;
@@ -1454,8 +1467,8 @@ static zend_result php_openssl_enable_server_sni(
 			}
 			if (!php_openssl_check_path_str_ex(
 					local_pk_str, resolved_pk_path_buff, 0, false, false,
-					"SNI_server_certs local_pk in ssl stream context")) {
-				php_error_docref(NULL, E_WARNING,
+					"SNI_server_certs local_pk in ssl stream context", stream)) {
+				php_stream_warn(stream, OpenFailed,
 					"Failed setting local private key file `%s';  could not open file",
 					ZSTR_VAL(local_pk_str)
 				);
@@ -1464,18 +1477,19 @@ static zend_result php_openssl_enable_server_sni(
 			}
 			zend_string_release(local_pk_str);
 
-			ctx = php_openssl_create_sni_server_ctx(resolved_cert_path_buff, resolved_pk_path_buff);
+			ctx = php_openssl_create_sni_server_ctx(stream, resolved_cert_path_buff, resolved_pk_path_buff);
 		} else if (Z_TYPE_P(current) == IS_STRING) {
-			if (php_openssl_check_path_str_ex(Z_STR_P(current), resolved_path_buff, 0, false, false, "SNI_server_certs in ssl stream context")) {
-				ctx = php_openssl_create_sni_server_ctx(resolved_path_buff, resolved_path_buff);
+			if (php_openssl_check_path_str_ex(Z_STR_P(current), resolved_path_buff, 0, false, false, "SNI_server_certs in ssl stream context", stream)) {
+				ctx = php_openssl_create_sni_server_ctx(stream, resolved_path_buff, resolved_path_buff);
 			} else {
-				php_error_docref(NULL, E_WARNING,
+				php_stream_warn(stream, NotFound,
 						"Failed setting local cert chain file `%s'; file not found",
 						Z_STRVAL_P(current)
 				);
 			}
 		} else {
-			php_error_docref(NULL, E_WARNING, "SNI_server_certs options values must be of type array|string");
+			// TODO: should this be a TypeError?
+			php_stream_warn(stream, Generic, "SNI_server_certs options values must be of type array|string");
 		}
 
 		if (ctx == NULL) {
@@ -1902,12 +1916,13 @@ static int php_openssl_psk_find_session_cb(SSL *ssl, const unsigned char *identi
 /* PSK setup */
 
 static zend_result php_openssl_validate_and_allocate_psk_callback(
+		php_stream *stream,
 		php_openssl_netstream_data_t *sslsock, const zval *callable,
 		bool is_client, bool is_persistent)
 {
 	const char *callback_name = is_client ? "psk_client_cb" : "psk_server_cb";
 	if (is_persistent) {
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, PersistentNotSupported,
 				"%s is not supported for persistent streams", callback_name);
 		return FAILURE;
 	}
@@ -1950,7 +1965,7 @@ static zend_result php_openssl_setup_client_psk(php_stream *stream,
 	}
 
 	if (FAILURE == php_openssl_validate_and_allocate_psk_callback(
-			sslsock, val, true, php_stream_is_persistent(stream))) {
+			stream, sslsock, val, true, php_stream_is_persistent(stream))) {
 		return FAILURE;
 	}
 
@@ -1972,7 +1987,7 @@ static zend_result php_openssl_setup_server_psk(php_stream *stream,
 	}
 
 	if (FAILURE == php_openssl_validate_and_allocate_psk_callback(
-			sslsock, val, false, php_stream_is_persistent(stream))) {
+			stream, sslsock, val, false, php_stream_is_persistent(stream))) {
 		return FAILURE;
 	}
 
@@ -2103,6 +2118,7 @@ enum php_openssl_session_callback_type {
  * Validate callable and allocate callback structure if needed.
  */
 static zend_result php_openssl_validate_and_allocate_session_callback(
+		php_stream *stream,
 		php_openssl_netstream_data_t *sslsock, const zval *callable,
 		enum php_openssl_session_callback_type cb_type, bool is_persistent)
 {
@@ -2123,7 +2139,7 @@ static zend_result php_openssl_validate_and_allocate_session_callback(
 
 	/* Callbacks not supported for persistent streams */
 	if (is_persistent) {
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, PersistentNotSupported,
 				"%s is not supported for persistent streams", callback_name);
 		return FAILURE;
 	}
@@ -2184,7 +2200,7 @@ static zend_result php_openssl_setup_client_session(php_stream *stream,
 
 	if (GET_VER_OPT("session_new_cb")) {
 		if (FAILURE == php_openssl_validate_and_allocate_session_callback(
-				sslsock, val, PHP_OPENSSL_NEW_CB, is_persistent)) {
+				stream, sslsock, val, PHP_OPENSSL_NEW_CB, is_persistent)) {
 			return FAILURE;
 		}
 
@@ -2231,7 +2247,7 @@ static zend_result php_openssl_setup_server_session(php_stream *stream,
 	/* Check for session_get_cb first (determines cache mode) */
 	if (GET_VER_OPT("session_get_cb")) {
 		if (FAILURE == php_openssl_validate_and_allocate_session_callback(
-				sslsock, val, PHP_OPENSSL_GET_CB, is_persistent)) {
+				stream, sslsock, val, PHP_OPENSSL_GET_CB, is_persistent)) {
 			return FAILURE;
 		}
 		has_get_cb = true;
@@ -2250,7 +2266,7 @@ static zend_result php_openssl_setup_server_session(php_stream *stream,
 	/* Check for session_new_cb */
 	if (GET_VER_OPT("session_new_cb")) {
 		if (FAILURE == php_openssl_validate_and_allocate_session_callback(
-				sslsock, val, PHP_OPENSSL_NEW_CB, is_persistent)) {
+				stream, sslsock, val, PHP_OPENSSL_NEW_CB, is_persistent)) {
 			return FAILURE;
 		}
 		has_new_cb = true;
@@ -2271,7 +2287,7 @@ static zend_result php_openssl_setup_server_session(php_stream *stream,
 	/* Check for session_remove_cb (optional) */
 	if (GET_VER_OPT("session_remove_cb")) {
 		if (FAILURE == php_openssl_validate_and_allocate_session_callback(
-				sslsock, val, PHP_OPENSSL_REMOVE_CB, is_persistent)) {
+				stream, sslsock, val, PHP_OPENSSL_REMOVE_CB, is_persistent)) {
 			return FAILURE;
 		}
 
@@ -2356,7 +2372,8 @@ static zend_result php_openssl_apply_client_session_data(php_stream *stream,
 		if (php_openssl_is_session_ce(val)) {
 			session = php_openssl_session_from_zval(val);
 			if (!session) {
-				php_error_docref(NULL, E_WARNING,
+				// TODO: Should this be a TypeError?
+				php_stream_warn(stream, Generic,
 						"Invalid OpenSSLSession object, falling back to full handshake");
 				return FAILURE;
 			}
@@ -2369,7 +2386,7 @@ static zend_result php_openssl_apply_client_session_data(php_stream *stream,
 
 		if (session) {
 			if (SSL_set_session(sslsock->ssl_handle, session) != 1) {
-				php_error_docref(NULL, E_WARNING,
+				php_stream_warn(stream, ResumptionFailed,
 						"Failed to set session for resumption, falling back to full handshake");
 				if (needs_free) {
 					SSL_SESSION_free(session);
@@ -2396,7 +2413,7 @@ static zend_result php_openssl_create_server_ctx(php_stream *stream,
 	sslsock->ctx = SSL_CTX_new(method);
 
 	if (sslsock->ctx == NULL) {
-		php_error_docref(NULL, E_WARNING, "SSL context creation failure");
+		php_stream_warn(stream, CreateFailed, "SSL context creation failure");
 		return FAILURE;
 	}
 
@@ -2462,7 +2479,8 @@ static zend_result php_openssl_create_server_ctx(php_stream *stream,
 	if (GET_VER_OPT("security_level")) {
 		zend_long lval = zval_get_long(val);
 		if (lval < 0 || lval > 5) {
-			php_error_docref(NULL, E_WARNING, "Security level must be between 0 and 5");
+			// TODO: Should this be a ValueError?
+			php_stream_warn(stream, Generic, "Security level must be between 0 and 5");
 		}
 #ifdef HAVE_SEC_LEVEL
 		SSL_CTX_set_security_level(sslsock->ctx, lval);
@@ -2478,7 +2496,7 @@ static zend_result php_openssl_create_server_ctx(php_stream *stream,
 			unsigned char *alpn = php_openssl_alpn_protos_parse(&alpn_len, alpn_protocols);
 
 			if (alpn == NULL) {
-				php_error_docref(NULL, E_WARNING, "Failed parsing comma-separated TLS ALPN protocol string");
+				php_stream_warn(stream, DecodingFailed, "Failed parsing comma-separated TLS ALPN protocol string");
 				SSL_CTX_free(sslsock->ctx);
 				sslsock->ctx = NULL;
 				return FAILURE;
@@ -2494,7 +2512,7 @@ static zend_result php_openssl_create_server_ctx(php_stream *stream,
 			efree(alpn);
 		}
 #else
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, ProtocolUnsupported,
 			"alpn_protocols support is not compiled into the OpenSSL library against which PHP is linked");
 #endif
 	}
@@ -2544,7 +2562,7 @@ static zend_result php_openssl_setup_crypto(php_stream *stream,
 {
 	if (sslsock->ssl_handle) {
 		if (sslsock->s.is_blocked) {
-			php_error_docref(NULL, E_WARNING, "SSL/TLS already set-up for this stream");
+			php_stream_warn(stream, Generic, "SSL/TLS already set-up for this stream");
 			return FAILURE;
 		} else {
 			return SUCCESS;
@@ -2563,11 +2581,11 @@ static zend_result php_openssl_setup_crypto(php_stream *stream,
 		php_openssl_netstream_data_t *parent_sslsock;
 
 		if (cparam->inputs.session->ops != &php_openssl_socket_ops) {
-			php_error_docref(NULL, E_WARNING, "Supplied session stream must be an SSL enabled stream");
+			php_stream_warn(stream, SslNotSupported, "Supplied session stream must be an SSL enabled stream");
 		} else if ((parent_sslsock = cparam->inputs.session->abstract)->ctx == NULL) {
-			php_error_docref(NULL, E_WARNING, "Supplied SSL session stream is not set up");
+			php_stream_warn(stream, SslNotSupported, "Supplied SSL session stream is not set up");
 		} else if (sslsock->is_client && parent_sslsock->ssl_handle == NULL) {
-			php_error_docref(NULL, E_WARNING, "Supplied SSL session stream is not initialized");
+			php_stream_warn(stream, SslNotSupported, "Supplied SSL session stream is not initialized");
 		} else {
 			SSL_CTX_up_ref(parent_sslsock->ctx);
 			sslsock->ctx = parent_sslsock->ctx;
@@ -2582,7 +2600,7 @@ static zend_result php_openssl_setup_crypto(php_stream *stream,
 
 			sslsock->ssl_handle = SSL_new(sslsock->ctx);
 			if (!sslsock->ssl_handle) {
-				php_error_docref(NULL, E_WARNING, "SSL handle creation failure");
+				php_stream_warn(stream, CreateFailed, "SSL handle creation failure");
 				SSL_CTX_free(sslsock->ctx);
 				sslsock->ctx = NULL;
 				return FAILURE;
@@ -2598,7 +2616,7 @@ static zend_result php_openssl_setup_crypto(php_stream *stream,
 				if (SSL_copy_session_id(sslsock->ssl_handle, parent_sslsock->ssl_handle)) {
 					SSL_CTX_set_session_cache_mode(sslsock->ctx, SSL_SESS_CACHE_CLIENT);
 				} else {
-					php_error_docref(NULL, E_WARNING, "SSL session copying failed creation failure");
+					php_stream_warn(stream, CreateFailed, "SSL session copying failed creation failure");
 				}
 			}
 
@@ -2614,7 +2632,7 @@ static zend_result php_openssl_setup_crypto(php_stream *stream,
 
 	if (sslsock->ssl_handle == NULL
 		|| !SSL_set_ex_data(sslsock->ssl_handle, php_openssl_get_ssl_stream_data_index(), stream)) {
-		php_error_docref(NULL, E_WARNING, "SSL handle creation failure");
+		php_stream_warn(stream, CreateFailed, "SSL handle creation failure");
 		SSL_CTX_free(sslsock->ctx);
 		sslsock->ctx = NULL;
 #ifdef HAVE_TLS_ALPN
@@ -2764,7 +2782,7 @@ static int php_openssl_enable_crypto(php_stream *stream,
 				elapsed_time = php_openssl_subtract_timeval(cur_time, start_time);
 
 				if (php_openssl_compare_timeval( elapsed_time, *timeout) > 0) {
-					php_error_docref(NULL, E_WARNING, "SSL: Handshake timed out");
+					php_stream_warn(stream, TimeOut, "SSL: Handshake timed out");
 					return -1;
 				}
 			}
@@ -3220,7 +3238,7 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_
 			if (php_stream_xport_crypto_setup(xparam->outputs.client, clisockdata->method,
 					sock->ctx ? stream : NULL) < 0 || php_stream_xport_crypto_enable(
 					xparam->outputs.client, 1) < 0) {
-				php_error_docref(NULL, E_WARNING, "Failed to enable crypto");
+				php_stream_warn(stream, ProtocolError, "Failed to enable crypto");
 
 				php_stream_close(xparam->outputs.client);
 				xparam->outputs.client = NULL;
@@ -3490,7 +3508,7 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val
 
 						if (php_stream_xport_crypto_setup(stream, sslsock->method, session_stream) < 0 ||
 								php_stream_xport_crypto_enable(stream, 1) < 0) {
-							php_error_docref(NULL, E_WARNING, "Failed to enable crypto");
+							php_stream_warn(stream, ProtocolError, "Failed to enable crypto");
 							xparam->outputs.returncode = -1;
 						}
 					}
@@ -3686,7 +3704,7 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen,
 		sslsock->enable_on_connect = 1;
 		sslsock->method = php_openssl_get_crypto_method(context, STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT);
 	} else if (strncmp(proto, "sslv2", protolen) == 0) {
-		php_error_docref(NULL, E_WARNING, "SSLv2 unavailable in this PHP version");
+		php_stream_warn(stream, ProtocolUnsupported, "SSLv2 unavailable in this PHP version");
 		php_stream_close(stream);
 		return NULL;
 	} else if (strncmp(proto, "sslv3", protolen) == 0) {
@@ -3694,7 +3712,7 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen,
 		sslsock->enable_on_connect = 1;
 		sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
 #else
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, ProtocolUnsupported,
 			"SSLv3 support is not compiled into the OpenSSL library against which PHP is linked");
 		php_stream_close(stream);
 		return NULL;
@@ -3710,7 +3728,7 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen,
 		sslsock->enable_on_connect = 1;
 		sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
 #else
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, ProtocolUnsupported,
 			"TLSv1.1 support is not compiled into the OpenSSL library against which PHP is linked");
 		php_stream_close(stream);
 		return NULL;
@@ -3720,7 +3738,7 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen,
 		sslsock->enable_on_connect = 1;
 		sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
 #else
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, ProtocolUnsupported,
 			"TLSv1.2 support is not compiled into the OpenSSL library against which PHP is linked");
 		php_stream_close(stream);
 		return NULL;
@@ -3730,7 +3748,7 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen,
 		sslsock->enable_on_connect = 1;
 		sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
 #else
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, ProtocolUnsupported,
 			"TLSv1.3 support is not compiled into the OpenSSL library against which PHP is linked");
 		php_stream_close(stream);
 		return NULL;
diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index bad645f8668a..7962f0f85de3 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -325,7 +325,7 @@ PHP_FUNCTION(stream_socket_accept)
 		if (peername) {
 			zend_string_release(peername);
 		}
-		php_error_docref(NULL, E_WARNING, "Accept failed: %s", errstr ? ZSTR_VAL(errstr) : "Unknown error");
+		php_stream_warn(stream, AcceptFailed, "Accept failed: %s", errstr ? ZSTR_VAL(errstr) : "Unknown error");
 		RETVAL_FALSE;
 	}
 
diff --git a/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt
index 37f5d39bfa75..6f470c61d1a1 100644
--- a/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt
+++ b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt
@@ -26,6 +26,6 @@ try {
 ?>
 --EXPECTF--
 Caught: Failed to open stream: operation failed
-Code: 20
+Code: 21
 Wrapper: PHP
 Error code name: OpenFailed
diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php
index 2c56932c4850..255c92beb89e 100644
--- a/main/streams/stream_errors.stub.php
+++ b/main/streams/stream_errors.stub.php
@@ -22,6 +22,7 @@ enum StreamErrorCode
     case ConnectFailed;
     case BindFailed;
     case ListenFailed;
+    case AcceptFailed;
     case NotWritable;
     case NotReadable;
 
@@ -86,6 +87,7 @@ enum StreamErrorCode
     case InvalidParam;
     case RedirectLimit;
     case AuthFailed;
+    case TimeOut;
 
     /* Encoding/decoding/archiving operations */
     case ArchivingFailed;
diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h
index 3a10e9bc5a40..89df622beb05 100644
--- a/main/streams/stream_errors_arginfo.h
+++ b/main/streams/stream_errors_arginfo.h
@@ -1,5 +1,5 @@
 /* This is a generated file, edit stream_errors.stub.php instead.
- * Stub hash: 3e9ee6f0fdd8ecf3ded82728487a9e774137036a
+ * Stub hash: 4cddf758cc9f2041802d8cbbaaa45593022a5db1
  * Has decl header: yes */
 
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getErrors, 0, 0, IS_ARRAY, 0)
@@ -38,6 +38,8 @@ static zend_class_entry *register_class_StreamErrorCode(void)
 
 	zend_enum_add_case_cstr(class_entry, "ListenFailed", NULL);
 
+	zend_enum_add_case_cstr(class_entry, "AcceptFailed", NULL);
+
 	zend_enum_add_case_cstr(class_entry, "NotWritable", NULL);
 
 	zend_enum_add_case_cstr(class_entry, "NotReadable", NULL);
@@ -146,6 +148,8 @@ static zend_class_entry *register_class_StreamErrorCode(void)
 
 	zend_enum_add_case_cstr(class_entry, "AuthFailed", NULL);
 
+	zend_enum_add_case_cstr(class_entry, "TimeOut", NULL);
+
 	zend_enum_add_case_cstr(class_entry, "ArchivingFailed", NULL);
 
 	zend_enum_add_case_cstr(class_entry, "EncodingFailed", NULL);
diff --git a/main/streams/stream_errors_decl.h b/main/streams/stream_errors_decl.h
index 4748768195f1..69fe96252b4c 100644
--- a/main/streams/stream_errors_decl.h
+++ b/main/streams/stream_errors_decl.h
@@ -1,8 +1,8 @@
 /* This is a generated file, edit stream_errors.stub.php instead.
- * Stub hash: 3e9ee6f0fdd8ecf3ded82728487a9e774137036a */
+ * Stub hash: 4cddf758cc9f2041802d8cbbaaa45593022a5db1 */
 
-#ifndef ZEND_STREAM_ERRORS_DECL_3e9ee6f0fdd8ecf3ded82728487a9e774137036a_H
-#define ZEND_STREAM_ERRORS_DECL_3e9ee6f0fdd8ecf3ded82728487a9e774137036a_H
+#ifndef ZEND_STREAM_ERRORS_DECL_4cddf758cc9f2041802d8cbbaaa45593022a5db1_H
+#define ZEND_STREAM_ERRORS_DECL_4cddf758cc9f2041802d8cbbaaa45593022a5db1_H
 
 typedef enum zend_enum_StreamErrorCode {
 	ZEND_ENUM_StreamErrorCode_None = 1,
@@ -16,74 +16,76 @@ typedef enum zend_enum_StreamErrorCode {
 	ZEND_ENUM_StreamErrorCode_ConnectFailed = 9,
 	ZEND_ENUM_StreamErrorCode_BindFailed = 10,
 	ZEND_ENUM_StreamErrorCode_ListenFailed = 11,
-	ZEND_ENUM_StreamErrorCode_NotWritable = 12,
-	ZEND_ENUM_StreamErrorCode_NotReadable = 13,
-	ZEND_ENUM_StreamErrorCode_Disabled = 14,
-	ZEND_ENUM_StreamErrorCode_NotFound = 15,
-	ZEND_ENUM_StreamErrorCode_PermissionDenied = 16,
-	ZEND_ENUM_StreamErrorCode_AlreadyExists = 17,
-	ZEND_ENUM_StreamErrorCode_InvalidPath = 18,
-	ZEND_ENUM_StreamErrorCode_PathTooLong = 19,
-	ZEND_ENUM_StreamErrorCode_OpenFailed = 20,
-	ZEND_ENUM_StreamErrorCode_CreateFailed = 21,
-	ZEND_ENUM_StreamErrorCode_DupFailed = 22,
-	ZEND_ENUM_StreamErrorCode_UnlinkFailed = 23,
-	ZEND_ENUM_StreamErrorCode_RenameFailed = 24,
-	ZEND_ENUM_StreamErrorCode_MkdirFailed = 25,
-	ZEND_ENUM_StreamErrorCode_RmdirFailed = 26,
-	ZEND_ENUM_StreamErrorCode_StatFailed = 27,
-	ZEND_ENUM_StreamErrorCode_MetaFailed = 28,
-	ZEND_ENUM_StreamErrorCode_ChmodFailed = 29,
-	ZEND_ENUM_StreamErrorCode_ChownFailed = 30,
-	ZEND_ENUM_StreamErrorCode_CopyFailed = 31,
-	ZEND_ENUM_StreamErrorCode_TouchFailed = 32,
-	ZEND_ENUM_StreamErrorCode_InvalidMode = 33,
-	ZEND_ENUM_StreamErrorCode_InvalidMeta = 34,
-	ZEND_ENUM_StreamErrorCode_ModeNotSupported = 35,
-	ZEND_ENUM_StreamErrorCode_Readonly = 36,
-	ZEND_ENUM_StreamErrorCode_RecursionDetected = 37,
-	ZEND_ENUM_StreamErrorCode_NotImplemented = 38,
-	ZEND_ENUM_StreamErrorCode_NoOpener = 39,
-	ZEND_ENUM_StreamErrorCode_PersistentNotSupported = 40,
-	ZEND_ENUM_StreamErrorCode_WrapperNotFound = 41,
-	ZEND_ENUM_StreamErrorCode_WrapperDisabled = 42,
-	ZEND_ENUM_StreamErrorCode_ProtocolUnsupported = 43,
-	ZEND_ENUM_StreamErrorCode_WrapperRegistrationFailed = 44,
-	ZEND_ENUM_StreamErrorCode_WrapperUnregistrationFailed = 45,
-	ZEND_ENUM_StreamErrorCode_WrapperRestorationFailed = 46,
-	ZEND_ENUM_StreamErrorCode_FilterNotFound = 47,
-	ZEND_ENUM_StreamErrorCode_FilterFailed = 48,
-	ZEND_ENUM_StreamErrorCode_CastFailed = 49,
-	ZEND_ENUM_StreamErrorCode_CastNotSupported = 50,
-	ZEND_ENUM_StreamErrorCode_MakeSeekableFailed = 51,
-	ZEND_ENUM_StreamErrorCode_BufferedDataLost = 52,
-	ZEND_ENUM_StreamErrorCode_NetworkSendFailed = 53,
-	ZEND_ENUM_StreamErrorCode_NetworkRecvFailed = 54,
-	ZEND_ENUM_StreamErrorCode_SslNotSupported = 55,
-	ZEND_ENUM_StreamErrorCode_ResumptionFailed = 56,
-	ZEND_ENUM_StreamErrorCode_SocketPathTooLong = 57,
-	ZEND_ENUM_StreamErrorCode_OobNotSupported = 58,
-	ZEND_ENUM_StreamErrorCode_ProtocolError = 59,
-	ZEND_ENUM_StreamErrorCode_InvalidUrl = 60,
-	ZEND_ENUM_StreamErrorCode_InvalidResponse = 61,
-	ZEND_ENUM_StreamErrorCode_InvalidHeader = 62,
-	ZEND_ENUM_StreamErrorCode_InvalidParam = 63,
-	ZEND_ENUM_StreamErrorCode_RedirectLimit = 64,
-	ZEND_ENUM_StreamErrorCode_AuthFailed = 65,
-	ZEND_ENUM_StreamErrorCode_ArchivingFailed = 66,
-	ZEND_ENUM_StreamErrorCode_EncodingFailed = 67,
-	ZEND_ENUM_StreamErrorCode_DecodingFailed = 68,
-	ZEND_ENUM_StreamErrorCode_InvalidFormat = 69,
-	ZEND_ENUM_StreamErrorCode_AllocationFailed = 70,
-	ZEND_ENUM_StreamErrorCode_TemporaryFileFailed = 71,
-	ZEND_ENUM_StreamErrorCode_LockFailed = 72,
-	ZEND_ENUM_StreamErrorCode_LockNotSupported = 73,
-	ZEND_ENUM_StreamErrorCode_UserspaceNotImplemented = 74,
-	ZEND_ENUM_StreamErrorCode_UserspaceInvalidReturn = 75,
-	ZEND_ENUM_StreamErrorCode_UserspaceCallFailed = 76,
+	ZEND_ENUM_StreamErrorCode_AcceptFailed = 12,
+	ZEND_ENUM_StreamErrorCode_NotWritable = 13,
+	ZEND_ENUM_StreamErrorCode_NotReadable = 14,
+	ZEND_ENUM_StreamErrorCode_Disabled = 15,
+	ZEND_ENUM_StreamErrorCode_NotFound = 16,
+	ZEND_ENUM_StreamErrorCode_PermissionDenied = 17,
+	ZEND_ENUM_StreamErrorCode_AlreadyExists = 18,
+	ZEND_ENUM_StreamErrorCode_InvalidPath = 19,
+	ZEND_ENUM_StreamErrorCode_PathTooLong = 20,
+	ZEND_ENUM_StreamErrorCode_OpenFailed = 21,
+	ZEND_ENUM_StreamErrorCode_CreateFailed = 22,
+	ZEND_ENUM_StreamErrorCode_DupFailed = 23,
+	ZEND_ENUM_StreamErrorCode_UnlinkFailed = 24,
+	ZEND_ENUM_StreamErrorCode_RenameFailed = 25,
+	ZEND_ENUM_StreamErrorCode_MkdirFailed = 26,
+	ZEND_ENUM_StreamErrorCode_RmdirFailed = 27,
+	ZEND_ENUM_StreamErrorCode_StatFailed = 28,
+	ZEND_ENUM_StreamErrorCode_MetaFailed = 29,
+	ZEND_ENUM_StreamErrorCode_ChmodFailed = 30,
+	ZEND_ENUM_StreamErrorCode_ChownFailed = 31,
+	ZEND_ENUM_StreamErrorCode_CopyFailed = 32,
+	ZEND_ENUM_StreamErrorCode_TouchFailed = 33,
+	ZEND_ENUM_StreamErrorCode_InvalidMode = 34,
+	ZEND_ENUM_StreamErrorCode_InvalidMeta = 35,
+	ZEND_ENUM_StreamErrorCode_ModeNotSupported = 36,
+	ZEND_ENUM_StreamErrorCode_Readonly = 37,
+	ZEND_ENUM_StreamErrorCode_RecursionDetected = 38,
+	ZEND_ENUM_StreamErrorCode_NotImplemented = 39,
+	ZEND_ENUM_StreamErrorCode_NoOpener = 40,
+	ZEND_ENUM_StreamErrorCode_PersistentNotSupported = 41,
+	ZEND_ENUM_StreamErrorCode_WrapperNotFound = 42,
+	ZEND_ENUM_StreamErrorCode_WrapperDisabled = 43,
+	ZEND_ENUM_StreamErrorCode_ProtocolUnsupported = 44,
+	ZEND_ENUM_StreamErrorCode_WrapperRegistrationFailed = 45,
+	ZEND_ENUM_StreamErrorCode_WrapperUnregistrationFailed = 46,
+	ZEND_ENUM_StreamErrorCode_WrapperRestorationFailed = 47,
+	ZEND_ENUM_StreamErrorCode_FilterNotFound = 48,
+	ZEND_ENUM_StreamErrorCode_FilterFailed = 49,
+	ZEND_ENUM_StreamErrorCode_CastFailed = 50,
+	ZEND_ENUM_StreamErrorCode_CastNotSupported = 51,
+	ZEND_ENUM_StreamErrorCode_MakeSeekableFailed = 52,
+	ZEND_ENUM_StreamErrorCode_BufferedDataLost = 53,
+	ZEND_ENUM_StreamErrorCode_NetworkSendFailed = 54,
+	ZEND_ENUM_StreamErrorCode_NetworkRecvFailed = 55,
+	ZEND_ENUM_StreamErrorCode_SslNotSupported = 56,
+	ZEND_ENUM_StreamErrorCode_ResumptionFailed = 57,
+	ZEND_ENUM_StreamErrorCode_SocketPathTooLong = 58,
+	ZEND_ENUM_StreamErrorCode_OobNotSupported = 59,
+	ZEND_ENUM_StreamErrorCode_ProtocolError = 60,
+	ZEND_ENUM_StreamErrorCode_InvalidUrl = 61,
+	ZEND_ENUM_StreamErrorCode_InvalidResponse = 62,
+	ZEND_ENUM_StreamErrorCode_InvalidHeader = 63,
+	ZEND_ENUM_StreamErrorCode_InvalidParam = 64,
+	ZEND_ENUM_StreamErrorCode_RedirectLimit = 65,
+	ZEND_ENUM_StreamErrorCode_AuthFailed = 66,
+	ZEND_ENUM_StreamErrorCode_TimeOut = 67,
+	ZEND_ENUM_StreamErrorCode_ArchivingFailed = 68,
+	ZEND_ENUM_StreamErrorCode_EncodingFailed = 69,
+	ZEND_ENUM_StreamErrorCode_DecodingFailed = 70,
+	ZEND_ENUM_StreamErrorCode_InvalidFormat = 71,
+	ZEND_ENUM_StreamErrorCode_AllocationFailed = 72,
+	ZEND_ENUM_StreamErrorCode_TemporaryFileFailed = 73,
+	ZEND_ENUM_StreamErrorCode_LockFailed = 74,
+	ZEND_ENUM_StreamErrorCode_LockNotSupported = 75,
+	ZEND_ENUM_StreamErrorCode_UserspaceNotImplemented = 76,
+	ZEND_ENUM_StreamErrorCode_UserspaceInvalidReturn = 77,
+	ZEND_ENUM_StreamErrorCode_UserspaceCallFailed = 78,
 } zend_enum_StreamErrorCode;
 
-#define ZEND_ENUM_StreamErrorCode_CASE_COUNT 76
+#define ZEND_ENUM_StreamErrorCode_CASE_COUNT 78
 
 #ifdef ZEND_ENUM_StreamErrorCode_USE_NAME_TABLE
 static const char *zend_enum_StreamErrorCode_case_names[ZEND_ENUM_StreamErrorCode_CASE_COUNT + 1] = {
@@ -98,6 +100,7 @@ static const char *zend_enum_StreamErrorCode_case_names[ZEND_ENUM_StreamErrorCod
 	[ZEND_ENUM_StreamErrorCode_ConnectFailed] = "ConnectFailed",
 	[ZEND_ENUM_StreamErrorCode_BindFailed] = "BindFailed",
 	[ZEND_ENUM_StreamErrorCode_ListenFailed] = "ListenFailed",
+	[ZEND_ENUM_StreamErrorCode_AcceptFailed] = "AcceptFailed",
 	[ZEND_ENUM_StreamErrorCode_NotWritable] = "NotWritable",
 	[ZEND_ENUM_StreamErrorCode_NotReadable] = "NotReadable",
 	[ZEND_ENUM_StreamErrorCode_Disabled] = "Disabled",
@@ -152,6 +155,7 @@ static const char *zend_enum_StreamErrorCode_case_names[ZEND_ENUM_StreamErrorCod
 	[ZEND_ENUM_StreamErrorCode_InvalidParam] = "InvalidParam",
 	[ZEND_ENUM_StreamErrorCode_RedirectLimit] = "RedirectLimit",
 	[ZEND_ENUM_StreamErrorCode_AuthFailed] = "AuthFailed",
+	[ZEND_ENUM_StreamErrorCode_TimeOut] = "TimeOut",
 	[ZEND_ENUM_StreamErrorCode_ArchivingFailed] = "ArchivingFailed",
 	[ZEND_ENUM_StreamErrorCode_EncodingFailed] = "EncodingFailed",
 	[ZEND_ENUM_StreamErrorCode_DecodingFailed] = "DecodingFailed",
@@ -180,4 +184,4 @@ typedef enum zend_enum_StreamErrorStore {
 	ZEND_ENUM_StreamErrorStore_All = 5,
 } zend_enum_StreamErrorStore;
 
-#endif /* ZEND_STREAM_ERRORS_DECL_3e9ee6f0fdd8ecf3ded82728487a9e774137036a_H */
+#endif /* ZEND_STREAM_ERRORS_DECL_4cddf758cc9f2041802d8cbbaaa45593022a5db1_H */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.