php-src/ext/sockets/sendrecvmsg.c
KsaR 01b3fc03c3
Update http->https in license (#6945)
1. Update: http://www.php.net/license/3_01.txt to https, as there is anyway server header "Location:" to https.
2. Update few license 3.0 to 3.01 as 3.0 states "php 5.1.1, 4.1.1, and earlier".
3. In some license comments is "at through the world-wide-web" while most is without "at", so deleted.
4. fixed indentation in some files before |
2021-05-06 12:16:35 +02:00

456 lines
12 KiB
C

/*
+----------------------------------------------------------------------+
| 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. |
+----------------------------------------------------------------------+
| Authors: Gustavo Lopes <cataphract@php.net> |
+----------------------------------------------------------------------+
*/
#include <php.h>
#include "php_sockets.h"
#include "sendrecvmsg.h"
#include "conversions.h"
#include <limits.h>
#include <Zend/zend_llist.h>
#ifdef ZTS
#include <TSRM/TSRM.h>
#endif
#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024))
#define DEFAULT_BUFF_SIZE 8192
#define MAX_ARRAY_KEY_SIZE 128
#ifdef PHP_WIN32
#include "windows_common.h"
#include <Mswsock.h>
#define IPV6_RECVPKTINFO IPV6_PKTINFO
#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
#define msghdr _WSAMSG
static GUID WSARecvMsg_GUID = WSAID_WSARECVMSG;
static __declspec(thread) LPFN_WSARECVMSG WSARecvMsg = NULL;
inline ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags)
{
DWORD recvd = 0,
bytesReturned;
if (WSARecvMsg == NULL) {
int res = WSAIoctl((SOCKET) sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER,
&WSARecvMsg_GUID, sizeof(WSARecvMsg_GUID),
&WSARecvMsg, sizeof(WSARecvMsg),
&bytesReturned, NULL, NULL);
if (res != 0) {
return -1;
}
}
msg->dwFlags = (DWORD)flags;
return WSARecvMsg((SOCKET)sockfd, msg, &recvd, NULL, NULL) == 0
? (ssize_t)recvd
: -1;
}
inline ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
{
DWORD sent = 0;
return WSASendMsg((SOCKET)sockfd, (struct msghdr*)msg, (DWORD)flags, &sent, NULL, NULL) == 0
? (ssize_t)sent
: -1;
}
#endif
#define LONG_CHECK_VALID_INT(l, arg_pos) \
do { \
if ((l) < INT_MIN && (l) > INT_MAX) { \
zend_argument_value_error((arg_pos), "must be between %d and %d", INT_MIN, INT_MAX); \
RETURN_THROWS(); \
} \
} while (0)
static struct {
int initialized;
HashTable ht;
} ancillary_registry;
static void ancillary_registery_free_elem(zval *el) {
pefree(Z_PTR_P(el), 1);
}
#ifdef ZTS
static MUTEX_T ancillary_mutex;
#endif
static void init_ancillary_registry(void)
{
ancillary_reg_entry entry;
anc_reg_key key;
ancillary_registry.initialized = 1;
zend_hash_init(&ancillary_registry.ht, 32, NULL, ancillary_registery_free_elem, 1);
#define PUT_ENTRY(sizev, var_size, calc, from, to, level, type) \
entry.size = sizev; \
entry.var_el_size = var_size; \
entry.calc_space = calc; \
entry.from_array = from; \
entry.to_array = to; \
key.cmsg_level = level; \
key.cmsg_type = type; \
zend_hash_str_update_mem(&ancillary_registry.ht, (char*)&key, sizeof(key), (void*)&entry, sizeof(entry))
#if defined(IPV6_PKTINFO) && HAVE_IPV6
PUT_ENTRY(sizeof(struct in6_pktinfo), 0, 0, from_zval_write_in6_pktinfo,
to_zval_read_in6_pktinfo, IPPROTO_IPV6, IPV6_PKTINFO);
#endif
#if defined(IPV6_HOPLIMIT) && HAVE_IPV6
PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int,
to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT);
#endif
#if defined(IPV6_TCLASS) && HAVE_IPV6
PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int,
to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS);
#endif
#ifdef SO_PASSCRED
PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred,
to_zval_read_ucred, SOL_SOCKET, SCM_CREDENTIALS);
#endif
#ifdef SCM_RIGHTS
PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_fd_array,
to_zval_read_fd_array, SOL_SOCKET, SCM_RIGHTS);
#endif
}
static void destroy_ancillary_registry(void)
{
if (ancillary_registry.initialized) {
zend_hash_destroy(&ancillary_registry.ht);
ancillary_registry.initialized = 0;
}
}
ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type)
{
anc_reg_key key = { cmsg_level, msg_type };
ancillary_reg_entry *entry;
#ifdef ZTS
tsrm_mutex_lock(ancillary_mutex);
#endif
if (!ancillary_registry.initialized) {
init_ancillary_registry();
}
#ifdef ZTS
tsrm_mutex_unlock(ancillary_mutex);
#endif
if ((entry = zend_hash_str_find_ptr(&ancillary_registry.ht, (char*)&key, sizeof(key))) != NULL) {
return entry;
} else {
return NULL;
}
}
PHP_FUNCTION(socket_sendmsg)
{
zval *zsocket,
*zmsg;
zend_long flags = 0;
php_socket *php_sock;
struct msghdr *msghdr;
zend_llist *allocations;
struct err_s err = {0};
ssize_t res;
/* zmsg should be passed by ref */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "Oa|l", &zsocket, socket_ce, &zmsg, &flags) == FAILURE) {
RETURN_THROWS();
}
LONG_CHECK_VALID_INT(flags, 3);
php_sock = Z_SOCKET_P(zsocket);
ENSURE_SOCKET_VALID(php_sock);
msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_send,
sizeof(*msghdr), "msghdr", &allocations, &err);
if (err.has_error) {
err_msg_dispose(&err);
RETURN_FALSE;
}
res = sendmsg(php_sock->bsd_socket, msghdr, (int)flags);
if (res != -1) {
RETVAL_LONG((zend_long)res);
} else {
PHP_SOCKET_ERROR(php_sock, "Error in sendmsg", errno);
RETVAL_FALSE;
}
allocations_dispose(&allocations);
}
PHP_FUNCTION(socket_recvmsg)
{
zval *zsocket,
*zmsg;
zend_long flags = 0;
php_socket *php_sock;
ssize_t res;
struct msghdr *msghdr;
zend_llist *allocations;
struct err_s err = {0};
//ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
if (zend_parse_parameters(ZEND_NUM_ARGS(), "Oa|l", &zsocket, socket_ce, &zmsg, &flags) == FAILURE) {
RETURN_THROWS();
}
LONG_CHECK_VALID_INT(flags, 3);
php_sock = Z_SOCKET_P(zsocket);
ENSURE_SOCKET_VALID(php_sock);
msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_recv,
sizeof(*msghdr), "msghdr", &allocations, &err);
if (err.has_error) {
err_msg_dispose(&err);
RETURN_FALSE;
}
res = recvmsg(php_sock->bsd_socket, msghdr, (int)flags);
if (res != -1) {
zval *zres, tmp;
struct key_value kv[] = {
{KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), &res},
{0}
};
zres = to_zval_run_conversions((char *)msghdr, to_zval_read_msghdr,
"msghdr", kv, &err, &tmp);
/* we don;t need msghdr anymore; free it */
msghdr = NULL;
zval_ptr_dtor(zmsg);
if (!err.has_error) {
ZVAL_COPY_VALUE(zmsg, zres);
} else {
err_msg_dispose(&err);
ZVAL_FALSE(zmsg);
/* no need to destroy/free zres -- it's NULL in this circumstance */
assert(zres == NULL);
}
RETVAL_LONG((zend_long)res);
} else {
SOCKETS_G(last_error) = errno;
php_error_docref(NULL, E_WARNING, "Error in recvmsg [%d]: %s",
errno, sockets_strerror(errno));
RETVAL_FALSE;
}
allocations_dispose(&allocations);
}
PHP_FUNCTION(socket_cmsg_space)
{
zend_long level,
type,
n = 0;
ancillary_reg_entry *entry;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|l",
&level, &type, &n) == FAILURE) {
RETURN_THROWS();
}
LONG_CHECK_VALID_INT(level, 1);
LONG_CHECK_VALID_INT(type, 2);
LONG_CHECK_VALID_INT(n, 3);
if (n < 0) {
zend_argument_value_error(3, "must be greater than or equal to 0");
RETURN_THROWS();
}
entry = get_ancillary_reg_entry(level, type);
if (entry == NULL) {
zend_value_error("Pair level " ZEND_LONG_FMT " and/or type " ZEND_LONG_FMT " is not supported",
level, type);
RETURN_THROWS();
}
if (entry->var_el_size > 0) {
/* Leading underscore to avoid symbol collision on AIX. */
size_t _rem_size = ZEND_LONG_MAX - entry->size;
size_t n_max = _rem_size / entry->var_el_size;
size_t size = entry->size + n * entry->var_el_size;
size_t total_size = CMSG_SPACE(size);
if (n > n_max /* zend_long overflow */
|| total_size > ZEND_LONG_MAX
|| total_size < size /* align overflow */) {
zend_argument_value_error(3, "is too large");
RETURN_THROWS();
}
}
RETURN_LONG((zend_long)CMSG_SPACE(entry->size + n * entry->var_el_size));
}
#if HAVE_IPV6
int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *arg4)
{
struct err_s err = {0};
zend_llist *allocations = NULL;
void *opt_ptr;
socklen_t optlen;
int retval;
assert(level == IPPROTO_IPV6);
switch (optname) {
#ifdef IPV6_PKTINFO
case IPV6_PKTINFO:
#ifdef PHP_WIN32
if (Z_TYPE_P(arg4) == IS_ARRAY) {
php_error_docref(NULL, E_WARNING, "Windows does not "
"support sticky IPV6_PKTINFO");
return FAILURE;
} else {
/* windows has no IPV6_RECVPKTINFO, and uses IPV6_PKTINFO
* for the same effect. We define IPV6_RECVPKTINFO to be
* IPV6_PKTINFO, so assume the assume user used IPV6_RECVPKTINFO */
return 1;
}
#endif
opt_ptr = from_zval_run_conversions(arg4, php_sock, from_zval_write_in6_pktinfo,
sizeof(struct in6_pktinfo), "in6_pktinfo", &allocations, &err);
if (err.has_error) {
err_msg_dispose(&err);
return FAILURE;
}
optlen = sizeof(struct in6_pktinfo);
goto dosockopt;
#endif
}
/* we also support IPV6_TCLASS, but that can be handled by the default
* integer optval handling in the caller */
return 1;
dosockopt:
retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen);
if (retval != 0) {
PHP_SOCKET_ERROR(php_sock, "Unable to set socket option", errno);
}
allocations_dispose(&allocations);
return retval != 0 ? FAILURE : SUCCESS;
}
int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result)
{
struct err_s err = {0};
void *buffer;
socklen_t size;
int res;
to_zval_read_field *reader;
assert(level == IPPROTO_IPV6);
switch (optname) {
#ifdef IPV6_PKTINFO
case IPV6_PKTINFO:
size = sizeof(struct in6_pktinfo);
reader = &to_zval_read_in6_pktinfo;
break;
#endif
default:
return 1;
}
buffer = ecalloc(1, size);
res = getsockopt(php_sock->bsd_socket, level, optname, buffer, &size);
if (res != 0) {
PHP_SOCKET_ERROR(php_sock, "unable to get socket option", errno);
} else {
zval tmp;
zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo",
empty_key_value_list, &err, &tmp);
if (err.has_error) {
err_msg_dispose(&err);
res = -1;
} else {
ZVAL_COPY_VALUE(result, zv);
}
}
efree(buffer);
return res == 0 ? SUCCESS : FAILURE;
}
#endif /* HAVE_IPV6 */
void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS)
{
/* IPv6 ancillary data */
#if defined(IPV6_RECVPKTINFO) && HAVE_IPV6
REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT);
#endif
#if defined(IPV6_RECVHOPLIMIT) && HAVE_IPV6
REGISTER_LONG_CONSTANT("IPV6_RECVHOPLIMIT", IPV6_RECVHOPLIMIT, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IPV6_HOPLIMIT", IPV6_HOPLIMIT, CONST_CS | CONST_PERSISTENT);
#endif
/* would require some effort:
REGISTER_LONG_CONSTANT("IPV6_RECVRTHDR", IPV6_RECVRTHDR, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IPV6_RECVHOPOPTS", IPV6_RECVHOPOPTS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IPV6_RECVDSTOPTS", IPV6_RECVDSTOPTS, CONST_CS | CONST_PERSISTENT);
*/
#if defined(IPV6_RECVTCLASS) && HAVE_IPV6
REGISTER_LONG_CONSTANT("IPV6_RECVTCLASS", IPV6_RECVTCLASS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, CONST_CS | CONST_PERSISTENT);
#endif
/*
REGISTER_LONG_CONSTANT("IPV6_RTHDR", IPV6_RTHDR, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IPV6_HOPOPTS", IPV6_HOPOPTS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IPV6_DSTOPTS", IPV6_DSTOPTS, CONST_CS | CONST_PERSISTENT);
*/
#ifdef SCM_RIGHTS
REGISTER_LONG_CONSTANT("SCM_RIGHTS", SCM_RIGHTS, CONST_CS | CONST_PERSISTENT);
#endif
#ifdef SO_PASSCRED
REGISTER_LONG_CONSTANT("SCM_CREDENTIALS", SCM_CREDENTIALS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SO_PASSCRED", SO_PASSCRED, CONST_CS | CONST_PERSISTENT);
#endif
#ifdef ZTS
ancillary_mutex = tsrm_mutex_alloc();
#endif
}
void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS)
{
#ifdef ZTS
tsrm_mutex_free(ancillary_mutex);
#endif
destroy_ancillary_registry();
}