diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index 4693787607b43..2821fa47e984b 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -129,7 +129,7 @@ ftp_open(const char *host, short port, zend_long timeout_sec) ftp->fd = php_network_connect_socket_to_host(host, (unsigned short) (port ? port : 21), SOCK_STREAM, - 0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE); + 0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE, NULL); if (ftp->fd == -1) { goto bail; } diff --git a/ext/standard/tests/network/gh14111.phpt b/ext/standard/tests/network/gh14111.phpt new file mode 100644 index 0000000000000..9cfc9b016914e --- /dev/null +++ b/ext/standard/tests/network/gh14111.phpt @@ -0,0 +1,38 @@ +--TEST-- +Testing linger `socket` option. +--FILE-- + ['linger' => false]]); +$socket = stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 0, STREAM_CLIENT_CONNECT, $context); +var_dump($socket); +$context = stream_context_create(['socket' => ['linger' => PHP_INT_MAX + 1]]); +$socket = stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 0, STREAM_CLIENT_CONNECT, $context); +var_dump($socket); +$context = stream_context_create(['socket' => ['linger' => 5]]); +$socket = stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 1, STREAM_CLIENT_CONNECT, $context); +var_dump($socket); +stream_set_blocking($socket, true); +var_dump(stream_socket_sendto($socket, "data")); +$data = base64_decode("1oIBAAABAAAAAAAAB2V4YW1wbGUDb3JnAAABAAE="); +stream_set_blocking($socket, 0); +stream_socket_sendto($socket, $data); +stream_socket_shutdown($socket, STREAM_SHUT_RDWR); +stream_socket_shutdown($server, STREAM_SHUT_RDWR); +?> +--EXPECTF-- +Warning: stream_socket_client(): Unable to connect to tcp://127.0.0.1:%d (Invalid `linger` value) in %s on line %d +bool(false) + +Warning: stream_socket_client(): Unable to connect to tcp://127.0.0.1:%d (Invalid `linger` value) in %s on line %d +bool(false) +resource(%d) of type (stream) +int(4) diff --git a/main/network.c b/main/network.c index b015b59c86e21..c9d6f4acb2af1 100644 --- a/main/network.c +++ b/main/network.c @@ -402,7 +402,7 @@ static inline void sub_times(struct timeval a, struct timeval b, struct timeval * */ /* {{{ php_network_bind_socket_to_local_addr */ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port, - int socktype, long sockopts, zend_string **error_string, int *error_code + int socktype, long sockopts, void *option, zend_string **error_string, int *error_code ) { int num_addrs, n, err = 0; @@ -470,6 +470,17 @@ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned po setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&sockoptval, sizeof(sockoptval)); } #endif +#ifdef PHP_SO_LINGER + if (sockopts & STREAM_SOCKOP_SO_LINGER) { + ZEND_ASSERT(option != NULL); + long linger = *(long *)option; + struct linger val = { + .l_onoff = (linger > 0), + .l_linger = (int)linger + }; + setsockopt(sock, IPPROTO_TCP, PHP_SO_LINGER, (char*)&val, sizeof(val)); + } +#endif n = bind(sock, sa, socklen); @@ -766,7 +777,8 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, /* {{{ php_network_connect_socket_to_host */ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port, int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string, - int *error_code, const char *bindto, unsigned short bindport, long sockopts + int *error_code, const char *bindto, unsigned short bindport, long sockopts, + void *option ) { int num_addrs, n, fatal = 0; @@ -896,6 +908,19 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val)); } } +#endif +#ifdef PHP_SO_LINGER + { + if (sockopts & STREAM_SOCKOP_SO_LINGER) { + ZEND_ASSERT(option != NULL); + long linger = *(long *)option; + struct linger val = { + .l_onoff = linger > 0, + .l_linger = (int)linger + }; + setsockopt(sock, IPPROTO_TCP, PHP_SO_LINGER, (char*)&val, sizeof(val)); + } + } #endif n = php_network_connect_socket(sock, sa, socklen, asynchronous, timeout ? &working_timeout : NULL, diff --git a/main/php_network.h b/main/php_network.h index a31bc57b76aee..9a05dc8f21587 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -54,6 +54,14 @@ # define PHP_IS_TRANSIENT_ERROR(err) (err == EAGAIN) #endif +#ifdef SO_LINGER +# ifdef SO_LINGER_SEC +# define PHP_SO_LINGER SO_LINGER_SEC +# else +# define PHP_SO_LINGER SO_LINGER +# endif +#endif + #ifdef PHP_WIN32 #define php_socket_errno() WSAGetLastError() #else @@ -116,6 +124,7 @@ typedef int php_socket_t; #define STREAM_SOCKOP_IPV6_V6ONLY (1 << 3) #define STREAM_SOCKOP_IPV6_V6ONLY_ENABLED (1 << 4) #define STREAM_SOCKOP_TCP_NODELAY (1 << 5) +#define STREAM_SOCKOP_SO_LINGER (1 << 6) /* uncomment this to debug poll(2) emulation on systems that have poll(2) */ @@ -265,7 +274,8 @@ PHPAPI void php_network_freeaddresses(struct sockaddr **sal); PHPAPI php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port, int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string, - int *error_code, const char *bindto, unsigned short bindport, long sockopts + int *error_code, const char *bindto, unsigned short bindport, long sockopts, + void *option ); PHPAPI int php_network_connect_socket(php_socket_t sockfd, @@ -280,7 +290,7 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd, php_network_connect_socket((sock), (addr), (addrlen), 0, (timeout), NULL, NULL) PHPAPI php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port, - int socktype, long sockopts, zend_string **error_string, int *error_code + int socktype, long sockopts, void *option, zend_string **error_string, int *error_code ); PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index 3d035de6edb21..7bbc88ac02930 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -659,12 +659,42 @@ static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text); } +static long parse_linger(zval *arg, bool *failed) +{ + zend_long lval; + *failed = false; + if (Z_TYPE_P(arg) == IS_STRING) { + zend_string *val = Z_STR_P(arg); + uint8_t r = is_numeric_string(ZSTR_VAL(val), ZSTR_LEN(val), &lval, NULL, false); + + switch (r) { + case IS_LONG: + break; + default: + *failed = true; + return -1; + } + } else if (Z_TYPE_P(arg) == IS_LONG) { + lval = Z_LVAL_P(arg); + } else { + *failed = true; + return -1; + } + + if (lval < 0 || lval > INT_MAX) { + *failed = true; + } + return (long)lval; +} + static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock, php_stream_xport_param *xparam) { char *host = NULL; int portno, err; long sockopts = STREAM_SOCKOP_NONE; + long linger = -1; + void *option = NULL; zval *tmpzval = NULL; #ifdef AF_UNIX @@ -724,9 +754,32 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t * } #endif +#ifdef PHP_SO_LINGER + if (PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "linger")) != NULL) { + bool failed; + linger = parse_linger(tmpzval, &failed); + + if (failed) { + if (xparam->want_errortext) { + xparam->outputs.error_text = strpprintf(0, "Invalid `linger` value"); + } + if (host) { + efree(host); + } + return -1; + } else { + sockopts |= STREAM_SOCKOP_SO_LINGER; + } + + option = &linger; + } +#endif + sock->socket = php_network_bind_socket_to_local_addr(host, portno, stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, sockopts, + option, xparam->want_errortext ? &xparam->outputs.error_text : NULL, &err ); @@ -747,6 +800,8 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_ int ret; zval *tmpzval = NULL; long sockopts = STREAM_SOCKOP_NONE; + long linger = -1; + void *option = NULL; #ifdef AF_UNIX if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { @@ -802,15 +857,40 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_ } #endif +#ifdef PHP_SO_LINGER + if (PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "linger")) != NULL) { + bool failed; + linger = parse_linger(tmpzval, &failed); + + if (failed) { + if (xparam->want_errortext) { + xparam->outputs.error_text = strpprintf(0, "Invalid `linger` value"); + } + if (host) { + efree(host); + } + if (bindto) { + efree(bindto); + } + return -1; + } else { + sockopts |= STREAM_SOCKOP_SO_LINGER; + } + + option = &linger; + } +#endif + if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */ #ifdef AF_UNIX - && stream->ops != &php_stream_unix_socket_ops - && stream->ops != &php_stream_unixdg_socket_ops + && stream->ops != &php_stream_unix_socket_ops + && stream->ops != &php_stream_unixdg_socket_ops #endif - && PHP_STREAM_CONTEXT(stream) - && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL - && zend_is_true(tmpzval) - ) { + && PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL + && zend_is_true(tmpzval) + ) { sockopts |= STREAM_SOCKOP_TCP_NODELAY; } @@ -826,7 +906,8 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_ &err, bindto, bindport, - sockopts + sockopts, + option ); ret = sock->socket == -1 ? -1 : 0;