/* +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Wez Furlong | +----------------------------------------------------------------------+ */ #include "php.h" #include "ext/standard/file.h" #include "streams/php_streams_int.h" #include "php_network.h" #if defined(PHP_WIN32) || defined(__riscos__) # undef AF_UNIX #endif #ifdef AF_UNIX #include #endif #ifndef MSG_DONTWAIT # define MSG_DONTWAIT 0 #endif #ifndef MSG_PEEK # define MSG_PEEK 0 #endif #ifdef PHP_WIN32 /* send/recv family on windows expects int */ # define XP_SOCK_BUF_SIZE(sz) (((sz) > INT_MAX) ? INT_MAX : (int)(sz)) #else # define XP_SOCK_BUF_SIZE(sz) (sz) #endif const php_stream_ops php_stream_generic_socket_ops; PHPAPI const php_stream_ops php_stream_socket_ops; const php_stream_ops php_stream_udp_socket_ops; #ifdef AF_UNIX const php_stream_ops php_stream_unix_socket_ops; const php_stream_ops php_stream_unixdg_socket_ops; #endif static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam); /* {{{ Generic socket stream operations */ static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t count) { php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; ssize_t didwrite; struct timeval *ptimeout; if (!sock || sock->socket == -1) { return 0; } if (sock->timeout.tv_sec == -1) ptimeout = NULL; else ptimeout = &sock->timeout; retry: didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0); if (didwrite <= 0) { char *estr; int err = php_socket_errno(); if (PHP_IS_TRANSIENT_ERROR(err)) { if (sock->is_blocked) { int retval; sock->timeout_event = 0; do { retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout); if (retval == 0) { sock->timeout_event = 1; break; } if (retval > 0) { /* writable now; retry */ goto retry; } err = php_socket_errno(); } while (err == EINTR); } else { /* EWOULDBLOCK/EAGAIN is not an error for a non-blocking stream. * Report zero byte write instead. */ return 0; } } if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { estr = php_socket_strerror(err, NULL, 0); php_error_docref(NULL, E_NOTICE, "Send of " ZEND_LONG_FMT " bytes failed with errno=%d %s", (zend_long)count, err, estr); efree(estr); } } if (didwrite > 0) { php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0); } return didwrite; } static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock, bool has_buffered_data) { int retval; struct timeval *ptimeout, zero_timeout; if (!sock || sock->socket == -1) { return; } sock->timeout_event = 0; if (has_buffered_data) { /* If there is already buffered data, use no timeout. */ zero_timeout.tv_sec = 0; zero_timeout.tv_usec = 0; ptimeout = &zero_timeout; } else if (sock->timeout.tv_sec == -1) { ptimeout = NULL; } else { ptimeout = &sock->timeout; } while(1) { retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout); if (retval == 0) sock->timeout_event = 1; if (retval >= 0) break; if (php_socket_errno() != EINTR) break; } } static ssize_t php_sockop_read(php_stream *stream, char *buf, size_t count) { php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; if (!sock || sock->socket == -1) { return -1; } int recv_flags = 0; /* Special handling for blocking read. */ if (sock->is_blocked) { /* Find out if there is any data buffered from the previous read. */ bool has_buffered_data = stream->has_buffered_data; /* No need to wait if there is any data buffered or no timeout. */ bool dont_wait = has_buffered_data || (sock->timeout.tv_sec == 0 && sock->timeout.tv_usec == 0); /* Set MSG_DONTWAIT if no wait is needed or there is unlimited timeout which was * added by fix for #41984 commited in 9343c5404. */ if (dont_wait || sock->timeout.tv_sec != -1) { recv_flags = MSG_DONTWAIT; } /* If the wait is needed or it is a platform without MSG_DONTWAIT support (e.g. Windows), * then poll for data. */ if (!dont_wait || MSG_DONTWAIT == 0) { php_sock_stream_wait_for_data(stream, sock, has_buffered_data); if (sock->timeout_event) { /* It is ok to timeout if there is any data buffered so return 0, otherwise -1. */ return has_buffered_data ? 0 : -1; } } } ssize_t nr_bytes = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(count), recv_flags); int err = php_socket_errno(); if (nr_bytes < 0) { if (PHP_IS_TRANSIENT_ERROR(err)) { nr_bytes = 0; } else { stream->eof = 1; } } else if (nr_bytes == 0) { stream->eof = 1; } if (nr_bytes > 0) { php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0); } return nr_bytes; } static int php_sockop_close(php_stream *stream, int close_handle) { php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; #ifdef PHP_WIN32 int n; #endif if (!sock) { return 0; } if (close_handle) { #ifdef PHP_WIN32 if (sock->socket == -1) sock->socket = SOCK_ERR; #endif if (sock->socket != SOCK_ERR) { #ifdef PHP_WIN32 /* prevent more data from coming in */ shutdown(sock->socket, SHUT_RD); /* try to make sure that the OS sends all data before we close the connection. * Essentially, we are waiting for the socket to become writeable, which means * that all pending data has been sent. * We use a small timeout which should encourage the OS to send the data, * but at the same time avoid hanging indefinitely. * */ do { n = php_pollfd_for_ms(sock->socket, POLLOUT, 500); } while (n == -1 && php_socket_errno() == EINTR); #endif closesocket(sock->socket); sock->socket = SOCK_ERR; } } pefree(sock, php_stream_is_persistent(stream)); return 0; } static int php_sockop_flush(php_stream *stream) { #if 0 php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; return fsync(sock->socket); #endif return 0; } static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb) { #ifdef ZEND_WIN32 return 0; #else php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; return zend_fstat(sock->socket, &ssb->sb); #endif } static inline int sock_sendto(php_netstream_data_t *sock, const char *buf, size_t buflen, int flags, struct sockaddr *addr, socklen_t addrlen ) { int ret; if (addr) { ret = sendto(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, addr, XP_SOCK_BUF_SIZE(addrlen)); return (ret == SOCK_CONN_ERR) ? -1 : ret; } #ifdef PHP_WIN32 return ((ret = send(sock->socket, buf, buflen > INT_MAX ? INT_MAX : (int)buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret; #else return ((ret = send(sock->socket, buf, buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret; #endif } static inline int sock_recvfrom(php_netstream_data_t *sock, char *buf, size_t buflen, int flags, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen ) { int ret; int want_addr = textaddr || addr; if (want_addr) { php_sockaddr_storage sa; socklen_t sl = sizeof(sa); ret = recvfrom(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, (struct sockaddr*)&sa, &sl); ret = (ret == SOCK_CONN_ERR) ? -1 : ret; #ifdef PHP_WIN32 /* POSIX discards excess bytes without signalling failure; emulate this on Windows */ if (ret == -1 && WSAGetLastError() == WSAEMSGSIZE) { ret = buflen; } #endif if (sl) { php_network_populate_name_from_sockaddr((struct sockaddr*)&sa, sl, textaddr, addr, addrlen); } else { if (textaddr) { *textaddr = ZSTR_EMPTY_ALLOC(); } if (addr) { *addr = NULL; *addrlen = 0; } } } else { ret = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags); ret = (ret == SOCK_CONN_ERR) ? -1 : ret; } return ret; } static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) { int oldmode, flags; php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; php_stream_xport_param *xparam; if (!sock) { return PHP_STREAM_OPTION_RETURN_NOTIMPL; } switch(option) { case PHP_STREAM_OPTION_CHECK_LIVENESS: { struct timeval tv; char buf; int alive = 1; if (value == -1) { if (sock->timeout.tv_sec == -1) { tv.tv_sec = FG(default_socket_timeout); tv.tv_usec = 0; } else { tv = sock->timeout; } } else { tv.tv_sec = value; tv.tv_usec = 0; } if (sock->socket == -1) { alive = 0; } else if ( ( value == 0 && !(stream->flags & PHP_STREAM_FLAG_NO_IO) && ((MSG_DONTWAIT != 0) || !sock->is_blocked) ) || php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0 ) { /* the poll() call was skipped if the socket is non-blocking (or MSG_DONTWAIT is available) and if the timeout is zero */ #ifdef PHP_WIN32 int ret; #else ssize_t ret; #endif int err; ret = recv(sock->socket, &buf, sizeof(buf), MSG_PEEK|MSG_DONTWAIT); err = php_socket_errno(); if (0 == ret || /* the counterpart did properly shutdown*/ (0 > ret && err != EWOULDBLOCK && err != EAGAIN && err != EMSGSIZE)) { /* there was an unrecoverable error */ alive = 0; } } return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; } case PHP_STREAM_OPTION_BLOCKING: oldmode = sock->is_blocked; if (SUCCESS == php_set_sock_blocking(sock->socket, value)) { sock->is_blocked = value; return oldmode; } return PHP_STREAM_OPTION_RETURN_ERR; case PHP_STREAM_OPTION_READ_TIMEOUT: sock->timeout = *(struct timeval*)ptrparam; sock->timeout_event = 0; return PHP_STREAM_OPTION_RETURN_OK; case PHP_STREAM_OPTION_META_DATA_API: add_assoc_bool((zval *)ptrparam, "timed_out", sock->timeout_event); add_assoc_bool((zval *)ptrparam, "blocked", sock->is_blocked); add_assoc_bool((zval *)ptrparam, "eof", stream->eof); return PHP_STREAM_OPTION_RETURN_OK; case PHP_STREAM_OPTION_XPORT_API: xparam = (php_stream_xport_param *)ptrparam; switch (xparam->op) { case STREAM_XPORT_OP_LISTEN: xparam->outputs.returncode = (listen(sock->socket, xparam->inputs.backlog) == 0) ? 0: -1; return PHP_STREAM_OPTION_RETURN_OK; case STREAM_XPORT_OP_GET_NAME: xparam->outputs.returncode = php_network_get_sock_name(sock->socket, xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, xparam->want_addr ? &xparam->outputs.addr : NULL, xparam->want_addr ? &xparam->outputs.addrlen : NULL ); return PHP_STREAM_OPTION_RETURN_OK; case STREAM_XPORT_OP_GET_PEER_NAME: xparam->outputs.returncode = php_network_get_peer_name(sock->socket, xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, xparam->want_addr ? &xparam->outputs.addr : NULL, xparam->want_addr ? &xparam->outputs.addrlen : NULL ); return PHP_STREAM_OPTION_RETURN_OK; case STREAM_XPORT_OP_SEND: flags = 0; if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { flags |= MSG_OOB; } xparam->outputs.returncode = sock_sendto(sock, xparam->inputs.buf, xparam->inputs.buflen, flags, xparam->inputs.addr, xparam->inputs.addrlen); if (xparam->outputs.returncode == -1) { char *err = php_socket_strerror(php_socket_errno(), NULL, 0); php_error_docref(NULL, E_WARNING, "%s\n", err); efree(err); } return PHP_STREAM_OPTION_RETURN_OK; case STREAM_XPORT_OP_RECV: flags = 0; if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { flags |= MSG_OOB; } if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) { flags |= MSG_PEEK; } xparam->outputs.returncode = sock_recvfrom(sock, xparam->inputs.buf, xparam->inputs.buflen, flags, xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, xparam->want_addr ? &xparam->outputs.addr : NULL, xparam->want_addr ? &xparam->outputs.addrlen : NULL ); return PHP_STREAM_OPTION_RETURN_OK; #ifdef HAVE_SHUTDOWN # ifndef SHUT_RD # define SHUT_RD 0 # endif # ifndef SHUT_WR # define SHUT_WR 1 # endif # ifndef SHUT_RDWR # define SHUT_RDWR 2 # endif case STREAM_XPORT_OP_SHUTDOWN: { static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR}; xparam->outputs.returncode = shutdown(sock->socket, shutdown_how[xparam->how]); return PHP_STREAM_OPTION_RETURN_OK; } #endif default: break; } } return PHP_STREAM_OPTION_RETURN_NOTIMPL; } static int php_sockop_cast(php_stream *stream, int castas, void **ret) { php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; if (!sock) { return FAILURE; } switch(castas) { case PHP_STREAM_AS_STDIO: if (ret) { *(FILE**)ret = fdopen(sock->socket, stream->mode); if (*ret) return SUCCESS; return FAILURE; } return SUCCESS; case PHP_STREAM_AS_FD_FOR_SELECT: case PHP_STREAM_AS_FD: case PHP_STREAM_AS_SOCKETD: if (ret) *(php_socket_t *)ret = sock->socket; return SUCCESS; default: return FAILURE; } } /* }}} */ /* These may look identical, but we need them this way so that * we can determine which type of socket we are dealing with * by inspecting stream->ops. * A "useful" side-effect is that the user's scripts can then * make similar decisions using stream_get_meta_data. * */ const php_stream_ops php_stream_generic_socket_ops = { php_sockop_write, php_sockop_read, php_sockop_close, php_sockop_flush, "generic_socket", NULL, /* seek */ php_sockop_cast, php_sockop_stat, php_sockop_set_option, }; const php_stream_ops php_stream_socket_ops = { php_sockop_write, php_sockop_read, php_sockop_close, php_sockop_flush, "tcp_socket", NULL, /* seek */ php_sockop_cast, php_sockop_stat, php_tcp_sockop_set_option, }; const php_stream_ops php_stream_udp_socket_ops = { php_sockop_write, php_sockop_read, php_sockop_close, php_sockop_flush, "udp_socket", NULL, /* seek */ php_sockop_cast, php_sockop_stat, php_tcp_sockop_set_option, }; #ifdef AF_UNIX const php_stream_ops php_stream_unix_socket_ops = { php_sockop_write, php_sockop_read, php_sockop_close, php_sockop_flush, "unix_socket", NULL, /* seek */ php_sockop_cast, php_sockop_stat, php_tcp_sockop_set_option, }; const php_stream_ops php_stream_unixdg_socket_ops = { php_sockop_write, php_sockop_read, php_sockop_close, php_sockop_flush, "udg_socket", NULL, /* seek */ php_sockop_cast, php_sockop_stat, php_tcp_sockop_set_option, }; #endif /* network socket operations */ #ifdef AF_UNIX static inline int parse_unix_address(php_stream_xport_param *xparam, struct sockaddr_un *unix_addr) { memset(unix_addr, 0, sizeof(*unix_addr)); unix_addr->sun_family = AF_UNIX; /* Abstract namespace does not need to be NUL-terminated, while path-based * sockets should be. */ bool is_abstract_ns = xparam->inputs.namelen > 0 && xparam->inputs.name[0] == '\0'; unsigned long max_length = is_abstract_ns ? sizeof(unix_addr->sun_path) : sizeof(unix_addr->sun_path) - 1; /* we need to be binary safe on systems that support an abstract * namespace */ if (xparam->inputs.namelen > max_length) { /* On linux, when the path begins with a NUL byte we are * referring to an abstract namespace. In theory we should * allow an extra byte below, since we don't need the NULL. * BUT, to get into this branch of code, the name is too long, * so we don't care. */ xparam->inputs.namelen = max_length; php_error_docref(NULL, E_NOTICE, "socket path exceeded the maximum allowed length of %lu bytes " "and was truncated", max_length); } memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen); return 1; } #endif static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err) { char *colon; char *host = NULL; #ifdef HAVE_IPV6 char *p; if (*(str) == '[' && str_len > 1) { /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ p = memchr(str + 1, ']', str_len - 2); if (!p || *(p + 1) != ':') { if (get_err) { *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); } return NULL; } *portno = atoi(p + 2); return estrndup(str + 1, p - str - 1); } #endif if (str_len) { colon = memchr(str, ':', str_len - 1); } else { colon = NULL; } if (colon) { *portno = atoi(colon + 1); host = estrndup(str, colon - str); } else { if (get_err) { *err = strpprintf(0, "Failed to parse address \"%s\"", str); } return NULL; } return host; } 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 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; zval *tmpzval = NULL; #ifdef AF_UNIX if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { struct sockaddr_un unix_addr; sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); if (sock->socket == SOCK_ERR) { if (xparam->want_errortext) { xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s", stream->ops == &php_stream_unix_socket_ops ? "" : "datagram", strerror(errno)); } return -1; } parse_unix_address(xparam, &unix_addr); return bind(sock->socket, (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen); } #endif host = parse_ip_address(xparam, &portno); if (host == NULL) { return -1; } #ifdef IPV6_V6ONLY if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL && Z_TYPE_P(tmpzval) != IS_NULL ) { sockopts |= STREAM_SOCKOP_IPV6_V6ONLY; sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval); } #endif #ifdef SO_REUSEPORT if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL && zend_is_true(tmpzval) ) { sockopts |= STREAM_SOCKOP_SO_REUSEPORT; } #endif #ifdef SO_BROADCAST if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */ && PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL && zend_is_true(tmpzval) ) { sockopts |= STREAM_SOCKOP_SO_BROADCAST; } #endif sock->socket = php_network_bind_socket_to_local_addr(host, portno, stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, sockopts, xparam->want_errortext ? &xparam->outputs.error_text : NULL, &err ); if (host) { efree(host); } return sock->socket == -1 ? -1 : 0; } static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock, php_stream_xport_param *xparam) { char *host = NULL, *bindto = NULL; int portno, bindport = 0; int err = 0; int ret; zval *tmpzval = NULL; long sockopts = STREAM_SOCKOP_NONE; #ifdef AF_UNIX if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { struct sockaddr_un unix_addr; sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); if (sock->socket == SOCK_ERR) { if (xparam->want_errortext) { xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket"); } return -1; } parse_unix_address(xparam, &unix_addr); ret = php_network_connect_socket(sock->socket, (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen, xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout, xparam->want_errortext ? &xparam->outputs.error_text : NULL, &err); xparam->outputs.error_code = err; goto out; } #endif host = parse_ip_address(xparam, &portno); if (host == NULL) { return -1; } if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) { if (Z_TYPE_P(tmpzval) != IS_STRING) { if (xparam->want_errortext) { xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string."); } efree(host); return -1; } bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text); } #ifdef SO_BROADCAST if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */ && PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL && zend_is_true(tmpzval) ) { sockopts |= STREAM_SOCKOP_SO_BROADCAST; } #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 #endif && 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; } /* Note: the test here for php_stream_udp_socket_ops is important, because we * want the default to be TCP sockets so that the openssl extension can * re-use this code. */ sock->socket = php_network_connect_socket_to_host(host, portno, stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout, xparam->want_errortext ? &xparam->outputs.error_text : NULL, &err, bindto, bindport, sockopts ); ret = sock->socket == -1 ? -1 : 0; xparam->outputs.error_code = err; if (host) { efree(host); } if (bindto) { efree(bindto); } #ifdef AF_UNIX out: #endif if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) { /* indicates pending connection */ return 1; } return ret; } static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock, php_stream_xport_param *xparam STREAMS_DC) { int clisock; bool nodelay = 0; zval *tmpzval = NULL; xparam->outputs.client = NULL; if ((NULL != PHP_STREAM_CONTEXT(stream)) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL && zend_is_true(tmpzval)) { nodelay = 1; } clisock = php_network_accept_incoming(sock->socket, xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, xparam->want_addr ? &xparam->outputs.addr : NULL, xparam->want_addr ? &xparam->outputs.addrlen : NULL, xparam->inputs.timeout, xparam->want_errortext ? &xparam->outputs.error_text : NULL, &xparam->outputs.error_code, nodelay); if (clisock >= 0) { php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata)); memcpy(clisockdata, sock, sizeof(*clisockdata)); clisockdata->socket = clisock; #ifdef __linux__ /* O_NONBLOCK is not inherited on Linux */ clisockdata->is_blocked = 1; #endif xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); if (xparam->outputs.client) { xparam->outputs.client->ctx = stream->ctx; if (stream->ctx) { GC_ADDREF(stream->ctx); } } } return xparam->outputs.client == NULL ? -1 : 0; } static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) { php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; php_stream_xport_param *xparam; switch(option) { case PHP_STREAM_OPTION_XPORT_API: xparam = (php_stream_xport_param *)ptrparam; switch(xparam->op) { case STREAM_XPORT_OP_CONNECT: case STREAM_XPORT_OP_CONNECT_ASYNC: xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam); return PHP_STREAM_OPTION_RETURN_OK; case STREAM_XPORT_OP_BIND: xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam); return PHP_STREAM_OPTION_RETURN_OK; case STREAM_XPORT_OP_ACCEPT: xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC); return PHP_STREAM_OPTION_RETURN_OK; default: /* fall through */ ; } } return php_sockop_set_option(stream, option, value, ptrparam); } PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen, const char *resourcename, size_t resourcenamelen, const char *persistent_id, int options, int flags, struct timeval *timeout, php_stream_context *context STREAMS_DC) { php_stream *stream = NULL; php_netstream_data_t *sock; const php_stream_ops *ops; /* which type of socket ? */ if (strncmp(proto, "tcp", protolen) == 0) { ops = &php_stream_socket_ops; } else if (strncmp(proto, "udp", protolen) == 0) { ops = &php_stream_udp_socket_ops; } #ifdef AF_UNIX else if (strncmp(proto, "unix", protolen) == 0) { ops = &php_stream_unix_socket_ops; } else if (strncmp(proto, "udg", protolen) == 0) { ops = &php_stream_unixdg_socket_ops; } #endif else { /* should never happen */ return NULL; } sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0); memset(sock, 0, sizeof(php_netstream_data_t)); sock->is_blocked = 1; sock->timeout.tv_sec = FG(default_socket_timeout); sock->timeout.tv_usec = 0; /* we don't know the socket until we have determined if we are binding or * connecting */ sock->socket = -1; stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+"); if (stream == NULL) { pefree(sock, persistent_id ? 1 : 0); return NULL; } return stream; }