From b09f5a4f56348a2f0b12f3d1a15b880845aca9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schl=C3=BCter?= Date: Fri, 1 Feb 2013 12:31:07 +0100 Subject: [PATCH 01/55] Fix typo in error message --- ext/snmp/snmp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/snmp/snmp.c b/ext/snmp/snmp.c index 926b666955f..037fcce825b 100644 --- a/ext/snmp/snmp.c +++ b/ext/snmp/snmp.c @@ -1135,7 +1135,7 @@ static int netsnmp_session_init(php_snmp_session **session_p, int version, char } *pptr = '\0'; } else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "mailformed IPv6 address, closing square bracket missing"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "malformed IPv6 address, closing square bracket missing"); return (-1); } } else { /* IPv4 address */ From ac18c318dc4af08074a56cf706678a2457495944 Mon Sep 17 00:00:00 2001 From: Anatoliy Belsky Date: Fri, 1 Feb 2013 12:43:26 +0100 Subject: [PATCH 02/55] fix enchant compilation under vc11 --- ext/enchant/enchant.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/enchant/enchant.c b/ext/enchant/enchant.c index 3d50cb80a63..e4b42637663 100644 --- a/ext/enchant/enchant.c +++ b/ext/enchant/enchant.c @@ -23,10 +23,10 @@ #include "config.h" #endif -#include #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" +#include #include "php_enchant.h" typedef EnchantBroker * EnchantBrokerPtr; From 3ee20e450fb5e52f68174f982fbd4f13eb2d9beb Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 1 Feb 2013 18:33:26 +0100 Subject: [PATCH 03/55] Fix segfault when cloning generator with properties Rule of thumb: Always implement the object clone handler rather than the object storage clone handler. Actually I think we should drop the latter. It's nearly never usable. --- .../generators/clone_with_properties.phpt | 18 +++++++++++++ Zend/zend_generators.c | 26 ++++++++++++++----- 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/generators/clone_with_properties.phpt diff --git a/Zend/tests/generators/clone_with_properties.phpt b/Zend/tests/generators/clone_with_properties.phpt new file mode 100644 index 00000000000..900253c6822 --- /dev/null +++ b/Zend/tests/generators/clone_with_properties.phpt @@ -0,0 +1,18 @@ +--TEST-- +Tests cloning a generator with properties +--FILE-- +prop = 'val'; + +$g2 = clone $g1; +unset($g1); + +var_dump($g2->prop); + +?> +--EXPECT-- +string(3) "val" diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index e8787d5e0df..621320614ca 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -27,6 +27,8 @@ ZEND_API zend_class_entry *zend_ce_generator; static zend_object_handlers zend_generator_handlers; +static zend_object_value zend_generator_create(zend_class_entry *class_type TSRMLS_DC); + ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished_execution TSRMLS_DC) /* {{{ */ { if (generator->value) { @@ -188,10 +190,19 @@ static void zend_generator_free_storage(zend_generator *generator TSRMLS_DC) /* } /* }}} */ -static void zend_generator_clone_storage(zend_generator *orig, zend_generator **clone_ptr TSRMLS_DC) /* {{{ */ +static zend_object_value zend_generator_clone(zval *object TSRMLS_DC) /* {{{ */ { - zend_generator *clone = emalloc(sizeof(zend_generator)); - memcpy(clone, orig, sizeof(zend_generator)); + zend_generator *orig = zend_object_store_get_object(object TSRMLS_CC); + zend_object_value clone_val = zend_generator_create(Z_OBJCE_P(object) TSRMLS_CC); + zend_generator *clone = zend_object_store_get_object_by_handle(clone_val.handle TSRMLS_CC); + + zend_objects_clone_members( + &clone->std, clone_val, &orig->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC + ); + + clone->execute_data = orig->execute_data; + clone->largest_used_integer_key = orig->largest_used_integer_key; + clone->flags = orig->flags; if (orig->execute_data) { /* Create a few shorter aliases to the old execution data */ @@ -335,14 +346,16 @@ static void zend_generator_clone_storage(zend_generator *orig, zend_generator ** /* The value and key are known not to be references, so simply add refs */ if (orig->value) { + clone->value = orig->value; Z_ADDREF_P(orig->value); } if (orig->key) { + clone->key = orig->key; Z_ADDREF_P(orig->key); } - *clone_ptr = clone; + return clone_val; } /* }}} */ @@ -362,8 +375,7 @@ static zend_object_value zend_generator_create(zend_class_entry *class_type TSRM object.handle = zend_objects_store_put(generator, (zend_objects_store_dtor_t) zend_generator_dtor_storage, (zend_objects_free_object_storage_t) zend_generator_free_storage, - (zend_objects_store_clone_t) zend_generator_clone_storage - TSRMLS_CC + NULL TSRMLS_CC ); object.handlers = &zend_generator_handlers; @@ -863,7 +875,7 @@ void zend_register_generator_ce(TSRMLS_D) /* {{{ */ memcpy(&zend_generator_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); zend_generator_handlers.get_constructor = zend_generator_get_constructor; - zend_generator_handlers.clone_obj = zend_objects_store_clone_obj; + zend_generator_handlers.clone_obj = zend_generator_clone; } /* }}} */ From 0cea9e6843384c6c0ebb52047c42b0431a4f5660 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Fri, 1 Feb 2013 19:23:25 +0100 Subject: [PATCH 04/55] Fixed bug #64128 buit-in web server is broken on ppc64. fdset management using bit operator is broken on non-x86 arch and cause built-in server the enter an infinite loop of "select" and never handle any request. --- NEWS | 3 +++ sapi/cli/php_cli_server.c | 30 +++++++++++++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/NEWS b/NEWS index 2fc746e3fe2..2aff180fa9b 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? 2012, PHP 5.4.13 +- CLI server: + . Fixed bug #64128 (buit-in web server is broken on ppc64). (Remi) + ?? ??? 2012, PHP 5.4.12 - Core: diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 28aba198f54..6a4e7c53ab7 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -710,10 +710,9 @@ static void php_cli_server_poller_remove(php_cli_server_poller *poller, int mode if (fd == poller->max_fd) { while (fd > 0) { fd--; - if (((unsigned int *)&poller->rfds)[fd / (8 * sizeof(unsigned int))] || ((unsigned int *)&poller->wfds)[fd / (8 * sizeof(unsigned int))]) { + if (PHP_SAFE_FD_ISSET(fd, &poller->rfds) || PHP_SAFE_FD_ISSET(fd, &poller->wfds)) { break; } - fd -= fd % (8 * sizeof(unsigned int)); } poller->max_fd = fd; } @@ -772,23 +771,20 @@ static int php_cli_server_poller_iter_on_active(php_cli_server_poller *poller, v } #else - php_socket_t fd = 0; + php_socket_t fd; const php_socket_t max_fd = poller->max_fd; - const unsigned int *pr = (unsigned int *)&poller->active.rfds, - *pw = (unsigned int *)&poller->active.wfds, - *e = pr + (max_fd + (8 * sizeof(unsigned int)) - 1) / (8 * sizeof(unsigned int)); - unsigned int mask; - while (pr < e && fd <= max_fd) { - for (mask = 1; mask; mask <<= 1, fd++) { - int events = (*pr & mask ? POLLIN: 0) | (*pw & mask ? POLLOUT: 0); - if (events) { - if (SUCCESS != callback(opaque, fd, events)) { - retval = FAILURE; - } - } + + for (fd=0 ; fd<=max_fd ; fd++) { + if (PHP_SAFE_FD_ISSET(fd, &poller->active.rfds)) { + if (SUCCESS != callback(opaque, fd, POLLIN)) { + retval = FAILURE; + } + } + if (PHP_SAFE_FD_ISSET(fd, &poller->active.wfds)) { + if (SUCCESS != callback(opaque, fd, POLLOUT)) { + retval = FAILURE; + } } - pr++; - pw++; } #endif return retval; From 2117b8edd1efd9940da8e5c4549e0ce13e92894c Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Fri, 1 Feb 2013 19:27:10 +0100 Subject: [PATCH 05/55] NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index f26806d916f..14c77d022e3 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,15 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? 201?, PHP 5.5.0 Beta 1 + - Core: . Fixed bug #60833 (self, parent, static behave inconsistently case-sensitive). (Stas, mario at include-once dot org) . Implemented FR #60524 (specify temp dir by php.ini). (ALeX Kazik). +- CLI server: + . Fixed bug #64128 (buit-in web server is broken on ppc64). (Remi) + - cURL: . Implemented FR #46439 - added CURLFile for safer file uploads. (Stas) From 114245c1b9cf153583c918e130faccc1f61d3ba5 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 1 Feb 2013 19:48:05 +0100 Subject: [PATCH 06/55] Fix bug #63830: Segfault on undefined function call in nested generator This also reverses the destruction order of the pushed arguments to align with how it is done everywhere else. I'm not exactly sure whether this is the right way to fix it, but it seems to work fine. --- NEWS | 2 ++ .../generators/nested_calls_with_die.phpt | 30 +++++++++++++++++++ Zend/zend_generators.c | 14 ++++++--- 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/generators/nested_calls_with_die.phpt diff --git a/NEWS b/NEWS index 14c77d022e3..e79cffed237 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,8 @@ PHP NEWS . Fixed bug #60833 (self, parent, static behave inconsistently case-sensitive). (Stas, mario at include-once dot org) . Implemented FR #60524 (specify temp dir by php.ini). (ALeX Kazik). + . Fixed bug #63830 (Segfault on undefined function call in nested generator). + (Nikita Popov) - CLI server: . Fixed bug #64128 (buit-in web server is broken on ppc64). (Remi) diff --git a/Zend/tests/generators/nested_calls_with_die.phpt b/Zend/tests/generators/nested_calls_with_die.phpt new file mode 100644 index 00000000000..f43d89ba219 --- /dev/null +++ b/Zend/tests/generators/nested_calls_with_die.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test nested calls with die() in a generator +--FILE-- +rewind(); +} + +function function_with_4_args() { + function_with_3_args(4, 5, 6); +} + +function outerGen() { + function_with_4_args(0, 1, 2, 3); + yield; // force generator +} + +$outerGen = outerGen(); +$outerGen->rewind(); + +?> +--EXPECT-- +Test diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 621320614ca..c1dbee124f3 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -94,10 +94,16 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished /* Clear any backed up stack arguments */ if (generator->stack != EG(argument_stack)) { - void **stack_frame = zend_vm_stack_frame_base(execute_data); - while (generator->stack->top != stack_frame) { - zval_ptr_dtor((zval**)stack_frame); - stack_frame++; + void **ptr = generator->stack->top - 1; + void **end = zend_vm_stack_frame_base(execute_data); + + /* If the top stack element is the argument count, skip it */ + if (execute_data->function_state.arguments) { + ptr--; + } + + for (; ptr >= end; --ptr) { + zval_ptr_dtor((zval**) ptr); } } From a100c25ea4b5864adc5203b6b979e8912062540a Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 1 Feb 2013 11:12:11 +0100 Subject: [PATCH 07/55] Fix overbroad skipif include --- ext/sockets/tests/ipv6_skipif.inc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ext/sockets/tests/ipv6_skipif.inc b/ext/sockets/tests/ipv6_skipif.inc index ad8cf77b357..1f824630cb8 100644 --- a/ext/sockets/tests/ipv6_skipif.inc +++ b/ext/sockets/tests/ipv6_skipif.inc @@ -2,7 +2,5 @@ if (!defined("AF_INET6")) { die('skip no IPv6 support'); } -/* If IPv6 is supported on the platform this will error out with code 111 - Connection refused. - If IPv6 is NOT supported, $errno will be set to something else (indicating parse/getaddrinfo error) */ -@stream_socket_client('tcp://[::1]:0', $errno); -if ($errno != 111) die('skip no IPv6 support'); +if (@stream_socket_client('udp://[::1]:8888') === false) + die('skip no IPv6 support'); From 97d656fc82104d9879b59dba9b80773346ec1f61 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 1 Feb 2013 14:35:14 +0100 Subject: [PATCH 08/55] Move some declarations to sockets.c --- ext/sockets/php_sockets.h | 35 ----------------------------------- ext/sockets/sockets.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index df205e5114d..2b6700d8838 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -37,41 +37,6 @@ extern zend_module_entry sockets_module_entry; #endif #endif -PHP_MINIT_FUNCTION(sockets); -PHP_MINFO_FUNCTION(sockets); -PHP_RSHUTDOWN_FUNCTION(sockets); - -PHP_FUNCTION(socket_select); -PHP_FUNCTION(socket_create_listen); -#ifdef HAVE_SOCKETPAIR -PHP_FUNCTION(socket_create_pair); -#endif -PHP_FUNCTION(socket_accept); -PHP_FUNCTION(socket_set_nonblock); -PHP_FUNCTION(socket_set_block); -PHP_FUNCTION(socket_listen); -PHP_FUNCTION(socket_close); -PHP_FUNCTION(socket_write); -PHP_FUNCTION(socket_read); -PHP_FUNCTION(socket_getsockname); -PHP_FUNCTION(socket_getpeername); -PHP_FUNCTION(socket_create); -PHP_FUNCTION(socket_connect); -PHP_FUNCTION(socket_strerror); -PHP_FUNCTION(socket_bind); -PHP_FUNCTION(socket_recv); -PHP_FUNCTION(socket_send); -PHP_FUNCTION(socket_recvfrom); -PHP_FUNCTION(socket_sendto); -PHP_FUNCTION(socket_get_option); -PHP_FUNCTION(socket_set_option); -#ifdef HAVE_SHUTDOWN -PHP_FUNCTION(socket_shutdown); -#endif -PHP_FUNCTION(socket_last_error); -PHP_FUNCTION(socket_clear_error); -PHP_FUNCTION(socket_import_stream); - #ifndef PHP_WIN32 typedef int PHP_SOCKET; # define PHP_SOCKETS_API PHPAPI diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 43bae9f6613..1cb36cd7eb3 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -279,6 +279,41 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_import_stream, 0, 0, 1) ZEND_END_ARG_INFO() /* }}} */ +PHP_MINIT_FUNCTION(sockets); +PHP_MINFO_FUNCTION(sockets); +PHP_RSHUTDOWN_FUNCTION(sockets); + +PHP_FUNCTION(socket_select); +PHP_FUNCTION(socket_create_listen); +#ifdef HAVE_SOCKETPAIR +PHP_FUNCTION(socket_create_pair); +#endif +PHP_FUNCTION(socket_accept); +PHP_FUNCTION(socket_set_nonblock); +PHP_FUNCTION(socket_set_block); +PHP_FUNCTION(socket_listen); +PHP_FUNCTION(socket_close); +PHP_FUNCTION(socket_write); +PHP_FUNCTION(socket_read); +PHP_FUNCTION(socket_getsockname); +PHP_FUNCTION(socket_getpeername); +PHP_FUNCTION(socket_create); +PHP_FUNCTION(socket_connect); +PHP_FUNCTION(socket_strerror); +PHP_FUNCTION(socket_bind); +PHP_FUNCTION(socket_recv); +PHP_FUNCTION(socket_send); +PHP_FUNCTION(socket_recvfrom); +PHP_FUNCTION(socket_sendto); +PHP_FUNCTION(socket_get_option); +PHP_FUNCTION(socket_set_option); +#ifdef HAVE_SHUTDOWN +PHP_FUNCTION(socket_shutdown); +#endif +PHP_FUNCTION(socket_last_error); +PHP_FUNCTION(socket_clear_error); +PHP_FUNCTION(socket_import_stream); + /* {{{ sockets_functions[] */ const zend_function_entry sockets_functions[] = { From 9283b8aea4c681e39fed772543919bea4bba44a1 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 1 Feb 2013 14:39:56 +0100 Subject: [PATCH 09/55] Move & improve PHP_SOCKET_ERROR def --- ext/sockets/php_sockets.h | 14 +++++++------- ext/sockets/sockets.c | 4 ---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 2b6700d8838..3762e026aa5 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -64,13 +64,13 @@ PHP_SOCKETS_API int php_sockets_le_socket(void); #define php_sockets_le_socket_name "Socket" -/* Prototypes */ -#ifdef ilia_0 /* not needed, only causes a compiler warning */ -static int php_open_listen_sock(php_socket **php_sock, int port, int backlog TSRMLS_DC); -static int php_accept_connect(php_socket *in_sock, php_socket **new_sock, struct sockaddr *la TSRMLS_DC); -static int php_read(php_socket *sock, void *buf, size_t maxlen, int flags); -static char *php_strerror(int error TSRMLS_DC); -#endif +#define PHP_SOCKET_ERROR(socket, msg, errn) \ + do { \ + int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ + (socket)->error = _err; \ + SOCKETS_G(last_error) = _err; \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + } while (0) ZEND_BEGIN_MODULE_GLOBALS(sockets) int last_error; diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 1cb36cd7eb3..6069fc5ec72 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -117,10 +117,6 @@ static char *php_strerror(int error TSRMLS_DC); #define PHP_NORMAL_READ 0x0001 #define PHP_BINARY_READ 0x0002 -#define PHP_SOCKET_ERROR(socket,msg,errn) socket->error = errn; \ - SOCKETS_G(last_error) = errn; \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, errn, php_strerror(errn TSRMLS_CC)) - static int le_socket; #define le_socket_name php_sockets_le_socket_name From 24e380f97033b54ba0994fcf9f7c6c76111a3c93 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sat, 2 Feb 2013 12:44:00 +0100 Subject: [PATCH 10/55] Remove a Windows only warning --- main/network.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/main/network.c b/main/network.c index 4b7a8d410ae..ba2ee1c4983 100644 --- a/main/network.c +++ b/main/network.c @@ -1076,11 +1076,6 @@ PHPAPI int php_set_sock_blocking(int socketd, int block TSRMLS_DC) /* with ioctlsocket, a non-zero sets nonblocking, a zero sets blocking */ flags = !block; if (ioctlsocket(socketd, FIONBIO, &flags) == SOCKET_ERROR) { - char *error_string; - - error_string = php_socket_strerror(WSAGetLastError(), NULL, 0); - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", error_string); - efree(error_string); ret = FAILURE; } #else From 40663ede837c401212e950a3f65a7cd2885ccead Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 1 Feb 2013 14:58:35 +0100 Subject: [PATCH 11/55] Fix tests (Windows) --- .../tests/socket_import_stream-4-win.phpt | 3 - ...socket_sentto_recvfrom_ipv6_udp-win32.phpt | 62 +++++++++++++++++++ .../socket_sentto_recvfrom_ipv6_udp.phpt | 3 + 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp-win32.phpt diff --git a/ext/sockets/tests/socket_import_stream-4-win.phpt b/ext/sockets/tests/socket_import_stream-4-win.phpt index e2fc523ce58..b36764f6177 100644 --- a/ext/sockets/tests/socket_import_stream-4-win.phpt +++ b/ext/sockets/tests/socket_import_stream-4-win.phpt @@ -80,9 +80,6 @@ stream_set_blocking Warning: stream_set_blocking(): %d is not a valid stream resource in %s on line %d socket_set_block -Warning: socket_set_block(): An operation was attempted on something that is not a socket. - in %ssocket_import_stream-4-win.php on line %d - Warning: socket_set_block(): unable to set blocking mode [%d]: An operation was attempted on something that is not a socket. in %ssocket_import_stream-4-win.php on line %d diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp-win32.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp-win32.phpt new file mode 100644 index 00000000000..ec965094bcc --- /dev/null +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp-win32.phpt @@ -0,0 +1,62 @@ +--TEST-- +Test if socket_recvfrom() receives data sent by socket_sendto() via IPv6 UDP (Win32) +--SKIPIF-- + +PHP Testfest Berlin 2009-05-09 diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt index 04f62eddd37..2beb8080cdb 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt @@ -5,6 +5,9 @@ Test if socket_recvfrom() receives data sent by socket_sendto() via IPv6 UDP if (!extension_loaded('sockets')) { die('SKIP The sockets extension is not loaded.'); } +if (substr(PHP_OS, 0, 3) == 'WIN') { + die('skip Not valid for Windows'); +} require 'ipv6_skipif.inc'; --FILE-- Date: Fri, 1 Feb 2013 16:38:54 +0100 Subject: [PATCH 12/55] Fix wrong blocking state being set --- ext/sockets/sockets.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 6069fc5ec72..c6c5477967c 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1126,7 +1126,7 @@ PHP_FUNCTION(socket_set_nonblock) if (stream != NULL) { if (php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, 0, NULL) != -1) { - php_sock->blocking = 1; + php_sock->blocking = 0; RETURN_TRUE; } } From ac47448abb477be99963f0b38fe82ffe78c21a8b Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sat, 2 Feb 2013 15:43:05 +0100 Subject: [PATCH 13/55] Ignore warnings on EAGAIN/EWOULDBLOCK/EINPROGRESS See bug #63570 --- ext/sockets/php_sockets.h | 4 +++- ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt | 4 ++-- ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt | 4 ++-- ext/sockets/tests/socket_sentto_recvfrom_unix.phpt | 5 ++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 3762e026aa5..9c5dc5a4aa5 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -69,7 +69,9 @@ PHP_SOCKETS_API int php_sockets_le_socket(void); int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ (socket)->error = _err; \ SOCKETS_G(last_error) = _err; \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + if (_err != EAGAIN && _err != EWOULDBLOCK && _err != EINPROGRESS) { \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + } \ } while (0) ZEND_BEGIN_MODULE_GLOBALS(sockets) diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt index bf95044d48a..00d69a855fb 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt @@ -14,7 +14,7 @@ if (!extension_loaded('sockets')) { if (!socket_set_nonblock($socket)) { die('Unable to set nonblocking mode for socket'); } - socket_recvfrom($socket, $buf, 12, 0, $from, $port); // cause warning + var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); //false (EAGAIN - no warning) $address = '127.0.0.1'; socket_sendto($socket, '', 1, 0, $address); // cause warning if (!socket_bind($socket, $address, 1223)) { @@ -44,7 +44,7 @@ if (!extension_loaded('sockets')) { socket_close($socket); --EXPECTF-- -Warning: socket_recvfrom(): unable to recvfrom [%d]: %a in %s on line %d +bool(false) Warning: Wrong parameter count for socket_sendto() in %s on line %d diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt index 2beb8080cdb..bd079042777 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt @@ -18,7 +18,7 @@ require 'ipv6_skipif.inc'; if (!socket_set_nonblock($socket)) { die('Unable to set nonblocking mode for socket'); } - socket_recvfrom($socket, $buf, 12, 0, $from, $port); // cause warning + var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); // false (EAGAIN, no warning) $address = '::1'; socket_sendto($socket, '', 1, 0, $address); // cause warning if (!socket_bind($socket, $address, 1223)) { @@ -48,7 +48,7 @@ require 'ipv6_skipif.inc'; socket_close($socket); --EXPECTF-- -Warning: socket_recvfrom(): unable to recvfrom [11]: Resource temporarily unavailable in %s on line %d +bool(false) Warning: Wrong parameter count for socket_sendto() in %s on line %d diff --git a/ext/sockets/tests/socket_sentto_recvfrom_unix.phpt b/ext/sockets/tests/socket_sentto_recvfrom_unix.phpt index 55ad75c65e4..e25bf4df1a5 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_unix.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_unix.phpt @@ -18,7 +18,7 @@ if (!extension_loaded('sockets')) { if (!socket_set_nonblock($socket)) { die('Unable to set nonblocking mode for socket'); } - socket_recvfrom($socket, $buf, 12, 0, $from, $port); // cause warning + var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); //false (EAGAIN, no warning) $address = sprintf("/tmp/%s.sock", uniqid()); if (!socket_bind($socket, $address)) { die("Unable to bind to $address"); @@ -53,8 +53,7 @@ if (!extension_loaded('sockets')) { ?> --EXPECTF-- Warning: socket_create(): Unable to create socket [%d]: Protocol not supported in %s on line %d - -Warning: socket_recvfrom(): unable to recvfrom [%d]: Resource temporarily unavailable in %s on line %d +bool(false) Warning: socket_sendto() expects at least 5 parameters, 4 given in %s on line %d bool(false) From 5e51c851431189677aa80f7a3a863699488678cd Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 1 Nov 2012 20:38:42 +0100 Subject: [PATCH 14/55] Wrap recvmsg() and sendmsg() This introduces two new functions: int socket_recvmsg(resource $socket, array &$msghdr, int $flags) int socket_sendmsg(resource $socket, array $msghdr, int $flags) The arrays representing struct msghdr follow the native counterpart closely: structs are mapped to arrays, fields to array elements whose key is the name of the field without the prefix (e.g. "name" instead of "msg_name") and array are mapped to sequential numeric PHP arrays. Right now the only type of ancillary data supported is fot the level/type pair IPPROTO_IPV6/IPV6_PKTINFO. I also refactored out the name resolution functions and made sockets_strerror() a global function. --- ext/sockets/config.m4 | 2 +- ext/sockets/php_sockets.h | 10 + ext/sockets/sendrecvmsg.c | 1377 +++++++++++++++++++++++++++++++++++ ext/sockets/sendrecvmsg.h | 8 + ext/sockets/sockaddr_conv.c | 119 +++ ext/sockets/sockaddr_conv.h | 24 + ext/sockets/sockets.c | 204 ++---- 7 files changed, 1599 insertions(+), 145 deletions(-) create mode 100644 ext/sockets/sendrecvmsg.c create mode 100644 ext/sockets/sendrecvmsg.h create mode 100644 ext/sockets/sockaddr_conv.c create mode 100644 ext/sockets/sockaddr_conv.h diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4 index 4032621ce6b..3b5b90714b7 100644 --- a/ext/sockets/config.m4 +++ b/ext/sockets/config.m4 @@ -43,6 +43,6 @@ if test "$PHP_SOCKETS" != "no"; then AC_DEFINE(HAVE_SA_SS_FAMILY,1,[Whether you have sockaddr_storage.ss_family]) fi - PHP_NEW_EXTENSION([sockets], [sockets.c multicast.c], [$ext_shared]) + PHP_NEW_EXTENSION([sockets], [sockets.c multicast.c sockaddr_conv.c sendrecvmsg.c], [$ext_shared]) PHP_INSTALL_HEADERS([ext/sockets/], [php_sockets.h]) fi diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 9c5dc5a4aa5..9158ca49072 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -85,6 +85,16 @@ ZEND_END_MODULE_GLOBALS(sockets) #define SOCKETS_G(v) (sockets_globals.v) #endif +ZEND_EXTERN_MODULE_GLOBALS(sockets); + +char *sockets_strerror(int error TSRMLS_DC); + +#define PHP_SOCKET_ERROR(socket,msg,errn) \ + socket->error = errn; \ + SOCKETS_G(last_error) = errn; \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, errn, \ + sockets_strerror(errn TSRMLS_CC)) + #else #define phpext_sockets_ptr NULL #endif diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c new file mode 100644 index 00000000000..379af125d70 --- /dev/null +++ b/ext/sockets/sendrecvmsg.c @@ -0,0 +1,1377 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2012 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: | + | http://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 | + +----------------------------------------------------------------------+ + */ + +#include +#include "php_sockets.h" +#include "sockaddr_conv.h" +#include "sendrecvmsg.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef ZTS +#include +#endif + +#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) +#define DEFAULT_BUFF_SIZE 8192 +#define MAX_ARRAY_KEY_SIZE 128 + +#define LONG_CHECK_VALID_INT(l) \ + do { \ + if ((l) < INT_MIN && (l) > INT_MAX) { \ + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The value %ld does not fit inside " \ + "the boundaries of a native integer", (l)); \ + return; \ + } \ + } while (0) + +struct err_s { + int has_error; + char *msg; + int level; + int should_free; +}; + +typedef struct { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys, + /* common part to res_context ends here */ + allocations; + php_socket *sock; +} ser_context; + +typedef struct { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys; +} res_context; + +struct key_value { + const char *key; + unsigned key_size; + void *value; +}; +#define KEY_FILL_SOCKADDR "fill_sockaddr" +#define KEY_RECVMSG_RET "recvmsg_ret" + + +typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); +typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); + +typedef struct { + /* zval info */ + const char *name; + unsigned name_size; + int required; + + /* structure info */ + size_t field_offset; /* 0 to pass full structure, e.g. when more than + one field is to be changed; in that case the + callbacks need to know the name of the fields */ + + /* callbacks */ + from_zval_write_field *from_zval; + to_zval_read_field *to_zval; +} field_descriptor; + +typedef struct { + int cmsg_level; /* originating protocol */ + int msg_type; /* protocol-specific type */ +} anc_reg_key; + +static struct { + int initialized; + HashTable ht; +} ancillary_registry; + +typedef socklen_t (*ancillary_size)(void); + +typedef struct { + socklen_t size; /* size of native structure */ + from_zval_write_field *from_array; + to_zval_read_field *to_array; +} ancillary_reg_entry; + +/* PARAMETERS */ +static int param_get_bool(void *ctx, const char *key, int def) +{ + int **elem; + if (zend_hash_find(ctx, key, strlen(key) + 1, (void**)&elem) == SUCCESS) { + return **elem; + } else { + return def; + } +} + +/* FORWARD DECLARATIONS */ +static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type); + +static inline void *accounted_emalloc(size_t alloc_size, ser_context *ctx) +{ + void *ret = emalloc(alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} + +static inline void *accounted_ecalloc(size_t nmemb, size_t alloc_size, ser_context *ctx) +{ + void *ret = ecalloc(nmemb, alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} +static inline void *accounted_safe_ecalloc(size_t nmemb, size_t alloc_size, size_t offset, ser_context *ctx) +{ + void *ret = safe_emalloc(nmemb, alloc_size, offset); + memset(ret, '\0', nmemb * alloc_size + offset); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} + +static void do_from_to_zval_err(struct err_s *err, + zend_llist *keys, + const char *what_conv, + const char *fmt, + va_list ap) +{ + smart_str path = {0}; + const char **node; + char *user_msg; + int user_msg_size; + zend_llist_position pos; + + if (err->has_error) { + return; + } + + for (node = zend_llist_get_first_ex(keys, &pos); + node != NULL; + node = zend_llist_get_next_ex(keys, &pos)) { + smart_str_appends(&path, *node); + smart_str_appends(&path, " > "); + } + + if (path.len > 3) { + path.len -= 3; + } + smart_str_0(&path); + + user_msg_size = vspprintf(&user_msg, 0, fmt, ap); + + err->has_error = 1; + err->level = E_WARNING; + spprintf(&err->msg, 0, "error converting %s data (path: %s): %.*s", + what_conv, + path.c && path.c != '\0' ? path.c : "unavailable", + user_msg_size, user_msg); + err->should_free = 1; + + efree(user_msg); + smart_str_free_ex(&path, 0); +} +__attribute__ ((format (printf, 2, 3))) +static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); + va_end(ap); +} +__attribute__ ((format (printf, 2, 3))) +static void do_to_zval_err(res_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); + va_end(ap); +} +static void err_msg_dispose(struct err_s *err TSRMLS_DC) +{ + if (err->msg != NULL) { + php_error_docref0(NULL TSRMLS_CC, err->level, "%s", err->msg); + if (err->should_free) { + efree(err->msg); + } + } +} + + +/* Generic Aggregated conversions */ +static void from_zval_write_aggregation(const zval *container, + char *structure, + const field_descriptor *descriptors, + ser_context *ctx) +{ + const field_descriptor *descr; + zval **elem; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + } + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + if (zend_hash_find(Z_ARRVAL_P(container), + descr->name, descr->name_size, (void**)&elem) == SUCCESS) { + + if (descr->from_zval == NULL) { + do_from_zval_err(ctx, "No information on how to convert value " + "of key '%s'", descr->name); + break; + } + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->from_zval(*elem, ((char*)structure) + descr->field_offset, ctx); + zend_llist_remove_tail(&ctx->keys); + + } else if (descr->required) { + do_from_zval_err(ctx, "The key '%s' is required", descr->name); + break; + } + } +} +static void to_zval_read_aggregation(const char *structure, + zval *zarr, /* initialized array */ + const field_descriptor *descriptors, + res_context *ctx) +{ + const field_descriptor *descr; + + assert(Z_TYPE_P(zarr) == IS_ARRAY); + assert(Z_ARRVAL_P(zarr) != NULL); + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + zval *new_zv; + + if (descr->to_zval == NULL) { + do_to_zval_err(ctx, "No information on how to convert native " + "field into value for key '%s'", descr->name); + break; + } + + ALLOC_INIT_ZVAL(new_zv); + add_assoc_zval_ex(zarr, descr->name, descr->name_size, new_zv); + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->to_zval(structure + descr->field_offset, new_zv, ctx); + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for integers */ +static long from_zval_integer_common(const zval *arr_value, ser_context *ctx) +{ + long ret = 0; + zval lzval = zval_used_for_init; + + if (Z_TYPE_P(arr_value) != IS_LONG) { + ZVAL_COPY_VALUE(&lzval, arr_value); + zval_copy_ctor(&lzval); + arr_value = &lzval; + } + + switch (Z_TYPE_P(arr_value)) { + case IS_LONG: +long_case: + ret = Z_LVAL_P(arr_value); + break; + + /* if not long we're operating on lzval */ + case IS_DOUBLE: +double_case: + convert_to_long(&lzval); + goto long_case; + + case IS_OBJECT: + case IS_STRING: { + long lval; + double dval; + + convert_to_string(&lzval); + + switch (is_numeric_string(Z_STRVAL(lzval), Z_STRLEN(lzval), &lval, &dval, 0)) { + case IS_DOUBLE: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_DOUBLE; + Z_DVAL(lzval) = dval; + goto double_case; + + case IS_LONG: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_LONG; + Z_DVAL(lzval) = lval; + goto long_case; + } + + /* if we get here, we don't have a numeric string */ + do_from_zval_err(ctx, "expected an integer, but got a non numeric " + "string (possibly from a converted object): '%s'", Z_STRVAL_P(arr_value)); + break; + } + + default: + do_from_zval_err(ctx, "%s", "expected an integer, either of a PHP " + "integer type or of a convertible type"); + break; + } + + zval_dtor(&lzval); + + return ret; +} +static void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + int ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval > INT_MAX || lval < INT_MIN) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native int"); + return; + } + + ival = (int)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_unsigned(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + unsigned ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(ival) && (lval < 0 || lval > UINT_MAX)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native unsigned int"); + return; + } + + ival = (unsigned)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint32_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(uint32_t) && (lval < 0 || lval > 0xFFFFFFFF)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 32-bit integer"); + return; + } + + ival = (uint32_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint16_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > 0xFFFF) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 16-bit integer"); + return; + } + + ival = htons((uint16_t)lval); + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + sa_family_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > (sa_family_t)-1) { /* sa_family_t is unsigned */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a sa_family_t value"); + return; + } + + ival = (sa_family_t)lval; + memcpy(field, &ival, sizeof(ival)); +} + +static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +{ + int ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) +{ + unsigned ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) +{ + uint16_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ntohs(ival)); +} +static void to_zval_read_uint32(const char *data, zval *zv, res_context *ctx) +{ + uint32_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) +{ + sa_family_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} + +/* CONVERSIONS for sockaddr */ +static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) +{ + int res; + struct sockaddr_in saddr = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet_addr(&saddr, Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(inaddr, &saddr.sin_addr, sizeof saddr.sin_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in_addr *addr = (const struct in_addr *)data; + socklen_t size = INET_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv4 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in, sin_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in, sin_addr), from_zval_write_sin_addr, to_zval_read_sin_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in, sin_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {0} +}; +static void from_zval_write_sockaddr_in(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_in, ctx); +} +static void to_zval_read_sockaddr_in(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in, ctx); +} +static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) +{ + int res; + struct sockaddr_in6 saddr6 = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet6_addr(&saddr6, + Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET6 " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in6_addr *addr = (const struct in6_addr *)data; + socklen_t size = INET6_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv6 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in6[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in6, sin6_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in6, sin6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in6, sin6_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {"flowinfo", sizeof("flowinfo"), 0, offsetof(struct sockaddr_in6, sin6_flowinfo), from_zval_write_uint32, to_zval_read_uint32}, + {"scope_id", sizeof("scope_id"), 0, offsetof(struct sockaddr_in6, sin6_scope_id), from_zval_write_uint32, to_zval_read_uint32}, + {0} +}; +static void from_zval_write_sockaddr_in6(const zval *container, char *sockaddr6, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr6, descriptors_sockaddr_in6, ctx); +} +static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); +} +static void from_zval_write_sockaddr_aux(const zval *container, + struct sockaddr **sockaddr_ptr, + socklen_t *sockaddr_len, + ser_context *ctx) +{ + int family; + zval **elem; + int fill_sockaddr; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + fill_sockaddr = param_get_bool(ctx, KEY_FILL_SOCKADDR, 1); + + if (zend_hash_find(Z_ARRVAL_P(container), "family", sizeof("family"), (void**)&elem) == SUCCESS + && Z_TYPE_PP(elem) != IS_NULL) { + const char *node = "family"; + zend_llist_add_element(&ctx->keys, &node); + from_zval_write_int(*elem, (char*)&family, ctx); + zend_llist_remove_tail(&ctx->keys); + } else { + family = ctx->sock->type; + } + + switch (family) { + case AF_INET: + /* though not all OSes support sockaddr_in used in IPv6 sockets */ + if (ctx->sock->type != AF_INET && ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (number %d) is not " + "supported on this socket", family); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in), ctx); + *sockaddr_len = sizeof(struct sockaddr_in); + if (fill_sockaddr) { + from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); + } + break; + case AF_INET6: + if (ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (AF_INET6) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in6), ctx); + *sockaddr_len = sizeof(struct sockaddr_in6); + if (fill_sockaddr) { + from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); + } + break; + default: + do_from_zval_err(ctx, "%s", "the only families currently supported are " + "AF_INET and AF_INET6"); + break; + } +} +static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_context *ctx) +{ + const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; + + assert(Z_TYPE_P(zv) == IS_ARRAY); + + switch (saddr->sa_family) { + case AF_INET: + to_zval_read_sockaddr_in(sockaddr_c, zv, ctx); + break; + + case AF_INET6: + to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); + break; + + default: + do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " + "not supported", + (int)saddr->sa_family); + break; + } +} + +/* CONVERSIONS for cmsghdr */ +/* + * [ level => , type => , data => [],] + * struct cmsghdr { + * socklen_t cmsg_len; // data byte count, including header + * int cmsg_level; // originating protocol + * int cmsg_type; // protocol-specific type + * // followed by unsigned char cmsg_data[]; + * }; + */ +static void from_zval_write_control(const zval *arr, + void **control_buf, + zend_llist_element *alloc, + size_t *control_len, + size_t *offset, + ser_context *ctx) +{ + struct cmsghdr *cmsghdr; + int level, + type; + size_t req_space, + space_left; + ancillary_reg_entry *entry; + + static const field_descriptor descriptor_level[] = { + {"level", sizeof("level"), 0, 0, from_zval_write_int, 0}, + {0} + }; + static const field_descriptor descriptor_type[] = { + {"type", sizeof("type"), 0, 0, from_zval_write_int, 0}, + {0} + }; + field_descriptor descriptor_data[] = { + {"data", sizeof("data"), 0, 0, 0, 0}, + {0} + }; + + from_zval_write_aggregation(arr, (char *)&level, descriptor_level, ctx); + if (ctx->err.has_error) { + return; + } + from_zval_write_aggregation(arr, (char *)&type, descriptor_type, ctx); + if (ctx->err.has_error) { + return; + } + + entry = get_ancillary_reg_entry(level, type); + if (entry == NULL) { + do_from_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + level, type); + return; + } + + req_space = CMSG_SPACE(entry->size); + space_left = *control_len - *offset; + assert(*control_len >= *offset); + + if (space_left < req_space) { + *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); + *control_len += 2 * req_space; + memcpy(&alloc->data, *control_buf, sizeof *control_buf); + } + + cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); + cmsghdr->cmsg_level = level; + cmsghdr->cmsg_type = type; + cmsghdr->cmsg_len = CMSG_LEN(entry->size); + + descriptor_data[0].from_zval = entry->from_array; + from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); + + *offset += req_space; +} +static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + HashPosition pos; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + zval **elem; + uint32_t i; + int num_elems; + void *control_buf; + zend_llist_element *alloc; + size_t control_len, + cur_offset; + struct msghdr *msg = (struct msghdr*)msghdr_c; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + return; + } + + /* estimate each message at 20 bytes */ + control_buf = accounted_safe_ecalloc(num_elems, CMSG_SPACE(20), 0, ctx); + alloc = ctx->allocations.tail; + control_len = (size_t)num_elems * CMSG_SPACE(20); + cur_offset = 0; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + from_zval_write_control(*elem, &control_buf, alloc, &control_len, + &cur_offset, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + msg->msg_control = control_buf; + msg->msg_controllen = control_len; +} +static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; + ancillary_reg_entry *entry; + + entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); + if (entry == NULL) { + do_to_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + cmsg->cmsg_level, cmsg->cmsg_type); + return; + } + if (CMSG_LEN(entry->size) > cmsg->cmsg_len) { + do_to_zval_err(ctx, "the cmsghdr structure is unexpectedly small; " + "expected a length of at least %ld, but got %ld", + (long)CMSG_LEN(entry->size), (long)cmsg->cmsg_len); + return; + } + + entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); +} +static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + /* takes a cmsghdr, not a msghdr like from_zval_write_control */ + static const field_descriptor descriptors[] = { + {"level", sizeof("level"), 0, offsetof(struct cmsghdr, cmsg_level), 0, to_zval_read_int}, + {"type", sizeof("type"), 0, offsetof(struct cmsghdr, cmsg_type), 0, to_zval_read_int}, + {"data", sizeof("data"), 0, 0 /* cmsghdr passed */, 0, to_zval_read_cmsg_data}, + {0} + }; + + array_init_size(zv, 3); + to_zval_read_aggregation(cmsghdr_c, zv, descriptors, ctx); +} +static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_context *ctx) +{ + struct msghdr *msg = (struct msghdr *)msghdr_c; + struct cmsghdr *cmsg; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + uint32_t i = 1; + + /*if (msg->msg_flags & MSG_CTRUNC) { + php_error_docref0(NULL, E_WARNING, "The MSG_CTRUNC flag is present; will not " + "attempt to read control messages"); + ZVAL_FALSE(zv); + return; + }*/ + + array_init(zv); + + for (cmsg = CMSG_FIRSTHDR(msg); + cmsg != NULL && !ctx->err.has_error; + cmsg = CMSG_NXTHDR(msg,cmsg)) { + zval *elem; + + ALLOC_INIT_ZVAL(elem); + add_next_index_zval(zv, elem); + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + to_zval_read_control((const char *)cmsg, elem, ctx); + + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for msghdr */ +static void from_zval_write_name(const zval *zname_arr, char *msghdr_c, ser_context *ctx) +{ + struct sockaddr *sockaddr; + socklen_t sockaddr_len; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + from_zval_write_sockaddr_aux(zname_arr, &sockaddr, &sockaddr_len, ctx); + + msghdr->msg_name = sockaddr; + msghdr->msg_namelen = sockaddr_len; +} +static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx) +{ + void *name = (void*)*(void**)sockaddr_p; + if (name == NULL) { + ZVAL_NULL(zv); + } else { + array_init(zv); + to_zval_read_sockaddr_aux(name, zv, ctx); + } +} +static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + long lval; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + lval = from_zval_integer_common(elem, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > MAX_USER_BUFF_SIZE) { + do_from_zval_err(ctx, "the buffer size must be between 1 and %ld; " + "given %ld", (long)MAX_USER_BUFF_SIZE, lval); + return; + } + + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); + msghdr->msg_iov[0].iov_len = (size_t)lval; +} +static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + HashPosition pos; + int num_elem; + zval **elem; + unsigned i; + struct msghdr *msg = (struct msghdr*)msghdr_c; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + } + + num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elem == 0) { + return; + } + + msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); + msg->msg_iovlen = (size_t)num_elem; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { + size_t len; + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + zval_add_ref(elem); + convert_to_string_ex(elem); + + len = Z_STRLEN_PP(elem); + msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); + msg->msg_iov[i - 1].iov_len = len; + memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); + + zval_ptr_dtor(elem); + + zend_llist_remove_tail(&ctx->keys); + } + +} +static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + uint32_t len; + + /* controllen should be an unsigned with at least 32-bit. Let's assume + * this least common denominator + */ + from_zval_write_uint32(elem, (char*)&len, ctx); + if (!ctx->err.has_error && len == 0) { + do_from_zval_err(ctx, "controllen cannot be 0"); + } + msghdr->msg_control = accounted_emalloc(len, ctx); + msghdr->msg_controllen = len; +} +static void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"iov", sizeof("iov"), 0, 0, from_zval_write_iov_array, 0}, + {"control", sizeof("control"), 0, 0, from_zval_write_control_array, 0}, + {0} + }; + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); +} +static void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx) +{ + /* zval to struct msghdr, version for recvmsg(). It differs from the version + * for sendmsg() in that it: + * - has a buffer_size instead of an iov array; + * - has no control element; has a controllen element instead + * struct msghdr { + * void *msg_name; + * socklen_t msg_namelen; + * struct iovec *msg_iov; + * size_t msg_iovlen; + * void *msg_control; + * size_t msg_controllen; //can also be socklen_t + * int msg_flags; + * }; + */ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"buffer_size", sizeof("buffer_size"), 0, 0, from_zval_write_msghdr_buffer_size, 0}, + {"controllen", sizeof("controllen"), 1, 0, from_zval_write_controllen, 0}, + {0} + }; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + const int falsev = 0, + *falsevp = &falsev; + + if (zend_hash_add(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR), + (void*)&falsevp, sizeof(falsevp), NULL) == FAILURE) { + do_from_zval_err(ctx, "could not add fill_sockaddr; this is a bug"); + return; + } + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); + + zend_hash_del(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR)); + if (ctx->err.has_error) { + return; + } + + if (msghdr->msg_iovlen == 0) { + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)DEFAULT_BUFF_SIZE, ctx); + msghdr->msg_iov[0].iov_len = (size_t)DEFAULT_BUFF_SIZE; + } +} + +static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) +{ + const struct msghdr *msghdr = (const struct msghdr *)msghdr_c; + size_t iovlen = msghdr->msg_iovlen; + ssize_t **recvmsg_ret, + bytes_left; + uint i; + + if (iovlen > UINT_MAX) { + do_to_zval_err(ctx, "unexpectedly large value for iov_len: %lu", + (unsigned long)iovlen); + } + array_init_size(zv, (uint)iovlen); + + if (zend_hash_find(&ctx->params, KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), + (void**)&recvmsg_ret) == FAILURE) { + do_to_zval_err(ctx, "recvmsg_ret not found in params. This is a bug"); + return; + } + bytes_left = **recvmsg_ret; + + for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { + zval *elem; + size_t len = MIN(msghdr->msg_iov[i].iov_len, bytes_left); + char *buf = safe_emalloc(1, len, 1); + + MAKE_STD_ZVAL(elem); + memcpy(buf, msghdr->msg_iov[i].iov_base, len); + buf[len] = '\0'; + + ZVAL_STRINGL(elem, buf, len, 0); + add_next_index_zval(zv, elem); + bytes_left -= len; + } +} +static void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, offsetof(struct msghdr, msg_name), 0, to_zval_read_name}, + {"control", sizeof("control"), 0, 0, 0, to_zval_read_control_array}, + {"iov", sizeof("iov"), 0, 0, 0, to_zval_read_iov}, + {"flags", sizeof("flags"), 0, offsetof(struct msghdr, msg_flags), 0, to_zval_read_int}, + {0} + }; + + array_init_size(zv, 4); + + to_zval_read_aggregation(msghdr_c, zv, descriptors, ctx); +} + + +/* CONVERSIONS for struct in6_pktinfo */ +static const field_descriptor descriptors_in6_pktinfo[] = { + {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, + {0} +}; +static void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); +} +static void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 2); + + to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); +} + +/* ENTRY POINT for conversions */ +static void free_from_zval_allocation(void *alloc_ptr_ptr) +{ + efree(*(void**)alloc_ptr_ptr); +} +static void *from_zval_run_conversions(const zval *container, + php_socket *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */) +{ + ser_context ctx = {{0}}; + char *structure = NULL; + + *allocations = NULL; + + if (err->has_error) { + return NULL; + } + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); + ctx.sock = sock; + + structure = ecalloc(1, struct_size); + + zend_llist_add_element(&ctx.keys, &top_name); + zend_llist_add_element(&ctx.allocations, &structure); + + /* main call */ + writer(container, structure, &ctx); + + if (ctx.err.has_error) { + zend_llist_destroy(&ctx.allocations); /* deallocates structure as well */ + structure = NULL; + *err = ctx.err; + } else { + *allocations = emalloc(sizeof **allocations); + **allocations = ctx.allocations; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return structure; +} +static zval *to_zval_run_conversions(const char *structure, + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err) +{ + res_context ctx = {{0}, {0}}; + const struct key_value *kv; + zval *zv = NULL; + + if (err->has_error) { + return NULL; + } + + ALLOC_INIT_ZVAL(zv); + + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_add_element(&ctx.keys, &top_name); + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + for (kv = key_value_pairs; kv->key != NULL; kv++) { + zend_hash_update(&ctx.params, kv->key, kv->key_size, + (void*)&kv->value, sizeof(kv->value), NULL); + } + + /* main call */ + reader(structure, zv, &ctx); + + if (ctx.err.has_error) { + zval_ptr_dtor(&zv); + zv = NULL; + *err = ctx.err; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return zv; +} + +#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, NULL, 1); + + /* struct in6_pktinfo *pktinfo; */ + entry.size = sizeof(struct in6_pktinfo); + entry.from_array = from_zval_write_in6_pktinfo; + entry.to_array = to_zval_read_in6_pktinfo; + key.cmsg_level = IPPROTO_IPV6; + key.msg_type = IPV6_PKTINFO; + zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), + (void*)&entry, sizeof(entry), NULL); +} +static 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 (zend_hash_find(&ancillary_registry.ht, (char*)&key, sizeof(key), + (void**)&entry) == SUCCESS) { + return entry; + } else { + return NULL; + } +} + +PHP_FUNCTION(socket_sendmsg) +{ + zval *zsocket, + *zmsg; + 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() TSRMLS_CC, "ra|l", &zsocket, &zmsg, &flags) == FAILURE) { + return; + } + + LONG_CHECK_VALID_INT(flags); + + ZEND_FETCH_RESOURCE(php_sock, php_socket *, &zsocket, -1, + php_sockets_le_socket_name, php_sockets_le_socket()); + + 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 TSRMLS_CC); + RETURN_FALSE; + } + + res = sendmsg(php_sock->bsd_socket, msghdr, (int)flags); + + if (res != -1) { + zend_llist_destroy(allocations); + efree(allocations); + + RETURN_LONG((long)res); + } else { + SOCKETS_G(last_error) = errno; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "error in sendmsg [%d]: %s", + errno, sockets_strerror(errno TSRMLS_CC)); + RETURN_FALSE; + } +} + +PHP_FUNCTION(socket_recvmsg) +{ + zval *zsocket, + *zmsg; + 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() TSRMLS_CC, "ra|l", + &zsocket, &zmsg, &flags) == FAILURE) { + return; + } + + LONG_CHECK_VALID_INT(flags); + + ZEND_FETCH_RESOURCE(php_sock, php_socket *, &zsocket, -1, + php_sockets_le_socket_name, php_sockets_le_socket()); + + 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 TSRMLS_CC); + RETURN_FALSE; + } + + res = recvmsg(php_sock->bsd_socket, msghdr, (int)flags); + + if (res != -1) { + zval *zres; + 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); + + /* we don;t need msghdr anymore; free it */ + msghdr = NULL; + zend_llist_destroy(allocations); + efree(allocations); + + zval_dtor(zmsg); + if (!err.has_error) { + ZVAL_COPY_VALUE(zmsg, zres); + efree(zres); /* only shallow destruction */ + } else { + err_msg_dispose(&err TSRMLS_CC); + ZVAL_FALSE(zmsg); + /* no need to destroy/free zres -- it's NULL in this circumstance */ + assert(zres == NULL); + } + } else { + SOCKETS_G(last_error) = errno; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "error in recvmsg [%d]: %s", + errno, sockets_strerror(errno TSRMLS_CC)); + RETURN_FALSE; + } + + RETURN_LONG((long)res); +} + +PHP_FUNCTION(socket_cmsg_space) +{ + long level, + type; + ancillary_reg_entry *entry; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &level, &type) == FAILURE) { + return; + } + + LONG_CHECK_VALID_INT(level); + LONG_CHECK_VALID_INT(type); + + entry = get_ancillary_reg_entry(level, type); + if (entry == NULL) { + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The pair level %ld/type %ld is " + "not supported by PHP", level, type); + return; + } + + RETURN_LONG((long)CMSG_SPACE(entry->size)); +} + +void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) +{ + REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); + +#ifdef ZTS + ancillary_mutex = tsrm_mutex_alloc(); +#endif +} + +void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS) +{ +#ifdef ZTS + tsrm_mutex_free(ancillary_mutex); +#endif +} diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h new file mode 100644 index 00000000000..82dc456e655 --- /dev/null +++ b/ext/sockets/sendrecvmsg.h @@ -0,0 +1,8 @@ +#include + +PHP_FUNCTION(socket_sendmsg); +PHP_FUNCTION(socket_recvmsg); +PHP_FUNCTION(socket_cmsg_space); + +void _socket_sendrecvmsg_init(INIT_FUNC_ARGS); +void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); diff --git a/ext/sockets/sockaddr_conv.c b/ext/sockets/sockaddr_conv.c new file mode 100644 index 00000000000..19c61740d0c --- /dev/null +++ b/ext/sockets/sockaddr_conv.c @@ -0,0 +1,119 @@ +#include +#include +#include "php_sockets.h" + +#ifdef PHP_WIN32 +#include +#else +#include +#include +#endif + +#if HAVE_IPV6 +/* Sets addr by hostname, or by ip in string form (AF_INET6) */ +int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ +{ + struct in6_addr tmp; +#if HAVE_GETADDRINFO + struct addrinfo hints; + struct addrinfo *addrinfo = NULL; +#endif + + if (inet_pton(AF_INET6, string, &tmp)) { + memcpy(&(sin6->sin6_addr.s6_addr), &(tmp.s6_addr), sizeof(struct in6_addr)); + } else { +#if HAVE_GETADDRINFO + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET6; + hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + getaddrinfo(string, NULL, &hints, &addrinfo); + if (!addrinfo) { +#ifdef PHP_WIN32 + PHP_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); +#else + PHP_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); +#endif + return 0; + } + if (addrinfo->ai_family != PF_INET6 || addrinfo->ai_addrlen != sizeof(struct sockaddr_in6)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: Non AF_INET6 domain returned on AF_INET6 socket"); + freeaddrinfo(addrinfo); + return 0; + } + + memcpy(&(sin6->sin6_addr.s6_addr), ((struct sockaddr_in6*)(addrinfo->ai_addr))->sin6_addr.s6_addr, sizeof(struct in6_addr)); + freeaddrinfo(addrinfo); + +#else + /* No IPv6 specific hostname resolution is available on this system? */ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: getaddrinfo() not available on this system"); + return 0; +#endif + + } + + return 1; +} +/* }}} */ +#endif + +/* Sets addr by hostname, or by ip in string form (AF_INET) */ +int php_set_inet_addr(struct sockaddr_in *sin, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ +{ + struct in_addr tmp; + struct hostent *host_entry; + + if (inet_aton(string, &tmp)) { + sin->sin_addr.s_addr = tmp.s_addr; + } else { + if (! (host_entry = gethostbyname(string))) { + /* Note: < -10000 indicates a host lookup error */ +#ifdef PHP_WIN32 + PHP_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); +#else + PHP_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); +#endif + return 0; + } + if (host_entry->h_addrtype != AF_INET) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: Non AF_INET domain returned on AF_INET socket"); + return 0; + } + memcpy(&(sin->sin_addr.s_addr), host_entry->h_addr_list[0], host_entry->h_length); + } + + return 1; +} +/* }}} */ + +/* Sets addr by hostname or by ip in string form (AF_INET or AF_INET6, + * depending on the socket) */ +int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ +{ + if (php_sock->type == AF_INET) { + struct sockaddr_in t = {0}; + if (php_set_inet_addr(&t, string, php_sock TSRMLS_CC)) { + memcpy(ss, &t, sizeof t); + ss->ss_family = AF_INET; + *ss_len = sizeof(t); + return 1; + } + } +#if HAVE_IPV6 + else if (php_sock->type == AF_INET6) { + struct sockaddr_in6 t = {0}; + if (php_set_inet6_addr(&t, string, php_sock TSRMLS_CC)) { + memcpy(ss, &t, sizeof t); + ss->ss_family = AF_INET6; + *ss_len = sizeof(t); + return 1; + } + } +#endif + else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "IP address used in the context of an unexpected type of socket"); + } + return 0; +} diff --git a/ext/sockets/sockaddr_conv.h b/ext/sockets/sockaddr_conv.h new file mode 100644 index 00000000000..444d749fe73 --- /dev/null +++ b/ext/sockets/sockaddr_conv.h @@ -0,0 +1,24 @@ +#ifndef PHP_SOCKADR_CONV_H +#define PHP_SOCKADR_CONV_H + +#include +#include "php_sockets.h" + +/* + * Convert an IPv6 literal or a hostname info a sockaddr_in6. + * The IPv6 literal can be a IPv4 mapped address (like ::ffff:127.0.0.1). + * If the hostname yields no IPv6 addresses, a mapped IPv4 address may be returned (AI_V4MAPPED) + */ +int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, php_socket *php_sock TSRMLS_DC); + +/* + * Convert an IPv4 literal or a hostname into a sockaddr_in. + */ +int php_set_inet_addr(struct sockaddr_in *sin, char *string, php_socket *php_sock TSRMLS_DC); + +/* + * Calls either php_set_inet6_addr() or php_set_inet_addr(), depending on the type of the socket. + */ +int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, php_socket *php_sock TSRMLS_DC); + +#endif diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 33d8a09d7d5..8f06d96f7f7 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -86,7 +86,9 @@ # endif #endif +#include "sockaddr_conv.h" #include "multicast.h" +#include "sendrecvmsg.h" ZEND_DECLARE_MODULE_GLOBALS(sockets) static PHP_GINIT_FUNCTION(sockets); @@ -113,8 +115,6 @@ static PHP_GINIT_FUNCTION(sockets); #define PF_INET AF_INET #endif -static char *php_strerror(int error TSRMLS_DC); - #define PHP_NORMAL_READ 0x0001 #define PHP_BINARY_READ 0x0002 @@ -270,10 +270,27 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_clear_error, 0, 0, 0) ZEND_ARG_INFO(0, socket) ZEND_END_ARG_INFO() - + ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_import_stream, 0, 0, 1) ZEND_ARG_INFO(0, stream) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_sendmsg, 0, 0, 3) + ZEND_ARG_INFO(0, socket) + ZEND_ARG_INFO(0, msghdr) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_recvmsg, 0, 0, 3) + ZEND_ARG_INFO(0, socket) + ZEND_ARG_INFO(1, msghdr) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_cmsg_space, 0, 0, 2) + ZEND_ARG_INFO(0, level) + ZEND_ARG_INFO(0, type) +ZEND_END_ARG_INFO() /* }}} */ PHP_MINIT_FUNCTION(sockets); @@ -344,6 +361,9 @@ const zend_function_entry sockets_functions[] = { PHP_FE(socket_last_error, arginfo_socket_last_error) PHP_FE(socket_clear_error, arginfo_socket_clear_error) PHP_FE(socket_import_stream, arginfo_socket_import_stream) + PHP_FE(socket_sendmsg, arginfo_socket_sendmsg) + PHP_FE(socket_recvmsg, arginfo_socket_recvmsg) + PHP_FE(socket_cmsg_space, arginfo_socket_cmsg_space) /* for downwards compatability */ PHP_FALIAS(socket_getopt, socket_get_option, arginfo_socket_get_option) @@ -389,13 +409,13 @@ PHP_SOCKETS_API int php_sockets_le_socket(void) /* {{{ */ static php_socket *php_create_socket(void) /* {{{ */ { php_socket *php_sock = emalloc(sizeof *php_sock); - + php_sock->bsd_socket = -1; /* invalid socket */ php_sock->type = PF_UNSPEC; php_sock->error = 0; php_sock->blocking = 1; php_sock->zstream = NULL; - + return php_sock; } /* }}} */ @@ -552,7 +572,7 @@ static int php_read(php_socket *sock, void *buf, size_t maxlen, int flags) } /* }}} */ -static char *php_strerror(int error TSRMLS_DC) /* {{{ */ +char *sockets_strerror(int error TSRMLS_DC) /* {{{ */ { const char *buf; @@ -599,113 +619,6 @@ static char *php_strerror(int error TSRMLS_DC) /* {{{ */ } /* }}} */ -#if HAVE_IPV6 -/* Sets addr by hostname, or by ip in string form (AF_INET6) */ -static int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ -{ - struct in6_addr tmp; -#if HAVE_GETADDRINFO - struct addrinfo hints; - struct addrinfo *addrinfo = NULL; -#endif - - if (inet_pton(AF_INET6, string, &tmp)) { - memcpy(&(sin6->sin6_addr.s6_addr), &(tmp.s6_addr), sizeof(struct in6_addr)); - } else { -#if HAVE_GETADDRINFO - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = PF_INET6; - getaddrinfo(string, NULL, &hints, &addrinfo); - if (!addrinfo) { -#ifdef PHP_WIN32 - PHP_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); -#else - PHP_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); -#endif - return 0; - } - if (addrinfo->ai_family != PF_INET6 || addrinfo->ai_addrlen != sizeof(struct sockaddr_in6)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: Non AF_INET6 domain returned on AF_INET6 socket"); - freeaddrinfo(addrinfo); - return 0; - } - - memcpy(&(sin6->sin6_addr.s6_addr), ((struct sockaddr_in6*)(addrinfo->ai_addr))->sin6_addr.s6_addr, sizeof(struct in6_addr)); - freeaddrinfo(addrinfo); - -#else - /* No IPv6 specific hostname resolution is available on this system? */ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: getaddrinfo() not available on this system"); - return 0; -#endif - - } - - return 1; -} -/* }}} */ -#endif - -/* Sets addr by hostname, or by ip in string form (AF_INET) */ -static int php_set_inet_addr(struct sockaddr_in *sin, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ -{ - struct in_addr tmp; - struct hostent *host_entry; - - if (inet_aton(string, &tmp)) { - sin->sin_addr.s_addr = tmp.s_addr; - } else { - if (! (host_entry = gethostbyname(string))) { - /* Note: < -10000 indicates a host lookup error */ -#ifdef PHP_WIN32 - PHP_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); -#else - PHP_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); -#endif - return 0; - } - if (host_entry->h_addrtype != AF_INET) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: Non AF_INET domain returned on AF_INET socket"); - return 0; - } - memcpy(&(sin->sin_addr.s_addr), host_entry->h_addr_list[0], host_entry->h_length); - } - - return 1; -} -/* }}} */ - -/* Sets addr by hostname or by ip in string form (AF_INET or AF_INET6, - * depending on the socket) */ -static int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ -{ - if (php_sock->type == AF_INET) { - struct sockaddr_in t = {0}; - if (php_set_inet_addr(&t, string, php_sock TSRMLS_CC)) { - memcpy(ss, &t, sizeof t); - ss->ss_family = AF_INET; - *ss_len = sizeof(t); - return 1; - } - } -#if HAVE_IPV6 - else if (php_sock->type == AF_INET6) { - struct sockaddr_in6 t = {0}; - if (php_set_inet6_addr(&t, string, php_sock TSRMLS_CC)) { - memcpy(ss, &t, sizeof t); - ss->ss_family = AF_INET6; - *ss_len = sizeof(t); - return 1; - } - } -#endif - else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "IP address used in the context of an unexpected type of socket"); - } - return 0; -} static int php_get_if_index_from_zval(zval *val, unsigned *out TSRMLS_DC) { @@ -751,12 +664,12 @@ static int php_get_if_index_from_array(const HashTable *ht, const char *key, php_socket *sock, unsigned int *if_index TSRMLS_DC) { zval **val; - + if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { *if_index = 0; /* default: 0 */ return SUCCESS; } - + return php_get_if_index_from_zval(*val, if_index TSRMLS_CC); } @@ -765,14 +678,14 @@ static int php_get_address_from_array(const HashTable *ht, const char *key, { zval **val, *valcp; - + if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", key); return FAILURE; } valcp = *val; zval_add_ref(&valcp); - convert_to_string_ex(val); + convert_to_string_ex(val); if (!php_set_inet46_addr(ss, ss_len, Z_STRVAL_P(valcp), sock TSRMLS_CC)) { zval_ptr_dtor(&valcp); return FAILURE; @@ -854,7 +767,7 @@ PHP_MINIT_FUNCTION(sockets) #define MCAST_LEAVE_SOURCE_GROUP IP_DROP_SOURCE_MEMBERSHIP #endif #endif - + REGISTER_LONG_CONSTANT("MCAST_JOIN_GROUP", MCAST_JOIN_GROUP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MCAST_LEAVE_GROUP", MCAST_LEAVE_GROUP, CONST_CS | CONST_PERSISTENT); #ifdef HAS_MCAST_EXT @@ -887,6 +800,8 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("SOL_TCP", IPPROTO_TCP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOL_UDP", IPPROTO_UDP, CONST_CS | CONST_PERSISTENT); + _socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU); + return SUCCESS; } /* }}} */ @@ -908,6 +823,7 @@ PHP_RSHUTDOWN_FUNCTION(sockets) efree(SOCKETS_G(strerror_buf)); SOCKETS_G(strerror_buf) = NULL; } + _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU); return SUCCESS; } @@ -1049,7 +965,7 @@ PHP_FUNCTION(socket_select) if (retval == -1) { SOCKETS_G(last_error) = errno; - php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to select [%d]: %s", errno, php_strerror(errno TSRMLS_CC)); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to select [%d]: %s", errno, sockets_strerror(errno TSRMLS_CC)); RETURN_FALSE; } @@ -1118,7 +1034,7 @@ PHP_FUNCTION(socket_set_nonblock) } ZEND_FETCH_RESOURCE(php_sock, php_socket *, &arg1, -1, le_socket_name, le_socket); - + if (php_sock->zstream != NULL) { php_stream *stream; /* omit notice if resource doesn't exist anymore */ @@ -1155,7 +1071,7 @@ PHP_FUNCTION(socket_set_block) } ZEND_FETCH_RESOURCE(php_sock, php_socket *, &arg1, -1, le_socket_name, le_socket); - + /* if socket was created from a stream, give the stream a chance to take * care of the operation itself, thereby allowing it to update its internal * state */ @@ -1509,7 +1425,7 @@ PHP_FUNCTION(socket_create) if (IS_INVALID_SOCKET(php_sock)) { SOCKETS_G(last_error) = errno; - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror(errno TSRMLS_CC)); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, sockets_strerror(errno TSRMLS_CC)); efree(php_sock); RETURN_FALSE; } @@ -1542,7 +1458,7 @@ PHP_FUNCTION(socket_connect) #if HAVE_IPV6 case AF_INET6: { struct sockaddr_in6 sin6 = {0}; - + if (argc != 3) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Socket of type AF_INET6 requires 3 arguments"); RETURN_FALSE; @@ -1563,7 +1479,7 @@ PHP_FUNCTION(socket_connect) #endif case AF_INET: { struct sockaddr_in sin = {0}; - + if (argc != 3) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Socket of type AF_INET requires 3 arguments"); RETURN_FALSE; @@ -1582,7 +1498,7 @@ PHP_FUNCTION(socket_connect) case AF_UNIX: { struct sockaddr_un s_un = {0}; - + if (addr_len >= sizeof(s_un.sun_path)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Path too long"); RETURN_FALSE; @@ -1619,7 +1535,7 @@ PHP_FUNCTION(socket_strerror) return; } - RETURN_STRING(php_strerror(arg1 TSRMLS_CC), 1); + RETURN_STRING(sockets_strerror(arg1 TSRMLS_CC), 1); } /* }}} */ @@ -2011,7 +1927,7 @@ PHP_FUNCTION(socket_get_option) } } } - + /* sol_socket options and general case */ switch(optname) { case SO_LINGER: @@ -2053,7 +1969,7 @@ PHP_FUNCTION(socket_get_option) add_assoc_long(return_value, "sec", tv.tv_sec); add_assoc_long(return_value, "usec", tv.tv_usec); break; - + default: optlen = sizeof(other_val); @@ -2126,12 +2042,12 @@ mcast_req_fun: source = {0}; socklen_t glen, slen; - + mcast_sreq_fun = &php_mcast_leave_source; mcast_sreq_fun: convert_to_array_ex(arg4); opt_ht = HASH_OF(*arg4); - + if (php_get_address_from_array(opt_ht, "group", php_sock, &group, &glen TSRMLS_CC) == FAILURE) { return FAILURE; @@ -2144,7 +2060,7 @@ mcast_req_fun: &if_index TSRMLS_CC) == FAILURE) { return FAILURE; } - + retval = mcast_sreq_fun(php_sock, level, (struct sockaddr*)&group, glen, (struct sockaddr*)&source, slen, if_index TSRMLS_CC); break; @@ -2184,12 +2100,12 @@ PHP_FUNCTION(socket_set_option) HashTable *opt_ht; zval **l_onoff, **l_linger; zval **sec, **usec; - + /* Multicast */ unsigned int if_index; struct in_addr if_addr; unsigned char ipv4_mcast_ttl_lback; - + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rllZ", &arg1, &level, &optname, &arg4) == FAILURE) { return; } @@ -2265,7 +2181,7 @@ ipv4_loop_ttl: if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { RETURN_FALSE; } - + opt_ptr = &if_index; optlen = sizeof(if_index); goto dosockopt; @@ -2348,7 +2264,7 @@ ipv6_loop_hops: #endif break; } - + default: convert_to_long_ex(arg4); ov = Z_LVAL_PP(arg4); @@ -2404,7 +2320,7 @@ PHP_FUNCTION(socket_create_pair) if (socketpair(domain, type, protocol, fds_array) != 0) { SOCKETS_G(last_error) = errno; - php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to create socket pair [%d]: %s", errno, php_strerror(errno TSRMLS_CC)); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to create socket pair [%d]: %s", errno, sockets_strerror(errno TSRMLS_CC)); efree(php_sock[0]); efree(php_sock[1]); RETURN_FALSE; @@ -2521,16 +2437,16 @@ PHP_FUNCTION(socket_import_stream) return; } php_stream_from_zval(stream, &zstream); - + if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void**)&socket, 1)) { /* error supposedly already shown */ RETURN_FALSE; } - + retsock = php_create_socket(); - + retsock->bsd_socket = socket; - + /* determine family */ if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) { retsock->type = addr.ss_family; @@ -2538,7 +2454,7 @@ PHP_FUNCTION(socket_import_stream) PHP_SOCKET_ERROR(retsock, "unable to obtain socket family", errno); goto error; } - + /* determine blocking mode */ #ifndef PHP_WIN32 t = fcntl(socket, F_GETFL); @@ -2558,7 +2474,7 @@ PHP_FUNCTION(socket_import_stream) retsock->blocking = 1; } #endif - + /* hold a zval reference to the stream (holding a php_stream* directly could * also be done, but this might be slightly better if in the future we want * to provide a socket_export_stream) */ @@ -2567,10 +2483,10 @@ PHP_FUNCTION(socket_import_stream) zval_copy_ctor(retsock->zstream); Z_UNSET_ISREF_P(retsock->zstream); Z_SET_REFCOUNT_P(retsock->zstream, 1); - + php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_NONE, NULL); - + ZEND_REGISTER_RESOURCE(return_value, retsock, le_socket); return; error: From eb4b1f6d46d94772611a24c70b15e46c557caeec Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 14:03:47 +0100 Subject: [PATCH 15/55] Add test for recvmsg() --- ext/sockets/tests/recvmsg.phpt | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 ext/sockets/tests/recvmsg.phpt diff --git a/ext/sockets/tests/recvmsg.phpt b/ext/sockets/tests/recvmsg.phpt new file mode 100644 index 00000000000..30263a4fbd0 --- /dev/null +++ b/ext/sockets/tests/recvmsg.phpt @@ -0,0 +1,86 @@ +--TEST-- +recvmsg(): basic test +--SKIPIF-- + ["family" => AF_INET6, "addr" => "::1"], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(IPPROTO_IPV6, IPV6_PKTINFO), +]; +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); + +--EXPECTF-- +creating send socket +resource(%d) of type (Socket) +bool(true) +creating receive socket +resource(%d) of type (Socket) +bool(true) +int(14) +Array +( + [name] => Array + ( + [family] => %d + [addr] => ::1 + [port] => 7001 + [flowinfo] => 0 + [scope_id] => 0 + ) + + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [addr] => ::1 + [ifindex] => %d + ) + + ) + + ) + + [iov] => Array + ( + [0] => testing packet + ) + + [flags] => 0 +) From 806a6e6399568d3bfbef355992fb3d09e29a607c Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 15:02:47 +0100 Subject: [PATCH 16/55] Add IPV6_UNICAST_HOPS option constant. --- ext/sockets/sockets.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 8f06d96f7f7..b213b0a6cd9 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -800,6 +800,10 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("SOL_TCP", IPPROTO_TCP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOL_UDP", IPPROTO_UDP, CONST_CS | CONST_PERSISTENT); +#if HAVE_IPV6 + REGISTER_LONG_CONSTANT("IPV6_UNICAST_HOPS", IPV6_UNICAST_HOPS, CONST_CS | CONST_PERSISTENT); +#endif + _socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU); return SUCCESS; From b06f00477ce4f20516c6f727797f208ffaefcae9 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 17:52:13 +0100 Subject: [PATCH 17/55] Fix bug converting zval sockaddr The bug ocurred when the family was not specified but was instead guessed. --- ext/sockets/sendrecvmsg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 379af125d70..837ae237357 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -627,6 +627,7 @@ static void from_zval_write_sockaddr_aux(const zval *container, *sockaddr_len = sizeof(struct sockaddr_in); if (fill_sockaddr) { from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET; } break; case AF_INET6: @@ -639,6 +640,7 @@ static void from_zval_write_sockaddr_aux(const zval *container, *sockaddr_len = sizeof(struct sockaddr_in6); if (fill_sockaddr) { from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET6; } break; default: From b27c22d627e342687eb0c21bac8859ea1d91b54b Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 17:53:48 +0100 Subject: [PATCH 18/55] Fix bug in from_zval_write_control_array() --- ext/sockets/sendrecvmsg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 837ae237357..0489e977be4 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -792,7 +792,7 @@ static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_c } msg->msg_control = control_buf; - msg->msg_controllen = control_len; + msg->msg_controllen = cur_offset; /* not control_len, which may be larger */ } static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) { From 73ab2385cb4dd2da08be940f0f6b0ef9f06f15b8 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 17:54:31 +0100 Subject: [PATCH 19/55] Support for IPV6_HOPLIMIT and IPV6_TCLASS --- ext/sockets/sendrecvmsg.c | 43 ++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 0489e977be4..e47bd46e383 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -98,7 +98,7 @@ typedef struct { typedef struct { int cmsg_level; /* originating protocol */ - int msg_type; /* protocol-specific type */ + int cmsg_type; /* protocol-specific type */ } anc_reg_key; static struct { @@ -1194,14 +1194,32 @@ static void init_ancillary_registry(void) zend_hash_init(&ancillary_registry.ht, 32, NULL, NULL, 1); - /* struct in6_pktinfo *pktinfo; */ +#define PUT_ENTRY() \ + zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), \ + (void*)&entry, sizeof(entry), NULL) + entry.size = sizeof(struct in6_pktinfo); entry.from_array = from_zval_write_in6_pktinfo; entry.to_array = to_zval_read_in6_pktinfo; key.cmsg_level = IPPROTO_IPV6; - key.msg_type = IPV6_PKTINFO; - zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), - (void*)&entry, sizeof(entry), NULL); + key.cmsg_type = IPV6_PKTINFO; + PUT_ENTRY(); + + entry.size = sizeof(int); + entry.from_array = from_zval_write_int; + entry.to_array = to_zval_read_int; + key.cmsg_level = IPPROTO_IPV6; + key.cmsg_type = IPV6_HOPLIMIT; + PUT_ENTRY(); + + entry.size = sizeof(int); + entry.from_array = from_zval_write_int; + entry.to_array = to_zval_read_int; + key.cmsg_level = IPPROTO_IPV6; + key.cmsg_type = IPV6_TCLASS; + PUT_ENTRY(); + + } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) { @@ -1364,7 +1382,22 @@ PHP_FUNCTION(socket_cmsg_space) void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_RECVHOPLIMIT", IPV6_RECVHOPLIMIT, CONST_CS | CONST_PERSISTENT); + /* 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); + */ + REGISTER_LONG_CONSTANT("IPV6_RECVTCLASS", IPV6_RECVTCLASS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_HOPLIMIT", IPV6_HOPLIMIT, CONST_CS | CONST_PERSISTENT); + /* + 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); + */ + REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, CONST_CS | CONST_PERSISTENT); #ifdef ZTS ancillary_mutex = tsrm_mutex_alloc(); From 0f849fe2aa7c8894b2dbde57abd8a3a3aa8f764a Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sun, 4 Nov 2012 19:16:10 +0100 Subject: [PATCH 20/55] Add test and slightly tweak another --- .../{recvmsg.phpt => socket_recvmsg.phpt} | 0 .../tests/socket_sendrecvmsg_multi_msg.phpt | 103 ++++++++++++++++++ 2 files changed, 103 insertions(+) rename ext/sockets/tests/{recvmsg.phpt => socket_recvmsg.phpt} (100%) create mode 100644 ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt diff --git a/ext/sockets/tests/recvmsg.phpt b/ext/sockets/tests/socket_recvmsg.phpt similarity index 100% rename from ext/sockets/tests/recvmsg.phpt rename to ext/sockets/tests/socket_recvmsg.phpt diff --git a/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt new file mode 100644 index 00000000000..055e263f724 --- /dev/null +++ b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt @@ -0,0 +1,103 @@ +--TEST-- +sendmsg()/recvmsg(): test ability to receive multiple messages +--SKIPIF-- + [ "addr" => "::1", "port" => 3000], + "iov" => ["test ", "thing", "\n"], + "control" => [[ + "level" => IPPROTO_IPV6, + "type" => IPV6_TCLASS, + "data" => 42, + ]] +], 0); +var_dump($r); +checktimeout($s, 500); + +$data = [ + "name" => ["family" => AF_INET6, "addr" => "::1"], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(IPPROTO_IPV6, IPV6_PKTINFO) + + socket_cmsg_space(IPPROTO_IPV6, IPV6_TCLASS), +]; +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); + +--EXPECTF-- +creating send socket +resource(5) of type (Socket) +bool(true) +creating receive socket +resource(6) of type (Socket) +bool(true) +int(11) +Array +( + [name] => Array + ( + [family] => %d + [addr] => ::1 + [port] => 7001 + [flowinfo] => 0 + [scope_id] => 0 + ) + + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [addr] => ::1 + [ifindex] => %d + ) + + ) + + [1] => Array + ( + [level] => %d + [type] => %d + [data] => 42 + ) + + ) + + [iov] => Array + ( + [0] => test thing + + ) + + [flags] => 0 +) From 17540788ad8c25969f1dbd02c1a3b75a8417fe9c Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 11:36:00 +0100 Subject: [PATCH 21/55] Added missing return statements --- ext/sockets/sendrecvmsg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index e47bd46e383..3405215ef33 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -918,6 +918,7 @@ static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_conte if (Z_TYPE_P(arr) != IS_ARRAY) { do_from_zval_err(ctx, "%s", "expected an array here"); + return; } num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); @@ -964,6 +965,7 @@ static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_con from_zval_write_uint32(elem, (char*)&len, ctx); if (!ctx->err.has_error && len == 0) { do_from_zval_err(ctx, "controllen cannot be 0"); + return; } msghdr->msg_control = accounted_emalloc(len, ctx); msghdr->msg_controllen = len; From b3effa60c73922ddf4a7df3be3a0e4e5ca47f70d Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 23 Oct 2012 13:09:38 +0200 Subject: [PATCH 22/55] Improve imported socket family detection Also added constant SO_FAMILY. --- ext/sockets/sockets.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index b213b0a6cd9..863825df13f 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -748,6 +748,9 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("SO_SNDTIMEO", SO_SNDTIMEO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_RCVTIMEO", SO_RCVTIMEO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_TYPE", SO_TYPE, CONST_CS | CONST_PERSISTENT); +#ifdef SO_FAMILY + REGISTER_LONG_CONSTANT("SO_FAMILY", SO_FAMILY, CONST_CS | CONST_PERSISTENT); +#endif REGISTER_LONG_CONSTANT("SO_ERROR", SO_ERROR, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOL_SOCKET", SOL_SOCKET, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOMAXCONN", SOMAXCONN, CONST_CS | CONST_PERSISTENT); @@ -2436,6 +2439,10 @@ PHP_FUNCTION(socket_import_stream) #ifndef PHP_WIN32 int t; #endif +#ifdef SO_DOMAIN + int type; + socklen_t type_len = sizeof(type); +#endif if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream) == FAILURE) { return; @@ -2452,6 +2459,11 @@ PHP_FUNCTION(socket_import_stream) retsock->bsd_socket = socket; /* determine family */ +#ifdef SO_DOMAIN + if (getsockopt(socket, SOL_SOCKET, SO_DOMAIN, &type, &type_len) == 0) { + retsock->type = type; + } else +#endif if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) { retsock->type = addr.ss_family; } else { From 131245474bf95490cf1a1dfdb5debe5d46133522 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 14:52:48 +0100 Subject: [PATCH 23/55] Redactor to expose socket_import_file_descriptor() --- ext/sockets/php_sockets.h | 1 + ext/sockets/sockets.c | 90 ++++++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 9158ca49072..3138eb60c45 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -88,6 +88,7 @@ ZEND_END_MODULE_GLOBALS(sockets) ZEND_EXTERN_MODULE_GLOBALS(sockets); char *sockets_strerror(int error TSRMLS_DC); +php_socket *socket_import_file_descriptor(PHP_SOCKET sock TSRMLS_DC); #define PHP_SOCKET_ERROR(socket,msg,errn) \ socket->error = errn; \ diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 863825df13f..449be8f9373 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2426,6 +2426,53 @@ PHP_FUNCTION(socket_clear_error) } /* }}} */ +php_socket *socket_import_file_descriptor(PHP_SOCKET socket TSRMLS_DC) +{ +#ifdef SO_DOMAIN + int type; + socklen_t type_len = sizeof(type); +#endif + php_socket *retsock; + php_sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); +#ifndef PHP_WIN32 + int t; +#endif + + retsock = php_create_socket(); + retsock->bsd_socket = socket; + + /* determine family */ +#ifdef SO_DOMAIN + if (getsockopt(socket, SOL_SOCKET, SO_DOMAIN, &type, &type_len) == 0) { + retsock->type = type; + } else +#endif + if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) { + retsock->type = addr.ss_family; + } else { + PHP_SOCKET_ERROR(retsock, "unable to obtain socket family", errno); + goto error; + } + + /* determine blocking mode */ +#ifndef PHP_WIN32 + t = fcntl(socket, F_GETFL); + if (t == -1) { + PHP_SOCKET_ERROR(retsock, "unable to obtain blocking state", errno); + goto error; + } else { + retsock->blocking = !(t & O_NONBLOCK); + } +#endif + + return retsock; + +error: + efree(retsock); + return NULL; +} + /* {{{ proto void socket_import_stream(resource stream) Imports a stream that encapsulates a socket into a socket extension resource. */ PHP_FUNCTION(socket_import_stream) @@ -2434,15 +2481,6 @@ PHP_FUNCTION(socket_import_stream) php_stream *stream; php_socket *retsock = NULL; PHP_SOCKET socket; /* fd */ - php_sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); -#ifndef PHP_WIN32 - int t; -#endif -#ifdef SO_DOMAIN - int type; - socklen_t type_len = sizeof(type); -#endif if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream) == FAILURE) { return; @@ -2454,33 +2492,12 @@ PHP_FUNCTION(socket_import_stream) RETURN_FALSE; } - retsock = php_create_socket(); - - retsock->bsd_socket = socket; - - /* determine family */ -#ifdef SO_DOMAIN - if (getsockopt(socket, SOL_SOCKET, SO_DOMAIN, &type, &type_len) == 0) { - retsock->type = type; - } else -#endif - if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) { - retsock->type = addr.ss_family; - } else { - PHP_SOCKET_ERROR(retsock, "unable to obtain socket family", errno); - goto error; + retsock = socket_import_file_descriptor(socket); + if (retsock == NULL) { + RETURN_FALSE; } - /* determine blocking mode */ -#ifndef PHP_WIN32 - t = fcntl(socket, F_GETFL); - if(t == -1) { - PHP_SOCKET_ERROR(retsock, "unable to obtain blocking state", errno); - goto error; - } else { - retsock->blocking = !(t & O_NONBLOCK); - } -#else +#ifdef PHP_WIN32 /* on windows, check if the stream is a socket stream and read its * private data; otherwise assume it's in non-blocking mode */ if (php_stream_is(stream, PHP_STREAM_IS_SOCKET)) { @@ -2504,11 +2521,6 @@ PHP_FUNCTION(socket_import_stream) PHP_STREAM_BUFFER_NONE, NULL); ZEND_REGISTER_RESOURCE(return_value, retsock, le_socket); - return; -error: - if (retsock != NULL) - efree(retsock); - RETURN_FALSE; } /* }}} */ From a85d7f28f69fbc522ed90aee1926d3733be7620d Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 00:38:23 +0100 Subject: [PATCH 24/55] Added support for AF_UNIX messages Added constants: SCM_RIGHTS, SCM_CREDENTIALS and SO_PASSCRED. The function socket_cmsg_space() was modified to support message types with variable size. Its new signature is: int socket_cmsg_space(int $level, int $type, int $n) where $n is the number of repetable elements that the message is composed of. --- ext/sockets/sendrecvmsg.c | 420 ++++++++++++++++++++++++++++++++------ 1 file changed, 363 insertions(+), 57 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 3405215ef33..f6a71dbefc7 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -75,10 +76,12 @@ struct key_value { }; #define KEY_FILL_SOCKADDR "fill_sockaddr" #define KEY_RECVMSG_RET "recvmsg_ret" +#define KEY_CMSG_LEN "cmsg_len" typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); +typedef size_t (calculate_req_space)(const zval *value, ser_context *ctx); typedef struct { /* zval info */ @@ -110,6 +113,8 @@ typedef socklen_t (*ancillary_size)(void); typedef struct { socklen_t size; /* size of native structure */ + socklen_t var_el_size; /* size of repeatable component */ + calculate_req_space *calc_space; from_zval_write_field *from_array; to_zval_read_field *to_array; } ancillary_reg_entry; @@ -218,6 +223,33 @@ static void err_msg_dispose(struct err_s *err TSRMLS_DC) } } +static unsigned from_array_iterate(const zval *arr, + void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), + void **args, + ser_context *ctx) +{ + HashPosition pos; + unsigned i; + zval **elem; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 1; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos), i++) { + if (snprintf(buf, sizeof(buf), "element #%u", i) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + func(elem, i, args, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + return i -1; +} /* Generic Aggregated conversions */ static void from_zval_write_aggregation(const zval *container, @@ -436,6 +468,53 @@ static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_co ival = (sa_family_t)lval; memcpy(field, &ival, sizeof(ival)); } +static void from_zval_write_pid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + pid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || (pid_t)lval != lval) { /* pid_t is signed */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a pid_t value"); + return; + } + + ival = (pid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + /* uid_t can be signed or unsigned (generally unsigned) */ + if ((uid_t)-1 > (uid_t)0) { + if (sizeof(long) > sizeof(uid_t) && (lval < 0 || (uid_t)lval != lval)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } else { + if (sizeof(long) > sizeof(uid_t) && (uid_t)lval != lval) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } + + ival = (uid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) { @@ -472,6 +551,20 @@ static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) ZVAL_LONG(zv, (long)ival); } +static void to_zval_read_pid_t(const char *data, zval *zv, res_context *ctx) +{ + pid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_uid_t(const char *data, zval *zv, res_context *ctx) +{ + uid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} /* CONVERSIONS for sockaddr */ static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) @@ -589,6 +682,53 @@ static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *c { to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); } +static void from_zval_write_sun_path(const zval *path, char *sockaddr_un_c, ser_context *ctx) +{ + zval lzval = zval_used_for_init; + struct sockaddr_un *saddr = (struct sockaddr_un*)sockaddr_un_c; + + if (Z_TYPE_P(path) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, path); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + path = &lzval; + } + + if (Z_STRLEN_P(path) >= sizeof(saddr->sun_path)) { + do_from_zval_err(ctx, "the path is too long, the maximum permitted " + "length is %ld", sizeof(saddr->sun_path) - 1); + return; + } + + memcpy(&saddr->sun_path, Z_STRVAL_P(path), Z_STRLEN_P(path)); + saddr->sun_path[Z_STRLEN_P(path)] = '\0'; + + zval_dtor(&lzval); +} +static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) { + struct sockaddr_un *saddr = (struct sockaddr_un*)data; + char *nul_pos; + + nul_pos = memchr(&saddr->sun_path, '\0', sizeof(saddr->sun_path)); + if (nul_pos == NULL) { + do_to_zval_err(ctx, "could not find a NUL in the path"); + return; + } + + ZVAL_STRINGL(zv, saddr->sun_path, nul_pos - (char*)&saddr->sun_path, 1); +} +static const field_descriptor descriptors_sockaddr_un[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"path", sizeof("path"), 0, offsetof(struct sockaddr_un, sun_path), from_zval_write_sun_path, to_zval_read_sun_path}, +}; +static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_un, ctx); +} +static void to_zval_read_sockaddr_un(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_un, ctx); +} static void from_zval_write_sockaddr_aux(const zval *container, struct sockaddr **sockaddr_ptr, socklen_t *sockaddr_len, @@ -630,6 +770,7 @@ static void from_zval_write_sockaddr_aux(const zval *container, (*sockaddr_ptr)->sa_family = AF_INET; } break; + case AF_INET6: if (ctx->sock->type != AF_INET6) { do_from_zval_err(ctx, "the specified family (AF_INET6) is not " @@ -643,9 +784,24 @@ static void from_zval_write_sockaddr_aux(const zval *container, (*sockaddr_ptr)->sa_family = AF_INET6; } break; + + case AF_UNIX: + if (ctx->sock->type != AF_UNIX) { + do_from_zval_err(ctx, "the specified family (AF_UNIX) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_un), ctx); + *sockaddr_len = sizeof(struct sockaddr_un); + if (fill_sockaddr) { + from_zval_write_sockaddr_un(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_UNIX; + } + break; + default: do_from_zval_err(ctx, "%s", "the only families currently supported are " - "AF_INET and AF_INET6"); + "AF_INET, AF_INET6 and AF_UNIX"); break; } } @@ -653,7 +809,12 @@ static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_cont { const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; - assert(Z_TYPE_P(zv) == IS_ARRAY); + if (saddr->sa_family == 0) { + ZVAL_NULL(zv); + return; + } + + array_init(zv); switch (saddr->sa_family) { case AF_INET: @@ -664,6 +825,10 @@ static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_cont to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); break; + case AF_UNIX: + to_zval_read_sockaddr_un(sockaddr_c, zv, ctx); + break; + default: do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " "not supported", @@ -725,7 +890,14 @@ static void from_zval_write_control(const zval *arr, return; } - req_space = CMSG_SPACE(entry->size); + if (entry->calc_space) { + entry->calc_space(arr, ctx); + if (ctx->err.has_error) { + return; + } + } else { + req_space = CMSG_SPACE(entry->size); + } space_left = *control_len - *offset; assert(*control_len >= *offset); @@ -796,8 +968,10 @@ static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_c } static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) { - const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; - ancillary_reg_entry *entry; + const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; + ancillary_reg_entry *entry; + size_t len, + *len_p = &len; entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); if (entry == NULL) { @@ -812,7 +986,16 @@ static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context return; } + len = (size_t)cmsg->cmsg_len; /* use another var because type of cmsg_len varies */ + if (zend_hash_add(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + &len_p, sizeof(len_p), NULL) == FAILURE) { + do_to_zval_err(ctx, "%s", "could not set parameter " KEY_CMSG_LEN); + return; + } + entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); + + zend_hash_del(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN)); } static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) { @@ -881,7 +1064,6 @@ static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx if (name == NULL) { ZVAL_NULL(zv); } else { - array_init(zv); to_zval_read_sockaddr_aux(name, zv, ctx); } } @@ -906,15 +1088,25 @@ static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); msghdr->msg_iov[0].iov_len = (size_t)lval; } +static void from_zval_write_iov_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + struct msghdr *msg = args[0]; + size_t len; + + zval_add_ref(elem); + convert_to_string_ex(elem); + + len = Z_STRLEN_PP(elem); + msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); + msg->msg_iov[i - 1].iov_len = len; + memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); + + zval_ptr_dtor(elem); +} static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) { - HashPosition pos; int num_elem; - zval **elem; - unsigned i; struct msghdr *msg = (struct msghdr*)msghdr_c; - char buf[sizeof("element #4294967295")]; - char *bufp = buf; if (Z_TYPE_P(arr) != IS_ARRAY) { do_from_zval_err(ctx, "%s", "expected an array here"); @@ -929,30 +1121,7 @@ static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_conte msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); msg->msg_iovlen = (size_t)num_elem; - for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; - !ctx->err.has_error - && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; - zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { - size_t len; - - if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { - memcpy(buf, "element", sizeof("element")); - } - zend_llist_add_element(&ctx->keys, &bufp); - - zval_add_ref(elem); - convert_to_string_ex(elem); - - len = Z_STRLEN_PP(elem); - msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); - msg->msg_iov[i - 1].iov_len = len; - memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); - - zval_ptr_dtor(elem); - - zend_llist_remove_tail(&ctx->keys); - } - + from_array_iterate(arr, from_zval_write_iov_array_aux, (void**)&msg, ctx); } static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) { @@ -1096,6 +1265,118 @@ static void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ct to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); } +/* CONVERSIONS for struct ucred */ +static const field_descriptor descriptors_ucred[] = { + {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, + {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, + /* assume the type gid_t is the same as uid_t: */ + {"gid", sizeof("gid"), 1, offsetof(struct ucred, gid), from_zval_write_uid_t, to_zval_read_uid_t}, + {0} +}; +static void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, ucred_c, descriptors_ucred, ctx); +} +static void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 3); + + to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); +} + +/* CONVERSIONS for SCM_RIGHTS */ +static size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) +{ + int num_elems; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return (size_t)-1; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + do_from_zval_err(ctx, "%s", "expected at least one element in this array"); + return (size_t)-1; + } + + return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); +} +static void from_zval_write_int_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + int *iarr = args[0]; + + if (Z_TYPE_PP(elem) == IS_LONG) { + + from_zval_write_int(*elem, (char*)&iarr[i], ctx); + + } else if (Z_TYPE_PP(elem) == IS_RESOURCE) { + php_stream *stream; + php_socket *sock; + + ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, + php_sockets_le_socket_name, php_sockets_le_socket()); + if (sock) { + iarr[i] = sock->bsd_socket; + return; + } + + ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, + "stream", php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + do_from_zval_err(ctx, "resource is not a stream or a socket"); + return; + } + + if (php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&iarr[i], + REPORT_ERRORS) == FAILURE) { + do_from_zval_err(ctx, "cast stream to file descriptor failed"); + return; + } + } else { + do_from_zval_err(ctx, "expected an integer or resource variable"); + } +} +static void from_zval_write_int_array(const zval *arr, char *int_arr, ser_context *ctx) +{ + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + from_array_iterate(arr, &from_zval_write_int_array_aux, (void**)&int_arr, ctx); +} +static void to_zval_read_int_array(const char *data, zval *zv, res_context *ctx) +{ + size_t **cmsg_len; + int num_elems, + i; + struct cmsghdr *dummy_cmsg = 0; + size_t data_offset; + + data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) + - (unsigned char *)dummy_cmsg; + + if (zend_hash_find(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + (void **)&cmsg_len) == FAILURE) { + do_to_zval_err(ctx, "could not get value of parameter " KEY_CMSG_LEN); + return; + } + + if (**cmsg_len < data_offset) { + do_to_zval_err(ctx, "length of cmsg is smaller than its data member " + "offset (%ld vs %ld)", (long)**cmsg_len, (long)data_offset); + return; + } + num_elems = (**cmsg_len - data_offset) / sizeof(int); + + array_init_size(zv, num_elems); + + for (i = 0; i < num_elems; i++) { + add_next_index_long(zv, (long)*((int *)data + i)); + } +} + /* ENTRY POINT for conversions */ static void free_from_zval_allocation(void *alloc_ptr_ptr) { @@ -1196,31 +1477,31 @@ static void init_ancillary_registry(void) zend_hash_init(&ancillary_registry.ht, 32, NULL, NULL, 1); -#define PUT_ENTRY() \ +#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_update(&ancillary_registry.ht, (char*)&key, sizeof(key), \ (void*)&entry, sizeof(entry), NULL) - entry.size = sizeof(struct in6_pktinfo); - entry.from_array = from_zval_write_in6_pktinfo; - entry.to_array = to_zval_read_in6_pktinfo; - key.cmsg_level = IPPROTO_IPV6; - key.cmsg_type = IPV6_PKTINFO; - PUT_ENTRY(); + PUT_ENTRY(sizeof(struct in6_pktinfo), 0, 0, from_zval_write_in6_pktinfo, + to_zval_read_in6_pktinfo, IPPROTO_IPV6, IPV6_PKTINFO); - entry.size = sizeof(int); - entry.from_array = from_zval_write_int; - entry.to_array = to_zval_read_int; - key.cmsg_level = IPPROTO_IPV6; - key.cmsg_type = IPV6_HOPLIMIT; - PUT_ENTRY(); + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, + to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); - entry.size = sizeof(int); - entry.from_array = from_zval_write_int; - entry.to_array = to_zval_read_int; - key.cmsg_level = IPPROTO_IPV6; - key.cmsg_type = IPV6_TCLASS; - PUT_ENTRY(); + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, + to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS); + PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, + to_zval_read_ucred, SOL_SOCKET, SCM_CREDENTIALS); + + PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_int_array, + to_zval_read_int_array, SOL_SOCKET, SCM_RIGHTS); } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) @@ -1361,15 +1642,24 @@ PHP_FUNCTION(socket_recvmsg) PHP_FUNCTION(socket_cmsg_space) { long level, - type; + type, + n = 0; ancillary_reg_entry *entry; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &level, &type) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|l", + &level, &type, &n) == FAILURE) { return; } LONG_CHECK_VALID_INT(level); LONG_CHECK_VALID_INT(type); + LONG_CHECK_VALID_INT(n); + + if (n < 0) { + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The third argument " + "cannot be negative"); + return; + } entry = get_ancillary_reg_entry(level, type); if (entry == NULL) { @@ -1378,11 +1668,22 @@ PHP_FUNCTION(socket_cmsg_space) return; } - RETURN_LONG((long)CMSG_SPACE(entry->size)); + if (entry->var_el_size > 0 && n > (LONG_MAX - (long)entry->size - + (long)CMSG_SPACE(0) - 15L) / entry->var_el_size) { + /* the -15 is to account for any padding CMSG_SPACE may add after the data */ + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The value for the " + "third argument (%ld) is too large", n); + return; + } + + RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->size)); } void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) { + /* IPv6 ancillary data + * Note that support for sticky options via setsockopt() is not implemented + * yet (where special support is needed, i.e., the optval is not an int). */ REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_RECVHOPLIMIT", IPV6_RECVHOPLIMIT, CONST_CS | CONST_PERSISTENT); /* would require some effort: @@ -1401,6 +1702,11 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) */ REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("SCM_RIGHTS", SCM_RIGHTS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SCM_CREDENTIALS", SCM_CREDENTIALS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SO_PASSCRED", SO_PASSCRED, CONST_CS | CONST_PERSISTENT); + #ifdef ZTS ancillary_mutex = tsrm_mutex_alloc(); #endif From 7fc4671df985ab1cedcd9b03d7bd792cc1188758 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 11:40:24 +0100 Subject: [PATCH 25/55] Add test for CMSG_CREDENTIALS message --- ext/sockets/sendrecvmsg.c | 60 ++++++++----- .../tests/socket_cmsg_credentials.phpt | 89 +++++++++++++++++++ 2 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 ext/sockets/tests/socket_cmsg_credentials.phpt diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index f6a71dbefc7..385c2322336 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -719,7 +719,8 @@ static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) } static const field_descriptor descriptors_sockaddr_un[] = { {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, - {"path", sizeof("path"), 0, offsetof(struct sockaddr_un, sun_path), from_zval_write_sun_path, to_zval_read_sun_path}, + {"path", sizeof("path"), 0, 0, from_zval_write_sun_path, to_zval_read_sun_path}, + {0} }; static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) { @@ -857,7 +858,8 @@ static void from_zval_write_control(const zval *arr, struct cmsghdr *cmsghdr; int level, type; - size_t req_space, + size_t data_len, + req_space, space_left; ancillary_reg_entry *entry; @@ -891,13 +893,14 @@ static void from_zval_write_control(const zval *arr, } if (entry->calc_space) { - entry->calc_space(arr, ctx); + data_len = entry->calc_space(arr, ctx); if (ctx->err.has_error) { return; } } else { - req_space = CMSG_SPACE(entry->size); + data_len = entry->size; } + req_space = CMSG_SPACE(data_len); space_left = *control_len - *offset; assert(*control_len >= *offset); @@ -910,7 +913,7 @@ static void from_zval_write_control(const zval *arr, cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); cmsghdr->cmsg_level = level; cmsghdr->cmsg_type = type; - cmsghdr->cmsg_len = CMSG_LEN(entry->size); + cmsghdr->cmsg_len = CMSG_LEN(data_len); descriptor_data[0].from_zval = entry->from_array; from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); @@ -1302,27 +1305,23 @@ static size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); } -static void from_zval_write_int_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) { int *iarr = args[0]; - if (Z_TYPE_PP(elem) == IS_LONG) { - - from_zval_write_int(*elem, (char*)&iarr[i], ctx); - - } else if (Z_TYPE_PP(elem) == IS_RESOURCE) { + if (Z_TYPE_PP(elem) == IS_RESOURCE) { php_stream *stream; php_socket *sock; ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, - php_sockets_le_socket_name, php_sockets_le_socket()); + NULL, php_sockets_le_socket()); if (sock) { iarr[i] = sock->bsd_socket; return; } ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, - "stream", php_file_le_stream(), php_file_le_pstream()); + NULL, php_file_le_stream(), php_file_le_pstream()); if (stream == NULL) { do_from_zval_err(ctx, "resource is not a stream or a socket"); return; @@ -1334,25 +1333,26 @@ static void from_zval_write_int_array_aux(zval **elem, unsigned i, void **args, return; } } else { - do_from_zval_err(ctx, "expected an integer or resource variable"); + do_from_zval_err(ctx, "expected a resource variable"); } } -static void from_zval_write_int_array(const zval *arr, char *int_arr, ser_context *ctx) +static void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx) { if (Z_TYPE_P(arr) != IS_ARRAY) { do_from_zval_err(ctx, "%s", "expected an array here"); return; } - from_array_iterate(arr, &from_zval_write_int_array_aux, (void**)&int_arr, ctx); + from_array_iterate(arr, &from_zval_write_fd_array_aux, (void**)&int_arr, ctx); } -static void to_zval_read_int_array(const char *data, zval *zv, res_context *ctx) +static void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) { size_t **cmsg_len; int num_elems, i; struct cmsghdr *dummy_cmsg = 0; size_t data_offset; + TSRMLS_FETCH(); data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) - (unsigned char *)dummy_cmsg; @@ -1373,7 +1373,25 @@ static void to_zval_read_int_array(const char *data, zval *zv, res_context *ctx) array_init_size(zv, num_elems); for (i = 0; i < num_elems; i++) { - add_next_index_long(zv, (long)*((int *)data + i)); + zval *elem; + int fd; + struct stat statbuf; + + MAKE_STD_ZVAL(elem); + + fd = *((int *)data + i); + + /* determine whether we have a socket */ + fstat(fd, &statbuf); + if (S_ISSOCK(statbuf.st_mode)) { + php_socket *sock = socket_import_file_descriptor(fd); + zend_register_resource(elem, sock, php_sockets_le_socket()); + } else { + php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); + php_stream_to_zval(stream, elem); + } + + add_next_index_zval(zv, elem); } } @@ -1500,8 +1518,8 @@ static void init_ancillary_registry(void) PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, to_zval_read_ucred, SOL_SOCKET, SCM_CREDENTIALS); - PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_int_array, - to_zval_read_int_array, SOL_SOCKET, 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); } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) @@ -1676,7 +1694,7 @@ PHP_FUNCTION(socket_cmsg_space) return; } - RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->size)); + RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size)); } void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) diff --git a/ext/sockets/tests/socket_cmsg_credentials.phpt b/ext/sockets/tests/socket_cmsg_credentials.phpt new file mode 100644 index 00000000000..6a1c23fa8c5 --- /dev/null +++ b/ext/sockets/tests/socket_cmsg_credentials.phpt @@ -0,0 +1,89 @@ +--TEST-- +recvmsg(): receive SCM_CREDENTIALS messages +--SKIPIF-- + ["test ", "thing", "\n"], +//], 0); +$r = socket_sendto($sends1, $msg = "dread", strlen($msg), 0, $path); +var_dump($r); +checktimeout($s, 500); + +$data = [ + "name" => [], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(SOL_SOCKET, SCM_CREDENTIALS) +]; +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); + +$pid = getmypid(); +var_dump($data['control'][0]['data']['pid'] === $pid); + +--EXPECTF-- +creating send socket +resource(%d) of type (Socket) +creating receive socket +resource(%d) of type (Socket) +bool(true) +int(5) +Array +( + [name] => + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [pid] => %d + [uid] => %d + [gid] => %d + ) + + ) + + ) + + [iov] => Array + ( + [0] => dread + ) + + [flags] => 0 +) +bool(true) From 74cf40c2fdccdfaed419482d080be4f73fb23a7e Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 15:15:36 +0100 Subject: [PATCH 26/55] Add test for CMSG_RIGHTS --- ext/sockets/tests/socket_cmsg_rights.phpt | 100 ++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 ext/sockets/tests/socket_cmsg_rights.phpt diff --git a/ext/sockets/tests/socket_cmsg_rights.phpt b/ext/sockets/tests/socket_cmsg_rights.phpt new file mode 100644 index 00000000000..8290f03880c --- /dev/null +++ b/ext/sockets/tests/socket_cmsg_rights.phpt @@ -0,0 +1,100 @@ +--TEST-- +recvmsg(): receive SCM_CREDENTIALS messages +--SKIPIF-- + [ "path" => $path ], + "iov" => ["test ", "thing", "\n"], + "control" => [ + [ + "level" => SOL_SOCKET, + "type" => SCM_RIGHTS, + "data" => [$sends1, STDIN, STDOUT, STDERR], + ] + ] +], 0); +var_dump($r); +checktimeout($s, 500); + +$data = [ + "name" => [], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(SOL_SOCKET, SCM_RIGHTS, 3) +]; +var_dump($data); +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); +--EXPECTF-- +creating send socket +resource(%d) of type (Socket) +creating receive socket +resource(%d) of type (Socket) +bool(true) +int(11) +array(3) { + ["name"]=> + array(0) { + } + ["buffer_size"]=> + int(2000) + ["controllen"]=> + int(32) +} +Array +( + [name] => + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [0] => Resource id #%d + [1] => Resource id #%d + [2] => Resource id #%d + ) + + ) + + ) + + [iov] => Array + ( + [0] => test thing + + ) + + [flags] => 0 +) From 51e65667f5dcb60af24603a543946aa258ac9003 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 16:12:21 +0100 Subject: [PATCH 27/55] Register extra MSG_* constants --- ext/sockets/sockets.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 449be8f9373..37e2e9fe98a 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -718,19 +718,38 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("SOCK_RAW", SOCK_RAW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_SEQPACKET",SOCK_SEQPACKET, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_RDM", SOCK_RDM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_OOB", MSG_OOB, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_WAITALL", MSG_WAITALL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_CTRUNC", MSG_CTRUNC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_TRUNC", MSG_TRUNC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_PEEK", MSG_PEEK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_DONTROUTE", MSG_DONTROUTE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_EOR", MSG_EOR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_EOF", MSG_EOF, CONST_CS | CONST_PERSISTENT); + +#ifdef MSG_CONFIRM + REGISTER_LONG_CONSTANT("MSG_CONFIRM", MSG_CONFIRM, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef MSG_ERRQUEUE + REGISTER_LONG_CONSTANT("MSG_ERRQUEUE", MSG_ERRQUEUE, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef MSG_NOSIGNAL + REGISTER_LONG_CONSTANT("MSG_NOSIGNAL", MSG_NOSIGNAL, CONST_CS | CONST_PERSISTENT); +#endif #ifdef MSG_DONTWAIT REGISTER_LONG_CONSTANT("MSG_DONTWAIT", MSG_DONTWAIT, CONST_CS | CONST_PERSISTENT); #endif - REGISTER_LONG_CONSTANT("MSG_PEEK", MSG_PEEK, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("MSG_DONTROUTE", MSG_DONTROUTE, CONST_CS | CONST_PERSISTENT); -#ifdef MSG_EOR - REGISTER_LONG_CONSTANT("MSG_EOR", MSG_EOR, CONST_CS | CONST_PERSISTENT); +#ifdef MSG_MORE + REGISTER_LONG_CONSTANT("MSG_MORE", MSG_MORE, CONST_CS | CONST_PERSISTENT); #endif -#ifdef MSG_EOF - REGISTER_LONG_CONSTANT("MSG_EOF", MSG_EOF, CONST_CS | CONST_PERSISTENT); +#ifdef MSG_WAITFORONE + REGISTER_LONG_CONSTANT("MSG_WAITFORONE",MSG_WAITFORONE, CONST_CS | CONST_PERSISTENT); #endif +#ifdef MSG_CMSG_CLOEXEC + REGISTER_LONG_CONSTANT("MSG_CMSG_CLOEXEC",MSG_CMSG_CLOEXEC,CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("SO_DEBUG", SO_DEBUG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_REUSEADDR", SO_REUSEADDR, CONST_CS | CONST_PERSISTENT); #ifdef SO_REUSEPORT From 190a0ed71377519425f1b33ef3b21f41064e416b Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 17:10:10 +0100 Subject: [PATCH 28/55] Fix build on Mac OS X By deactivating unsupported features on this OS. --- ext/sockets/sendrecvmsg.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 385c2322336..4436d18e841 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -1252,6 +1252,7 @@ static void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx /* CONVERSIONS for struct in6_pktinfo */ +#ifdef IPV6_PKTINFO static const field_descriptor descriptors_in6_pktinfo[] = { {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, @@ -1267,8 +1268,10 @@ static void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ct to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); } +#endif /* CONVERSIONS for struct ucred */ +#ifdef SO_PASSCRED static const field_descriptor descriptors_ucred[] = { {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, @@ -1286,8 +1289,10 @@ static void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); } +#endif /* CONVERSIONS for SCM_RIGHTS */ +#ifdef SCM_RIGHTS static size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) { int num_elems; @@ -1394,6 +1399,7 @@ static void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) add_next_index_zval(zv, elem); } } +#endif /* ENTRY POINT for conversions */ static void free_from_zval_allocation(void *alloc_ptr_ptr) @@ -1506,20 +1512,28 @@ static void init_ancillary_registry(void) zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), \ (void*)&entry, sizeof(entry), NULL) +#ifdef IPV6_PKTINFO PUT_ENTRY(sizeof(struct in6_pktinfo), 0, 0, from_zval_write_in6_pktinfo, to_zval_read_in6_pktinfo, IPPROTO_IPV6, IPV6_PKTINFO); +#endif +#ifdef IPV6_HOPLIMIT PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); +#endif PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS); +#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 ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) @@ -1702,8 +1716,14 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) /* IPv6 ancillary data * Note that support for sticky options via setsockopt() is not implemented * yet (where special support is needed, i.e., the optval is not an int). */ +#ifdef IPV6_RECVPKTINFO REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef IPV6_RECVHOPLIMIT 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); @@ -1711,8 +1731,6 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) */ REGISTER_LONG_CONSTANT("IPV6_RECVTCLASS", IPV6_RECVTCLASS, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("IPV6_HOPLIMIT", IPV6_HOPLIMIT, CONST_CS | CONST_PERSISTENT); /* REGISTER_LONG_CONSTANT("IPV6_RTHDR", IPV6_RTHDR, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_HOPOPTS", IPV6_HOPOPTS, CONST_CS | CONST_PERSISTENT); @@ -1720,10 +1738,13 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) */ REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, 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(); From 5bf7b08efd691780f421e0b4f176404fe3a80b2c Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 17:35:46 +0100 Subject: [PATCH 29/55] Check return of fstat() --- ext/sockets/sendrecvmsg.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 4436d18e841..16330e0ded0 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -1387,7 +1387,12 @@ static void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) fd = *((int *)data + i); /* determine whether we have a socket */ - fstat(fd, &statbuf); + if (fstat(fd, &statbuf) == -1) { + do_to_zval_err(ctx, "error creating resource for received file " + "descriptor %d: fstat() call failed with errno %d", fd, errno); + efree(elem); + return; + } if (S_ISSOCK(statbuf.st_mode)) { php_socket *sock = socket_import_file_descriptor(fd); zend_register_resource(elem, sock, php_sockets_le_socket()); From 3e515a2fd93204594c80ad2379f42fbb2db18d78 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 11:25:23 +0100 Subject: [PATCH 30/55] Fix mcast_ipv6_send test --- ext/sockets/tests/mcast_ipv6_send.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/sockets/tests/mcast_ipv6_send.phpt b/ext/sockets/tests/mcast_ipv6_send.phpt index b8d38bf68f4..f75bb09903c 100644 --- a/ext/sockets/tests/mcast_ipv6_send.phpt +++ b/ext/sockets/tests/mcast_ipv6_send.phpt @@ -9,8 +9,8 @@ if (!defined('IPPROTO_IPV6')) { die('skip IPv6 not available.'); } $level = IPPROTO_IPV6; -$s = socket_create($domain, SOCK_DGRAM, SOL_UDP) or die("skip Can not create socket"); -if (socket_set_option($s, $level, IP_MULTICAST_IF, 1) === false) { +$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("skip Can not create socket"); +if (socket_set_option($s, $level, IPV6_MULTICAST_IF, 1) === false) { die("skip interface 1 either doesn't exist or has no ipv6 address"); } --FILE-- From 51394f76a5fca718fbf218888d97402f845ee261 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 12:48:47 +0100 Subject: [PATCH 31/55] Move some multicast stuff to multicast.c --- ext/sockets/multicast.c | 362 +++++++++++++++++++++++++++++++++++--- ext/sockets/multicast.h | 10 ++ ext/sockets/php_sockets.h | 6 + ext/sockets/sockets.c | 276 ++--------------------------- 4 files changed, 363 insertions(+), 291 deletions(-) diff --git a/ext/sockets/multicast.c b/ext/sockets/multicast.c index d4a00a8d174..dc242693acc 100644 --- a/ext/sockets/multicast.c +++ b/ext/sockets/multicast.c @@ -54,6 +54,7 @@ #include "php_sockets.h" #include "multicast.h" +#include "sockaddr_conv.h" #include "main/php_network.h" @@ -76,6 +77,309 @@ static const char *_php_source_op_to_string(enum source_op sop); static int _php_source_op_to_ipv4_op(enum source_op sop); #endif +static int php_get_if_index_from_zval(zval *val, unsigned *out TSRMLS_DC) +{ + int ret; + + if (Z_TYPE_P(val) == IS_LONG) { + if (Z_LVAL_P(val) < 0 || Z_LVAL_P(val) > UINT_MAX) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "the interface index cannot be negative or larger than %u;" + " given %ld", UINT_MAX, Z_LVAL_P(val)); + ret = FAILURE; + } else { + *out = Z_LVAL_P(val); + ret = SUCCESS; + } + } else { +#if HAVE_IF_NAMETOINDEX + unsigned int ind; + zval_add_ref(&val); + convert_to_string_ex(&val); + ind = if_nametoindex(Z_STRVAL_P(val)); + if (ind == 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "no interface with name \"%s\" could be found", Z_STRVAL_P(val)); + ret = FAILURE; + } else { + *out = ind; + ret = SUCCESS; + } + zval_ptr_dtor(&val); +#else + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "this platform does not support looking up an interface by " + "name, an integer interface index must be supplied instead"); + ret = FAILURE; +#endif + } + + return ret; +} + +static int php_get_if_index_from_array(const HashTable *ht, const char *key, + php_socket *sock, unsigned int *if_index TSRMLS_DC) +{ + zval **val; + + if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { + *if_index = 0; /* default: 0 */ + return SUCCESS; + } + + return php_get_if_index_from_zval(*val, if_index TSRMLS_CC); +} + +static int php_get_address_from_array(const HashTable *ht, const char *key, + php_socket *sock, php_sockaddr_storage *ss, socklen_t *ss_len TSRMLS_DC) +{ + zval **val, + *valcp; + + if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", key); + return FAILURE; + } + valcp = *val; + zval_add_ref(&valcp); + convert_to_string_ex(val); + if (!php_set_inet46_addr(ss, ss_len, Z_STRVAL_P(valcp), sock TSRMLS_CC)) { + zval_ptr_dtor(&valcp); + return FAILURE; + } + zval_ptr_dtor(&valcp); + return SUCCESS; +} + +static int php_do_mcast_opt(php_socket *php_sock, int level, int optname, zval **arg4 TSRMLS_DC) +{ + HashTable *opt_ht; + unsigned int if_index; + int retval; + int (*mcast_req_fun)(php_socket *, int, struct sockaddr *, socklen_t, + unsigned TSRMLS_DC); +#ifdef HAS_MCAST_EXT + int (*mcast_sreq_fun)(php_socket *, int, struct sockaddr *, socklen_t, + struct sockaddr *, socklen_t, unsigned TSRMLS_DC); +#endif + + switch (optname) { + case MCAST_JOIN_GROUP: + mcast_req_fun = &php_mcast_join; + goto mcast_req_fun; + case MCAST_LEAVE_GROUP: + { + php_sockaddr_storage group = {0}; + socklen_t glen; + + mcast_req_fun = &php_mcast_leave; +mcast_req_fun: + convert_to_array_ex(arg4); + opt_ht = HASH_OF(*arg4); + + if (php_get_address_from_array(opt_ht, "group", php_sock, &group, + &glen TSRMLS_CC) == FAILURE) { + return FAILURE; + } + if (php_get_if_index_from_array(opt_ht, "interface", php_sock, + &if_index TSRMLS_CC) == FAILURE) { + return FAILURE; + } + + retval = mcast_req_fun(php_sock, level, (struct sockaddr*)&group, + glen, if_index TSRMLS_CC); + break; + } + +#ifdef HAS_MCAST_EXT + case MCAST_BLOCK_SOURCE: + mcast_sreq_fun = &php_mcast_block_source; + goto mcast_sreq_fun; + case MCAST_UNBLOCK_SOURCE: + mcast_sreq_fun = &php_mcast_unblock_source; + goto mcast_sreq_fun; + case MCAST_JOIN_SOURCE_GROUP: + mcast_sreq_fun = &php_mcast_join_source; + goto mcast_sreq_fun; + case MCAST_LEAVE_SOURCE_GROUP: + { + php_sockaddr_storage group = {0}, + source = {0}; + socklen_t glen, + slen; + + mcast_sreq_fun = &php_mcast_leave_source; + mcast_sreq_fun: + convert_to_array_ex(arg4); + opt_ht = HASH_OF(*arg4); + + if (php_get_address_from_array(opt_ht, "group", php_sock, &group, + &glen TSRMLS_CC) == FAILURE) { + return FAILURE; + } + if (php_get_address_from_array(opt_ht, "source", php_sock, &source, + &slen TSRMLS_CC) == FAILURE) { + return FAILURE; + } + if (php_get_if_index_from_array(opt_ht, "interface", php_sock, + &if_index TSRMLS_CC) == FAILURE) { + return FAILURE; + } + + retval = mcast_sreq_fun(php_sock, level, (struct sockaddr*)&group, + glen, (struct sockaddr*)&source, slen, if_index TSRMLS_CC); + break; + } +#endif + default: + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "unexpected option in php_do_mcast_opt (level %d, option %d). " + "This is a bug.", level, optname); + return FAILURE; + } + + if (retval != 0) { + if (retval != -2) { /* error, but message already emitted */ + PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + } + return FAILURE; + } + return SUCCESS; +} + +int php_do_setsockopt_ip_mcast(php_socket *php_sock, + int level, + int optname, + zval **arg4) +{ + unsigned int if_index; + struct in_addr if_addr; + void *opt_ptr; + socklen_t optlen; + unsigned char ipv4_mcast_ttl_lback; + int retval; + + switch (optname) { + case MCAST_JOIN_GROUP: + case MCAST_LEAVE_GROUP: +#ifdef HAS_MCAST_EXT + case MCAST_BLOCK_SOURCE: + case MCAST_UNBLOCK_SOURCE: + case MCAST_JOIN_SOURCE_GROUP: + case MCAST_LEAVE_SOURCE_GROUP: +#endif + if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } + + case IP_MULTICAST_IF: + if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { + return FAILURE; + } + + if (php_if_index_to_addr4(if_index, php_sock, &if_addr TSRMLS_CC) == FAILURE) { + return FAILURE; + } + opt_ptr = &if_addr; + optlen = sizeof(if_addr); + goto dosockopt; + + case IP_MULTICAST_LOOP: + convert_to_boolean_ex(arg4); + goto ipv4_loop_ttl; + + case IP_MULTICAST_TTL: + convert_to_long_ex(arg4); + if (Z_LVAL_PP(arg4) < 0L || Z_LVAL_PP(arg4) > 255L) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Expected a value between 0 and 255"); + return FAILURE; + } +ipv4_loop_ttl: + ipv4_mcast_ttl_lback = (unsigned char) Z_LVAL_PP(arg4); + opt_ptr = &ipv4_mcast_ttl_lback; + optlen = sizeof(ipv4_mcast_ttl_lback); + goto dosockopt; + } + + 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); + return FAILURE; + } + + return SUCCESS; +} + +int php_do_setsockopt_ipv6_mcast(php_socket *php_sock, + int level, + int optname, + zval **arg4) +{ + unsigned int if_index; + void *opt_ptr; + socklen_t optlen; + int ov; + int retval; + + switch (optname) { + case MCAST_JOIN_GROUP: + case MCAST_LEAVE_GROUP: +#ifdef HAS_MCAST_EXT + case MCAST_BLOCK_SOURCE: + case MCAST_UNBLOCK_SOURCE: + case MCAST_JOIN_SOURCE_GROUP: + case MCAST_LEAVE_SOURCE_GROUP: +#endif + if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } + + case IPV6_MULTICAST_IF: + if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { + return FAILURE; + } + + opt_ptr = &if_index; + optlen = sizeof(if_index); + goto dosockopt; + + case IPV6_MULTICAST_LOOP: + convert_to_boolean_ex(arg4); + goto ipv6_loop_hops; + case IPV6_MULTICAST_HOPS: + convert_to_long_ex(arg4); + if (Z_LVAL_PP(arg4) < -1L || Z_LVAL_PP(arg4) > 255L) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Expected a value between -1 and 255"); + return FAILURE; + } +ipv6_loop_hops: + ov = (int) Z_LVAL_PP(arg4); + opt_ptr = &ov; + optlen = sizeof(ov); + goto dosockopt; + } + + return 1; /* not handled */ + +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); + return FAILURE; + } + + return SUCCESS; +} + int php_mcast_join( php_socket *sock, int level, @@ -157,21 +461,21 @@ static int _php_mcast_join_leave( { #ifdef RFC3678_API struct group_req greq = {0}; - + memcpy(&greq.gr_group, group, group_len); assert(greq.gr_group.ss_family != 0); /* the caller has set this */ greq.gr_interface = if_index; return setsockopt(sock->bsd_socket, level, join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, (char*)&greq, - sizeof(greq)); + sizeof(greq)); #else if (sock->type == AF_INET) { struct ip_mreq mreq = {0}; struct in_addr addr; - + assert(group_len == sizeof(struct sockaddr_in)); - + if (if_index != 0) { if (php_if_index_to_addr4(if_index, sock, &addr TSRMLS_CC) == FAILURE) @@ -188,12 +492,12 @@ static int _php_mcast_join_leave( #if HAVE_IPV6 else if (sock->type == AF_INET6) { struct ipv6_mreq mreq = {0}; - + assert(group_len == sizeof(struct sockaddr_in6)); mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)group)->sin6_addr; mreq.ipv6mr_interface = if_index; - + return setsockopt(sock->bsd_socket, level, join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, (char*)&mreq, sizeof(mreq)); @@ -221,26 +525,26 @@ static int _php_mcast_source_op( { #ifdef RFC3678_API struct group_source_req gsreq = {0}; - + memcpy(&gsreq.gsr_group, group, group_len); assert(gsreq.gsr_group.ss_family != 0); memcpy(&gsreq.gsr_source, source, source_len); assert(gsreq.gsr_source.ss_family != 0); gsreq.gsr_interface = if_index; - + return setsockopt(sock->bsd_socket, level, _php_source_op_to_rfc3678_op(sop), (char*)&gsreq, sizeof(gsreq)); #else if (sock->type == AF_INET) { struct ip_mreq_source mreqs = {0}; struct in_addr addr; - + mreqs.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr; mreqs.imr_sourceaddr = ((struct sockaddr_in*)source)->sin_addr; - + assert(group_len == sizeof(struct sockaddr_in)); assert(source_len == sizeof(struct sockaddr_in)); - + if (if_index != 0) { if (php_if_index_to_addr4(if_index, sock, &addr TSRMLS_CC) == FAILURE) @@ -249,7 +553,7 @@ static int _php_mcast_source_op( } else { mreqs.imr_interface.s_addr = htonl(INADDR_ANY); } - + return setsockopt(sock->bsd_socket, level, _php_source_op_to_ipv4_op(sop), (char*)&mreqs, sizeof(mreqs)); } @@ -283,7 +587,7 @@ static int _php_source_op_to_rfc3678_op(enum source_op sop) case UNBLOCK_SOURCE: return MCAST_UNBLOCK_SOURCE; } - + assert(0); return 0; } @@ -300,7 +604,7 @@ static const char *_php_source_op_to_string(enum source_op sop) case UNBLOCK_SOURCE: return "MCAST_UNBLOCK_SOURCE"; } - + assert(0); return ""; } @@ -317,7 +621,7 @@ static int _php_source_op_to_ipv4_op(enum source_op sop) case UNBLOCK_SOURCE: return IP_UNBLOCK_SOURCE; } - + assert(0); return 0; } @@ -416,16 +720,16 @@ retry: int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr TSRMLS_DC) { struct ifreq if_req; - + if (if_index == 0) { out_addr->s_addr = INADDR_ANY; return SUCCESS; } - + #if !defined(ifr_ifindex) && defined(ifr_index) #define ifr_ifindex ifr_index #endif - + #if defined(SIOCGIFNAME) if_req.ifr_ifindex = if_index; if (ioctl(php_sock->bsd_socket, SIOCGIFNAME, &if_req) == -1) { @@ -438,13 +742,13 @@ int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_add "Failed obtaining address for interface %u: error %d", if_index, errno); return FAILURE; } - + if (ioctl(php_sock->bsd_socket, SIOCGIFADDR, &if_req) == -1) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed obtaining address for interface %u: error %d", if_index, errno); return FAILURE; } - + memcpy(out_addr, &((struct sockaddr_in *) &if_req.ifr_addr)->sin_addr, sizeof *out_addr); return SUCCESS; @@ -458,25 +762,25 @@ int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *i int size = 0, lastsize = 0; size_t entry_len; - + if (addr->s_addr == INADDR_ANY) { *if_index = 0; return SUCCESS; } - + for(;;) { size += 5 * sizeof(struct ifreq); buf = ecalloc(size, 1); if_conf.ifc_len = size; if_conf.ifc_buf = buf; - + if (ioctl(php_sock->bsd_socket, SIOCGIFCONF, (char*)&if_conf) == -1 && (errno != EINVAL || lastsize != 0)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed obtaining interfaces list: error %d", errno); goto err; } - + if (if_conf.ifc_len == lastsize) /* not increasing anymore */ break; @@ -486,15 +790,15 @@ int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *i buf = NULL; } } - + for (p = if_conf.ifc_buf; p < if_conf.ifc_buf + if_conf.ifc_len; p += entry_len) { struct ifreq *cur_req; - + /* let's hope the pointer is aligned */ cur_req = (struct ifreq*) p; - + #ifdef HAVE_SOCKADDR_SA_LEN entry_len = cur_req->ifr_addr.sa_len + sizeof(cur_req->ifr_name); #else @@ -502,7 +806,7 @@ int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *i entry_len = sizeof(struct sockaddr) + sizeof(cur_req->ifr_name); #endif entry_len = MAX(entry_len, sizeof(*cur_req)); - + if ((((struct sockaddr*)&cur_req->ifr_addr)->sa_family == AF_INET) && (((struct sockaddr_in*)&cur_req->ifr_addr)->sin_addr.s_addr == addr->s_addr)) { @@ -537,7 +841,7 @@ int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *i php_error_docref(NULL TSRMLS_CC, E_WARNING, "The interface with IP address %s was not found", addr_str); } - + err: if (buf != NULL) efree(buf); diff --git a/ext/sockets/multicast.h b/ext/sockets/multicast.h index 498a71f67a1..c363b584727 100644 --- a/ext/sockets/multicast.h +++ b/ext/sockets/multicast.h @@ -27,6 +27,16 @@ #define HAS_MCAST_EXT 1 #endif +int php_do_setsockopt_ip_mcast(php_socket *php_sock, + int level, + int optname, + zval **arg4); + +int php_do_setsockopt_ipv6_mcast(php_socket *php_sock, + int level, + int optname, + zval **arg4); + int php_if_index_to_addr4( unsigned if_index, php_socket *php_sock, diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 3138eb60c45..78da0c29e68 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -87,6 +87,12 @@ ZEND_END_MODULE_GLOBALS(sockets) ZEND_EXTERN_MODULE_GLOBALS(sockets); +enum sockopt_return { + SOCKOPT_ERROR, + SOCKOPT_CONTINUE, + SOCKOPT_SUCCESS +}; + char *sockets_strerror(int error TSRMLS_DC); php_socket *socket_import_file_descriptor(PHP_SOCKET sock TSRMLS_DC); diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 37e2e9fe98a..9f115944259 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -619,81 +619,6 @@ char *sockets_strerror(int error TSRMLS_DC) /* {{{ */ } /* }}} */ - -static int php_get_if_index_from_zval(zval *val, unsigned *out TSRMLS_DC) -{ - int ret; - - if (Z_TYPE_P(val) == IS_LONG) { - if (Z_LVAL_P(val) < 0 || Z_LVAL_P(val) > UINT_MAX) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "the interface index cannot be negative or larger than %u;" - " given %ld", UINT_MAX, Z_LVAL_P(val)); - ret = FAILURE; - } else { - *out = Z_LVAL_P(val); - ret = SUCCESS; - } - } else { -#if HAVE_IF_NAMETOINDEX - unsigned int ind; - zval_add_ref(&val); - convert_to_string_ex(&val); - ind = if_nametoindex(Z_STRVAL_P(val)); - if (ind == 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "no interface with name \"%s\" could be found", Z_STRVAL_P(val)); - ret = FAILURE; - } else { - *out = ind; - ret = SUCCESS; - } - zval_ptr_dtor(&val); -#else - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "this platform does not support looking up an interface by " - "name, an integer interface index must be supplied instead"); - ret = FAILURE; -#endif - } - - return ret; -} - -static int php_get_if_index_from_array(const HashTable *ht, const char *key, - php_socket *sock, unsigned int *if_index TSRMLS_DC) -{ - zval **val; - - if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { - *if_index = 0; /* default: 0 */ - return SUCCESS; - } - - return php_get_if_index_from_zval(*val, if_index TSRMLS_CC); -} - -static int php_get_address_from_array(const HashTable *ht, const char *key, - php_socket *sock, php_sockaddr_storage *ss, socklen_t *ss_len TSRMLS_DC) -{ - zval **val, - *valcp; - - if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", key); - return FAILURE; - } - valcp = *val; - zval_add_ref(&valcp); - convert_to_string_ex(val); - if (!php_set_inet46_addr(ss, ss_len, Z_STRVAL_P(valcp), sock TSRMLS_CC)) { - zval_ptr_dtor(&valcp); - return FAILURE; - } - zval_ptr_dtor(&valcp); - return SUCCESS; -} - /* {{{ PHP_GINIT_FUNCTION */ static PHP_GINIT_FUNCTION(sockets) { @@ -2012,102 +1937,6 @@ PHP_FUNCTION(socket_get_option) } /* }}} */ -static int php_do_mcast_opt(php_socket *php_sock, int level, int optname, zval **arg4 TSRMLS_DC) -{ - HashTable *opt_ht; - unsigned int if_index; - int retval; - int (*mcast_req_fun)(php_socket *, int, struct sockaddr *, socklen_t, - unsigned TSRMLS_DC); -#ifdef HAS_MCAST_EXT - int (*mcast_sreq_fun)(php_socket *, int, struct sockaddr *, socklen_t, - struct sockaddr *, socklen_t, unsigned TSRMLS_DC); -#endif - - switch (optname) { - case MCAST_JOIN_GROUP: - mcast_req_fun = &php_mcast_join; - goto mcast_req_fun; - case MCAST_LEAVE_GROUP: - { - php_sockaddr_storage group = {0}; - socklen_t glen; - - mcast_req_fun = &php_mcast_leave; -mcast_req_fun: - convert_to_array_ex(arg4); - opt_ht = HASH_OF(*arg4); - - if (php_get_address_from_array(opt_ht, "group", php_sock, &group, - &glen TSRMLS_CC) == FAILURE) { - return FAILURE; - } - if (php_get_if_index_from_array(opt_ht, "interface", php_sock, - &if_index TSRMLS_CC) == FAILURE) { - return FAILURE; - } - - retval = mcast_req_fun(php_sock, level, (struct sockaddr*)&group, - glen, if_index TSRMLS_CC); - break; - } - -#ifdef HAS_MCAST_EXT - case MCAST_BLOCK_SOURCE: - mcast_sreq_fun = &php_mcast_block_source; - goto mcast_sreq_fun; - case MCAST_UNBLOCK_SOURCE: - mcast_sreq_fun = &php_mcast_unblock_source; - goto mcast_sreq_fun; - case MCAST_JOIN_SOURCE_GROUP: - mcast_sreq_fun = &php_mcast_join_source; - goto mcast_sreq_fun; - case MCAST_LEAVE_SOURCE_GROUP: - { - php_sockaddr_storage group = {0}, - source = {0}; - socklen_t glen, - slen; - - mcast_sreq_fun = &php_mcast_leave_source; - mcast_sreq_fun: - convert_to_array_ex(arg4); - opt_ht = HASH_OF(*arg4); - - if (php_get_address_from_array(opt_ht, "group", php_sock, &group, - &glen TSRMLS_CC) == FAILURE) { - return FAILURE; - } - if (php_get_address_from_array(opt_ht, "source", php_sock, &source, - &slen TSRMLS_CC) == FAILURE) { - return FAILURE; - } - if (php_get_if_index_from_array(opt_ht, "interface", php_sock, - &if_index TSRMLS_CC) == FAILURE) { - return FAILURE; - } - - retval = mcast_sreq_fun(php_sock, level, (struct sockaddr*)&group, - glen, (struct sockaddr*)&source, slen, if_index TSRMLS_CC); - break; - } -#endif - default: - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "unexpected option in php_do_mcast_opt (level %d, option %d). " - "This is a bug.", level, optname); - return FAILURE; - } - - if (retval != 0) { - if (retval != -2) { /* error, but message already emitted */ - PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); - } - return FAILURE; - } - return SUCCESS; -} - /* {{{ proto bool socket_set_option(resource socket, int level, int optname, int|array optval) Sets socket options for the socket */ PHP_FUNCTION(socket_set_option) @@ -2127,10 +1956,6 @@ PHP_FUNCTION(socket_set_option) zval **l_onoff, **l_linger; zval **sec, **usec; - /* Multicast */ - unsigned int if_index; - struct in_addr if_addr; - unsigned char ipv4_mcast_ttl_lback; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rllZ", &arg1, &level, &optname, &arg4) == FAILURE) { return; @@ -2140,94 +1965,23 @@ PHP_FUNCTION(socket_set_option) set_errno(0); +#define HANDLE_SUBCALL(res) \ + do { \ + if (res == 1) { goto default_case; } \ + else if (res == SUCCESS) { RETURN_TRUE; } \ + else { RETURN_FALSE; } \ + } while (0) + + if (level == IPPROTO_IP) { - switch (optname) { - case MCAST_JOIN_GROUP: - case MCAST_LEAVE_GROUP: -#ifdef HAS_MCAST_EXT - case MCAST_BLOCK_SOURCE: - case MCAST_UNBLOCK_SOURCE: - case MCAST_JOIN_SOURCE_GROUP: - case MCAST_LEAVE_SOURCE_GROUP: -#endif - if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } else { - RETURN_TRUE; - } - - case IP_MULTICAST_IF: - if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } - - if (php_if_index_to_addr4(if_index, php_sock, &if_addr TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } - opt_ptr = &if_addr; - optlen = sizeof(if_addr); - goto dosockopt; - - case IP_MULTICAST_LOOP: - convert_to_boolean_ex(arg4); - goto ipv4_loop_ttl; - case IP_MULTICAST_TTL: - convert_to_long_ex(arg4); - if (Z_LVAL_PP(arg4) < 0L || Z_LVAL_PP(arg4) > 255L) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Expected a value between 0 and 255"); - RETURN_FALSE; - } -ipv4_loop_ttl: - ipv4_mcast_ttl_lback = (unsigned char) Z_LVAL_PP(arg4); - opt_ptr = &ipv4_mcast_ttl_lback; - optlen = sizeof(ipv4_mcast_ttl_lback); - goto dosockopt; - } + int res = php_do_setsockopt_ip_mcast(php_sock, level, optname, arg4); + HANDLE_SUBCALL(res); } #if HAVE_IPV6 else if (level == IPPROTO_IPV6) { - switch (optname) { - case MCAST_JOIN_GROUP: - case MCAST_LEAVE_GROUP: -#ifdef HAS_MCAST_EXT - case MCAST_BLOCK_SOURCE: - case MCAST_UNBLOCK_SOURCE: - case MCAST_JOIN_SOURCE_GROUP: - case MCAST_LEAVE_SOURCE_GROUP: -#endif - if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } else { - RETURN_TRUE; - } - - case IPV6_MULTICAST_IF: - if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } - - opt_ptr = &if_index; - optlen = sizeof(if_index); - goto dosockopt; - - case IPV6_MULTICAST_LOOP: - convert_to_boolean_ex(arg4); - goto ipv6_loop_hops; - case IPV6_MULTICAST_HOPS: - convert_to_long_ex(arg4); - if (Z_LVAL_PP(arg4) < -1L || Z_LVAL_PP(arg4) > 255L) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Expected a value between -1 and 255"); - RETURN_FALSE; - } -ipv6_loop_hops: - ov = (int) Z_LVAL_PP(arg4); - opt_ptr = &ov; - optlen = sizeof(ov); - goto dosockopt; - } + int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4); + HANDLE_SUBCALL(res); } #endif @@ -2292,6 +2046,7 @@ ipv6_loop_hops: } default: +default_case: convert_to_long_ex(arg4); ov = Z_LVAL_PP(arg4); @@ -2300,12 +2055,9 @@ ipv6_loop_hops: break; } -dosockopt: retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen); if (retval != 0) { - if (retval != -2) { /* error, but message already emitted */ - PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); - } + PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); RETURN_FALSE; } From 8fb1aa618453149bb876bda4cafd1860468c4443 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 13:36:40 +0100 Subject: [PATCH 32/55] Destroy ancillary registry on shutdown --- ext/sockets/sendrecvmsg.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 16330e0ded0..88b937f8285 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -1540,6 +1540,13 @@ static void init_ancillary_registry(void) 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; + } } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) { @@ -1761,4 +1768,6 @@ void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS) #ifdef ZTS tsrm_mutex_free(ancillary_mutex); #endif + + destroy_ancillary_registry(); } From b18bd8904e41941db204ac6b2bf4cf43421e8838 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 13:38:57 +0100 Subject: [PATCH 33/55] Rename some functions for consistency --- ext/sockets/sendrecvmsg.c | 4 ++-- ext/sockets/sendrecvmsg.h | 4 ++-- ext/sockets/sockets.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 88b937f8285..201adbda433 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -1723,7 +1723,7 @@ PHP_FUNCTION(socket_cmsg_space) RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size)); } -void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) +void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS) { /* IPv6 ancillary data * Note that support for sticky options via setsockopt() is not implemented @@ -1763,7 +1763,7 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) #endif } -void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS) +void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS) { #ifdef ZTS tsrm_mutex_free(ancillary_mutex); diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h index 82dc456e655..929a6ad9cfe 100644 --- a/ext/sockets/sendrecvmsg.h +++ b/ext/sockets/sendrecvmsg.h @@ -4,5 +4,5 @@ PHP_FUNCTION(socket_sendmsg); PHP_FUNCTION(socket_recvmsg); PHP_FUNCTION(socket_cmsg_space); -void _socket_sendrecvmsg_init(INIT_FUNC_ARGS); -void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); +void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS); +void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 9f115944259..1d86028691b 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -751,7 +751,7 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("IPV6_UNICAST_HOPS", IPV6_UNICAST_HOPS, CONST_CS | CONST_PERSISTENT); #endif - _socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU); + php_socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU); return SUCCESS; } @@ -774,7 +774,7 @@ PHP_RSHUTDOWN_FUNCTION(sockets) efree(SOCKETS_G(strerror_buf)); SOCKETS_G(strerror_buf) = NULL; } - _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU); + php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU); return SUCCESS; } From 66ea02458746a4853ff6190c6c75da5e95677911 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 17:27:08 +0100 Subject: [PATCH 34/55] Support sticky IPV6_PKTINFO --- ext/sockets/sendrecvmsg.c | 90 ++++++++++++++++++- ext/sockets/sendrecvmsg.h | 3 + ext/sockets/sockets.c | 10 +++ .../tests/socket_set_option_in6_pktinfo.phpt | 31 +++++++ 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 ext/sockets/tests/socket_set_option_in6_pktinfo.phpt diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 201adbda433..6b1a528c5b1 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -77,6 +77,7 @@ struct key_value { #define KEY_FILL_SOCKADDR "fill_sockaddr" #define KEY_RECVMSG_RET "recvmsg_ret" #define KEY_CMSG_LEN "cmsg_len" +static const struct key_value empty_key_value_list[] = {{0}}; typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); @@ -222,6 +223,12 @@ static void err_msg_dispose(struct err_s *err TSRMLS_DC) } } } +static void allocations_dispose(zend_llist **allocations) +{ + zend_llist_destroy(*allocations); + efree(*allocations); + *allocations = NULL; +} static unsigned from_array_iterate(const zval *arr, void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), @@ -1660,8 +1667,7 @@ PHP_FUNCTION(socket_recvmsg) /* we don;t need msghdr anymore; free it */ msghdr = NULL; - zend_llist_destroy(allocations); - efree(allocations); + allocations_dispose(&allocations); zval_dtor(zmsg); if (!err.has_error) { @@ -1723,6 +1729,86 @@ PHP_FUNCTION(socket_cmsg_space) RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size)); } +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: + 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 TSRMLS_CC); + 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 *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo", + empty_key_value_list, &err); + if (err.has_error) { + err_msg_dispose(&err); + res = -1; + } else { + ZVAL_COPY_VALUE(result, zv); + efree(zv); + } + } + efree(buffer); + + return res == 0 ? SUCCESS : FAILURE; +} + void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS) { /* IPv6 ancillary data diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h index 929a6ad9cfe..82fb38b4b5f 100644 --- a/ext/sockets/sendrecvmsg.h +++ b/ext/sockets/sendrecvmsg.h @@ -6,3 +6,6 @@ PHP_FUNCTION(socket_cmsg_space); void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS); void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); + +int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4); +int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result); diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 1d86028691b..9c57e2d98d2 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1877,6 +1877,13 @@ PHP_FUNCTION(socket_get_option) } } } + } else if (level == IPPROTO_IPV6) { + int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value); + if (ret == SUCCESS) { + return; + } else if (ret == FAILURE) { + RETURN_FALSE; + } /* else continue */ } /* sol_socket options and general case */ @@ -1981,6 +1988,9 @@ PHP_FUNCTION(socket_set_option) #if HAVE_IPV6 else if (level == IPPROTO_IPV6) { int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4); + if (res == 1) { + res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4); + } HANDLE_SUBCALL(res); } #endif diff --git a/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt new file mode 100644 index 00000000000..53320cad0c3 --- /dev/null +++ b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt @@ -0,0 +1,31 @@ +--TEST-- +socket_set_option() with IPV6_PKTINFO +--SKIPIF-- + '::1', + "ifindex" => 0 +])); +//Oddly, Linux does not support IPV6_PKTINFO in sockgetopt(). +//See do_ipv6_getsockopt() on the kernel sources +//A work-around with is sort-of possible (with IPV6_2292PKTOPTIONS), +//but not worth it +//var_dump(socket_get_option($s, IPPROTO_IPV6, IPV6_PKTINFO)); + +--EXPECTF-- +Warning: socket_set_option(): error converting user data (path: in6_pktinfo): The key 'addr' is required in %s on line %d +bool(false) +bool(true) + From 4414b33abd087bba26cb2cbdc2bf05938d5a6690 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 12 Nov 2012 00:40:38 +0100 Subject: [PATCH 35/55] Refactoring: move stuff to new conversions.c --- ext/sockets/config.m4 | 2 +- ext/sockets/conversions.c | 1477 +++++++++++++++++++++++++++++++++++++ ext/sockets/conversions.h | 78 ++ ext/sockets/sendrecvmsg.c | 1461 +----------------------------------- ext/sockets/sendrecvmsg.h | 27 +- 5 files changed, 1584 insertions(+), 1461 deletions(-) create mode 100644 ext/sockets/conversions.c create mode 100644 ext/sockets/conversions.h diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4 index 3b5b90714b7..9c752496463 100644 --- a/ext/sockets/config.m4 +++ b/ext/sockets/config.m4 @@ -43,6 +43,6 @@ if test "$PHP_SOCKETS" != "no"; then AC_DEFINE(HAVE_SA_SS_FAMILY,1,[Whether you have sockaddr_storage.ss_family]) fi - PHP_NEW_EXTENSION([sockets], [sockets.c multicast.c sockaddr_conv.c sendrecvmsg.c], [$ext_shared]) + PHP_NEW_EXTENSION([sockets], [sockets.c multicast.c conversions.c sockaddr_conv.c sendrecvmsg.c], [$ext_shared]) PHP_INSTALL_HEADERS([ext/sockets/], [php_sockets.h]) fi diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c new file mode 100644 index 00000000000..7ca9972ac0d --- /dev/null +++ b/ext/sockets/conversions.c @@ -0,0 +1,1477 @@ +#include "conversions.h" +#include "sockaddr_conv.h" +#include "conversions.h" +#include "sendrecvmsg.h" /* for ancillary registry */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) +#define DEFAULT_BUFF_SIZE 8192 + +struct _ser_context { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys, + /* common part to res_context ends here */ + allocations; + php_socket *sock; +}; +struct _res_context { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys; +}; + +typedef struct { + /* zval info */ + const char *name; + unsigned name_size; + int required; + + /* structure info */ + size_t field_offset; /* 0 to pass full structure, e.g. when more than + one field is to be changed; in that case the + callbacks need to know the name of the fields */ + + /* callbacks */ + from_zval_write_field *from_zval; + to_zval_read_field *to_zval; +} field_descriptor; + +#define KEY_FILL_SOCKADDR "fill_sockaddr" +#define KEY_RECVMSG_RET "recvmsg_ret" +#define KEY_CMSG_LEN "cmsg_len" + +/* PARAMETERS */ +static int param_get_bool(void *ctx, const char *key, int def) +{ + int **elem; + if (zend_hash_find(ctx, key, strlen(key) + 1, (void**)&elem) == SUCCESS) { + return **elem; + } else { + return def; + } +} + +/* MEMORY */ +static inline void *accounted_emalloc(size_t alloc_size, ser_context *ctx) +{ + void *ret = emalloc(alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} +static inline void *accounted_ecalloc(size_t nmemb, size_t alloc_size, ser_context *ctx) +{ + void *ret = ecalloc(nmemb, alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} +static inline void *accounted_safe_ecalloc(size_t nmemb, size_t alloc_size, size_t offset, ser_context *ctx) +{ + void *ret = safe_emalloc(nmemb, alloc_size, offset); + memset(ret, '\0', nmemb * alloc_size + offset); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} + +/* ERRORS */ +static void do_from_to_zval_err(struct err_s *err, + zend_llist *keys, + const char *what_conv, + const char *fmt, + va_list ap) +{ + smart_str path = {0}; + const char **node; + char *user_msg; + int user_msg_size; + zend_llist_position pos; + + if (err->has_error) { + return; + } + + for (node = zend_llist_get_first_ex(keys, &pos); + node != NULL; + node = zend_llist_get_next_ex(keys, &pos)) { + smart_str_appends(&path, *node); + smart_str_appends(&path, " > "); + } + + if (path.len > 3) { + path.len -= 3; + } + smart_str_0(&path); + + user_msg_size = vspprintf(&user_msg, 0, fmt, ap); + + err->has_error = 1; + err->level = E_WARNING; + spprintf(&err->msg, 0, "error converting %s data (path: %s): %.*s", + what_conv, + path.c && path.c != '\0' ? path.c : "unavailable", + user_msg_size, user_msg); + err->should_free = 1; + + efree(user_msg); + smart_str_free_ex(&path, 0); +} +__attribute__ ((format (printf, 2, 3))) +static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); + va_end(ap); +} +__attribute__ ((format (printf, 2, 3))) +static void do_to_zval_err(res_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); + va_end(ap); +} + +void err_msg_dispose(struct err_s *err TSRMLS_DC) +{ + if (err->msg != NULL) { + php_error_docref0(NULL TSRMLS_CC, err->level, "%s", err->msg); + if (err->should_free) { + efree(err->msg); + } + } +} +void allocations_dispose(zend_llist **allocations) +{ + zend_llist_destroy(*allocations); + efree(*allocations); + *allocations = NULL; +} + +static unsigned from_array_iterate(const zval *arr, + void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), + void **args, + ser_context *ctx) +{ + HashPosition pos; + unsigned i; + zval **elem; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 1; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos), i++) { + if (snprintf(buf, sizeof(buf), "element #%u", i) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + func(elem, i, args, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + return i -1; +} + +/* Generic Aggregated conversions */ +static void from_zval_write_aggregation(const zval *container, + char *structure, + const field_descriptor *descriptors, + ser_context *ctx) +{ + const field_descriptor *descr; + zval **elem; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + } + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + if (zend_hash_find(Z_ARRVAL_P(container), + descr->name, descr->name_size, (void**)&elem) == SUCCESS) { + + if (descr->from_zval == NULL) { + do_from_zval_err(ctx, "No information on how to convert value " + "of key '%s'", descr->name); + break; + } + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->from_zval(*elem, ((char*)structure) + descr->field_offset, ctx); + zend_llist_remove_tail(&ctx->keys); + + } else if (descr->required) { + do_from_zval_err(ctx, "The key '%s' is required", descr->name); + break; + } + } +} +static void to_zval_read_aggregation(const char *structure, + zval *zarr, /* initialized array */ + const field_descriptor *descriptors, + res_context *ctx) +{ + const field_descriptor *descr; + + assert(Z_TYPE_P(zarr) == IS_ARRAY); + assert(Z_ARRVAL_P(zarr) != NULL); + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + zval *new_zv; + + if (descr->to_zval == NULL) { + do_to_zval_err(ctx, "No information on how to convert native " + "field into value for key '%s'", descr->name); + break; + } + + ALLOC_INIT_ZVAL(new_zv); + add_assoc_zval_ex(zarr, descr->name, descr->name_size, new_zv); + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->to_zval(structure + descr->field_offset, new_zv, ctx); + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for integers */ +static long from_zval_integer_common(const zval *arr_value, ser_context *ctx) +{ + long ret = 0; + zval lzval = zval_used_for_init; + + if (Z_TYPE_P(arr_value) != IS_LONG) { + ZVAL_COPY_VALUE(&lzval, arr_value); + zval_copy_ctor(&lzval); + arr_value = &lzval; + } + + switch (Z_TYPE_P(arr_value)) { + case IS_LONG: +long_case: + ret = Z_LVAL_P(arr_value); + break; + + /* if not long we're operating on lzval */ + case IS_DOUBLE: +double_case: + convert_to_long(&lzval); + goto long_case; + + case IS_OBJECT: + case IS_STRING: { + long lval; + double dval; + + convert_to_string(&lzval); + + switch (is_numeric_string(Z_STRVAL(lzval), Z_STRLEN(lzval), &lval, &dval, 0)) { + case IS_DOUBLE: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_DOUBLE; + Z_DVAL(lzval) = dval; + goto double_case; + + case IS_LONG: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_LONG; + Z_DVAL(lzval) = lval; + goto long_case; + } + + /* if we get here, we don't have a numeric string */ + do_from_zval_err(ctx, "expected an integer, but got a non numeric " + "string (possibly from a converted object): '%s'", Z_STRVAL_P(arr_value)); + break; + } + + default: + do_from_zval_err(ctx, "%s", "expected an integer, either of a PHP " + "integer type or of a convertible type"); + break; + } + + zval_dtor(&lzval); + + return ret; +} +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + int ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval > INT_MAX || lval < INT_MIN) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native int"); + return; + } + + ival = (int)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_unsigned(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + unsigned ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(ival) && (lval < 0 || lval > UINT_MAX)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native unsigned int"); + return; + } + + ival = (unsigned)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint32_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(uint32_t) && (lval < 0 || lval > 0xFFFFFFFF)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 32-bit integer"); + return; + } + + ival = (uint32_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint16_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > 0xFFFF) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 16-bit integer"); + return; + } + + ival = htons((uint16_t)lval); + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + sa_family_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > (sa_family_t)-1) { /* sa_family_t is unsigned */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a sa_family_t value"); + return; + } + + ival = (sa_family_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_pid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + pid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || (pid_t)lval != lval) { /* pid_t is signed */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a pid_t value"); + return; + } + + ival = (pid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + /* uid_t can be signed or unsigned (generally unsigned) */ + if ((uid_t)-1 > (uid_t)0) { + if (sizeof(long) > sizeof(uid_t) && (lval < 0 || (uid_t)lval != lval)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } else { + if (sizeof(long) > sizeof(uid_t) && (uid_t)lval != lval) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } + + ival = (uid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} + +void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +{ + int ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) +{ + unsigned ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) +{ + uint16_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ntohs(ival)); +} +static void to_zval_read_uint32(const char *data, zval *zv, res_context *ctx) +{ + uint32_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) +{ + sa_family_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_pid_t(const char *data, zval *zv, res_context *ctx) +{ + pid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_uid_t(const char *data, zval *zv, res_context *ctx) +{ + uid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} + +/* CONVERSIONS for sockaddr */ +static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) +{ + int res; + struct sockaddr_in saddr = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet_addr(&saddr, Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(inaddr, &saddr.sin_addr, sizeof saddr.sin_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in_addr *addr = (const struct in_addr *)data; + socklen_t size = INET_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv4 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in, sin_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in, sin_addr), from_zval_write_sin_addr, to_zval_read_sin_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in, sin_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {0} +}; +static void from_zval_write_sockaddr_in(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_in, ctx); +} +static void to_zval_read_sockaddr_in(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in, ctx); +} +static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) +{ + int res; + struct sockaddr_in6 saddr6 = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet6_addr(&saddr6, + Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET6 " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in6_addr *addr = (const struct in6_addr *)data; + socklen_t size = INET6_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv6 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in6[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in6, sin6_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in6, sin6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in6, sin6_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {"flowinfo", sizeof("flowinfo"), 0, offsetof(struct sockaddr_in6, sin6_flowinfo), from_zval_write_uint32, to_zval_read_uint32}, + {"scope_id", sizeof("scope_id"), 0, offsetof(struct sockaddr_in6, sin6_scope_id), from_zval_write_uint32, to_zval_read_uint32}, + {0} +}; +static void from_zval_write_sockaddr_in6(const zval *container, char *sockaddr6, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr6, descriptors_sockaddr_in6, ctx); +} +static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); +} +static void from_zval_write_sun_path(const zval *path, char *sockaddr_un_c, ser_context *ctx) +{ + zval lzval = zval_used_for_init; + struct sockaddr_un *saddr = (struct sockaddr_un*)sockaddr_un_c; + + if (Z_TYPE_P(path) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, path); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + path = &lzval; + } + + if (Z_STRLEN_P(path) >= sizeof(saddr->sun_path)) { + do_from_zval_err(ctx, "the path is too long, the maximum permitted " + "length is %ld", sizeof(saddr->sun_path) - 1); + return; + } + + memcpy(&saddr->sun_path, Z_STRVAL_P(path), Z_STRLEN_P(path)); + saddr->sun_path[Z_STRLEN_P(path)] = '\0'; + + zval_dtor(&lzval); +} +static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) { + struct sockaddr_un *saddr = (struct sockaddr_un*)data; + char *nul_pos; + + nul_pos = memchr(&saddr->sun_path, '\0', sizeof(saddr->sun_path)); + if (nul_pos == NULL) { + do_to_zval_err(ctx, "could not find a NUL in the path"); + return; + } + + ZVAL_STRINGL(zv, saddr->sun_path, nul_pos - (char*)&saddr->sun_path, 1); +} +static const field_descriptor descriptors_sockaddr_un[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"path", sizeof("path"), 0, 0, from_zval_write_sun_path, to_zval_read_sun_path}, + {0} +}; +static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_un, ctx); +} +static void to_zval_read_sockaddr_un(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_un, ctx); +} +static void from_zval_write_sockaddr_aux(const zval *container, + struct sockaddr **sockaddr_ptr, + socklen_t *sockaddr_len, + ser_context *ctx) +{ + int family; + zval **elem; + int fill_sockaddr; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + fill_sockaddr = param_get_bool(ctx, KEY_FILL_SOCKADDR, 1); + + if (zend_hash_find(Z_ARRVAL_P(container), "family", sizeof("family"), (void**)&elem) == SUCCESS + && Z_TYPE_PP(elem) != IS_NULL) { + const char *node = "family"; + zend_llist_add_element(&ctx->keys, &node); + from_zval_write_int(*elem, (char*)&family, ctx); + zend_llist_remove_tail(&ctx->keys); + } else { + family = ctx->sock->type; + } + + switch (family) { + case AF_INET: + /* though not all OSes support sockaddr_in used in IPv6 sockets */ + if (ctx->sock->type != AF_INET && ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (number %d) is not " + "supported on this socket", family); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in), ctx); + *sockaddr_len = sizeof(struct sockaddr_in); + if (fill_sockaddr) { + from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET; + } + break; + + case AF_INET6: + if (ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (AF_INET6) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in6), ctx); + *sockaddr_len = sizeof(struct sockaddr_in6); + if (fill_sockaddr) { + from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET6; + } + break; + + case AF_UNIX: + if (ctx->sock->type != AF_UNIX) { + do_from_zval_err(ctx, "the specified family (AF_UNIX) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_un), ctx); + *sockaddr_len = sizeof(struct sockaddr_un); + if (fill_sockaddr) { + from_zval_write_sockaddr_un(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_UNIX; + } + break; + + default: + do_from_zval_err(ctx, "%s", "the only families currently supported are " + "AF_INET, AF_INET6 and AF_UNIX"); + break; + } +} +static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_context *ctx) +{ + const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; + + if (saddr->sa_family == 0) { + ZVAL_NULL(zv); + return; + } + + array_init(zv); + + switch (saddr->sa_family) { + case AF_INET: + to_zval_read_sockaddr_in(sockaddr_c, zv, ctx); + break; + + case AF_INET6: + to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); + break; + + case AF_UNIX: + to_zval_read_sockaddr_un(sockaddr_c, zv, ctx); + break; + + default: + do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " + "not supported", + (int)saddr->sa_family); + break; + } +} + +/* CONVERSIONS for cmsghdr */ +/* + * [ level => , type => , data => [],] + * struct cmsghdr { + * socklen_t cmsg_len; // data byte count, including header + * int cmsg_level; // originating protocol + * int cmsg_type; // protocol-specific type + * // followed by unsigned char cmsg_data[]; + * }; + */ +static void from_zval_write_control(const zval *arr, + void **control_buf, + zend_llist_element *alloc, + size_t *control_len, + size_t *offset, + ser_context *ctx) +{ + struct cmsghdr *cmsghdr; + int level, + type; + size_t data_len, + req_space, + space_left; + ancillary_reg_entry *entry; + + static const field_descriptor descriptor_level[] = { + {"level", sizeof("level"), 0, 0, from_zval_write_int, 0}, + {0} + }; + static const field_descriptor descriptor_type[] = { + {"type", sizeof("type"), 0, 0, from_zval_write_int, 0}, + {0} + }; + field_descriptor descriptor_data[] = { + {"data", sizeof("data"), 0, 0, 0, 0}, + {0} + }; + + from_zval_write_aggregation(arr, (char *)&level, descriptor_level, ctx); + if (ctx->err.has_error) { + return; + } + from_zval_write_aggregation(arr, (char *)&type, descriptor_type, ctx); + if (ctx->err.has_error) { + return; + } + + entry = get_ancillary_reg_entry(level, type); + if (entry == NULL) { + do_from_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + level, type); + return; + } + + if (entry->calc_space) { + data_len = entry->calc_space(arr, ctx); + if (ctx->err.has_error) { + return; + } + } else { + data_len = entry->size; + } + req_space = CMSG_SPACE(data_len); + space_left = *control_len - *offset; + assert(*control_len >= *offset); + + if (space_left < req_space) { + *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); + *control_len += 2 * req_space; + memcpy(&alloc->data, *control_buf, sizeof *control_buf); + } + + cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); + cmsghdr->cmsg_level = level; + cmsghdr->cmsg_type = type; + cmsghdr->cmsg_len = CMSG_LEN(data_len); + + descriptor_data[0].from_zval = entry->from_array; + from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); + + *offset += req_space; +} +static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + HashPosition pos; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + zval **elem; + uint32_t i; + int num_elems; + void *control_buf; + zend_llist_element *alloc; + size_t control_len, + cur_offset; + struct msghdr *msg = (struct msghdr*)msghdr_c; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + return; + } + + /* estimate each message at 20 bytes */ + control_buf = accounted_safe_ecalloc(num_elems, CMSG_SPACE(20), 0, ctx); + alloc = ctx->allocations.tail; + control_len = (size_t)num_elems * CMSG_SPACE(20); + cur_offset = 0; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + from_zval_write_control(*elem, &control_buf, alloc, &control_len, + &cur_offset, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + msg->msg_control = control_buf; + msg->msg_controllen = cur_offset; /* not control_len, which may be larger */ +} +static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; + ancillary_reg_entry *entry; + size_t len, + *len_p = &len; + + entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); + if (entry == NULL) { + do_to_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + cmsg->cmsg_level, cmsg->cmsg_type); + return; + } + if (CMSG_LEN(entry->size) > cmsg->cmsg_len) { + do_to_zval_err(ctx, "the cmsghdr structure is unexpectedly small; " + "expected a length of at least %ld, but got %ld", + (long)CMSG_LEN(entry->size), (long)cmsg->cmsg_len); + return; + } + + len = (size_t)cmsg->cmsg_len; /* use another var because type of cmsg_len varies */ + if (zend_hash_add(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + &len_p, sizeof(len_p), NULL) == FAILURE) { + do_to_zval_err(ctx, "%s", "could not set parameter " KEY_CMSG_LEN); + return; + } + + entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); + + zend_hash_del(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN)); +} +static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + /* takes a cmsghdr, not a msghdr like from_zval_write_control */ + static const field_descriptor descriptors[] = { + {"level", sizeof("level"), 0, offsetof(struct cmsghdr, cmsg_level), 0, to_zval_read_int}, + {"type", sizeof("type"), 0, offsetof(struct cmsghdr, cmsg_type), 0, to_zval_read_int}, + {"data", sizeof("data"), 0, 0 /* cmsghdr passed */, 0, to_zval_read_cmsg_data}, + {0} + }; + + array_init_size(zv, 3); + to_zval_read_aggregation(cmsghdr_c, zv, descriptors, ctx); +} +static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_context *ctx) +{ + struct msghdr *msg = (struct msghdr *)msghdr_c; + struct cmsghdr *cmsg; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + uint32_t i = 1; + + /*if (msg->msg_flags & MSG_CTRUNC) { + php_error_docref0(NULL, E_WARNING, "The MSG_CTRUNC flag is present; will not " + "attempt to read control messages"); + ZVAL_FALSE(zv); + return; + }*/ + + array_init(zv); + + for (cmsg = CMSG_FIRSTHDR(msg); + cmsg != NULL && !ctx->err.has_error; + cmsg = CMSG_NXTHDR(msg,cmsg)) { + zval *elem; + + ALLOC_INIT_ZVAL(elem); + add_next_index_zval(zv, elem); + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + to_zval_read_control((const char *)cmsg, elem, ctx); + + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for msghdr */ +static void from_zval_write_name(const zval *zname_arr, char *msghdr_c, ser_context *ctx) +{ + struct sockaddr *sockaddr; + socklen_t sockaddr_len; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + from_zval_write_sockaddr_aux(zname_arr, &sockaddr, &sockaddr_len, ctx); + + msghdr->msg_name = sockaddr; + msghdr->msg_namelen = sockaddr_len; +} +static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx) +{ + void *name = (void*)*(void**)sockaddr_p; + if (name == NULL) { + ZVAL_NULL(zv); + } else { + to_zval_read_sockaddr_aux(name, zv, ctx); + } +} +static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + long lval; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + lval = from_zval_integer_common(elem, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > MAX_USER_BUFF_SIZE) { + do_from_zval_err(ctx, "the buffer size must be between 1 and %ld; " + "given %ld", (long)MAX_USER_BUFF_SIZE, lval); + return; + } + + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); + msghdr->msg_iov[0].iov_len = (size_t)lval; +} +static void from_zval_write_iov_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + struct msghdr *msg = args[0]; + size_t len; + + zval_add_ref(elem); + convert_to_string_ex(elem); + + len = Z_STRLEN_PP(elem); + msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); + msg->msg_iov[i - 1].iov_len = len; + memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); + + zval_ptr_dtor(elem); +} +static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + int num_elem; + struct msghdr *msg = (struct msghdr*)msghdr_c; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elem == 0) { + return; + } + + msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); + msg->msg_iovlen = (size_t)num_elem; + + from_array_iterate(arr, from_zval_write_iov_array_aux, (void**)&msg, ctx); +} +static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + uint32_t len; + + /* controllen should be an unsigned with at least 32-bit. Let's assume + * this least common denominator + */ + from_zval_write_uint32(elem, (char*)&len, ctx); + if (!ctx->err.has_error && len == 0) { + do_from_zval_err(ctx, "controllen cannot be 0"); + return; + } + msghdr->msg_control = accounted_emalloc(len, ctx); + msghdr->msg_controllen = len; +} +void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"iov", sizeof("iov"), 0, 0, from_zval_write_iov_array, 0}, + {"control", sizeof("control"), 0, 0, from_zval_write_control_array, 0}, + {0} + }; + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); +} +void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx) +{ + /* zval to struct msghdr, version for recvmsg(). It differs from the version + * for sendmsg() in that it: + * - has a buffer_size instead of an iov array; + * - has no control element; has a controllen element instead + * struct msghdr { + * void *msg_name; + * socklen_t msg_namelen; + * struct iovec *msg_iov; + * size_t msg_iovlen; + * void *msg_control; + * size_t msg_controllen; //can also be socklen_t + * int msg_flags; + * }; + */ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"buffer_size", sizeof("buffer_size"), 0, 0, from_zval_write_msghdr_buffer_size, 0}, + {"controllen", sizeof("controllen"), 1, 0, from_zval_write_controllen, 0}, + {0} + }; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + const int falsev = 0, + *falsevp = &falsev; + + if (zend_hash_add(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR), + (void*)&falsevp, sizeof(falsevp), NULL) == FAILURE) { + do_from_zval_err(ctx, "could not add fill_sockaddr; this is a bug"); + return; + } + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); + + zend_hash_del(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR)); + if (ctx->err.has_error) { + return; + } + + if (msghdr->msg_iovlen == 0) { + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)DEFAULT_BUFF_SIZE, ctx); + msghdr->msg_iov[0].iov_len = (size_t)DEFAULT_BUFF_SIZE; + } +} + +static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) +{ + const struct msghdr *msghdr = (const struct msghdr *)msghdr_c; + size_t iovlen = msghdr->msg_iovlen; + ssize_t **recvmsg_ret, + bytes_left; + uint i; + + if (iovlen > UINT_MAX) { + do_to_zval_err(ctx, "unexpectedly large value for iov_len: %lu", + (unsigned long)iovlen); + } + array_init_size(zv, (uint)iovlen); + + if (zend_hash_find(&ctx->params, KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), + (void**)&recvmsg_ret) == FAILURE) { + do_to_zval_err(ctx, "recvmsg_ret not found in params. This is a bug"); + return; + } + bytes_left = **recvmsg_ret; + + for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { + zval *elem; + size_t len = MIN(msghdr->msg_iov[i].iov_len, bytes_left); + char *buf = safe_emalloc(1, len, 1); + + MAKE_STD_ZVAL(elem); + memcpy(buf, msghdr->msg_iov[i].iov_base, len); + buf[len] = '\0'; + + ZVAL_STRINGL(elem, buf, len, 0); + add_next_index_zval(zv, elem); + bytes_left -= len; + } +} +void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, offsetof(struct msghdr, msg_name), 0, to_zval_read_name}, + {"control", sizeof("control"), 0, 0, 0, to_zval_read_control_array}, + {"iov", sizeof("iov"), 0, 0, 0, to_zval_read_iov}, + {"flags", sizeof("flags"), 0, offsetof(struct msghdr, msg_flags), 0, to_zval_read_int}, + {0} + }; + + array_init_size(zv, 4); + + to_zval_read_aggregation(msghdr_c, zv, descriptors, ctx); +} + +/* CONVERSIONS for if_index */ +static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context *ctx) +{ + zval *va; unsigned *out; + unsigned ret; + zval lzval = zval_used_for_init; + + if (Z_TYPE_P(zv) == IS_LONG) { + if (Z_LVAL_P(zv) < 0 || Z_LVAL_P(zv) > UINT_MAX) { + do_from_zval_err(ctx, "the interface index cannot be negative or " + "larger than %u; given %ld", UINT_MAX, Z_LVAL_P(zv)); + } else { + ret = (unsigned)Z_LVAL_P(zv); + } + } else { +#if HAVE_IF_NAMETOINDEX + + if (Z_TYPE_P(zv) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zv); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zv = &lzval; + } + + ret = if_nametoindex(Z_STRVAL_P(zv)); + if (ret == 0) { + do_from_zval_err(ctx, "no interface with name \"%s\" could be " + "found", Z_STRVAL_P(zv)); + } +#else + do_from_zval_err(ctx, + "this platform does not support looking up an interface by " + "name, an integer interface index must be supplied instead"); +#endif + } + + if (!ctx->err.has_error) { + memcpy(uinteger, &ret, sizeof(ret)); + } + + zval_dtor(&lzval); +} + +/* CONVERSIONS for struct in6_pktinfo */ +#ifdef IPV6_PKTINFO +static const field_descriptor descriptors_in6_pktinfo[] = { + {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, + {0} +}; +void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); +} +void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 2); + + to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); +} +#endif + +/* CONVERSIONS for struct ucred */ +#ifdef SO_PASSCRED +static const field_descriptor descriptors_ucred[] = { + {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, + {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, + /* assume the type gid_t is the same as uid_t: */ + {"gid", sizeof("gid"), 1, offsetof(struct ucred, gid), from_zval_write_uid_t, to_zval_read_uid_t}, + {0} +}; +void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, ucred_c, descriptors_ucred, ctx); +} +void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 3); + + to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); +} +#endif + +/* CONVERSIONS for SCM_RIGHTS */ +#ifdef SCM_RIGHTS +size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) +{ + int num_elems; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return (size_t)-1; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + do_from_zval_err(ctx, "%s", "expected at least one element in this array"); + return (size_t)-1; + } + + return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); +} +static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + int *iarr = args[0]; + + if (Z_TYPE_PP(elem) == IS_RESOURCE) { + php_stream *stream; + php_socket *sock; + + ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, + NULL, php_sockets_le_socket()); + if (sock) { + iarr[i] = sock->bsd_socket; + return; + } + + ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, + NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + do_from_zval_err(ctx, "resource is not a stream or a socket"); + return; + } + + if (php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&iarr[i], + REPORT_ERRORS) == FAILURE) { + do_from_zval_err(ctx, "cast stream to file descriptor failed"); + return; + } + } else { + do_from_zval_err(ctx, "expected a resource variable"); + } +} +void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx) +{ + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + from_array_iterate(arr, &from_zval_write_fd_array_aux, (void**)&int_arr, ctx); +} +void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) +{ + size_t **cmsg_len; + int num_elems, + i; + struct cmsghdr *dummy_cmsg = 0; + size_t data_offset; + TSRMLS_FETCH(); + + data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) + - (unsigned char *)dummy_cmsg; + + if (zend_hash_find(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + (void **)&cmsg_len) == FAILURE) { + do_to_zval_err(ctx, "could not get value of parameter " KEY_CMSG_LEN); + return; + } + + if (**cmsg_len < data_offset) { + do_to_zval_err(ctx, "length of cmsg is smaller than its data member " + "offset (%ld vs %ld)", (long)**cmsg_len, (long)data_offset); + return; + } + num_elems = (**cmsg_len - data_offset) / sizeof(int); + + array_init_size(zv, num_elems); + + for (i = 0; i < num_elems; i++) { + zval *elem; + int fd; + struct stat statbuf; + + MAKE_STD_ZVAL(elem); + + fd = *((int *)data + i); + + /* determine whether we have a socket */ + if (fstat(fd, &statbuf) == -1) { + do_to_zval_err(ctx, "error creating resource for received file " + "descriptor %d: fstat() call failed with errno %d", fd, errno); + efree(elem); + return; + } + if (S_ISSOCK(statbuf.st_mode)) { + php_socket *sock = socket_import_file_descriptor(fd); + zend_register_resource(elem, sock, php_sockets_le_socket()); + } else { + php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); + php_stream_to_zval(stream, elem); + } + + add_next_index_zval(zv, elem); + } +} +#endif + +/* ENTRY POINT for conversions */ +static void free_from_zval_allocation(void *alloc_ptr_ptr) +{ + efree(*(void**)alloc_ptr_ptr); +} +void *from_zval_run_conversions(const zval *container, + php_socket *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */) +{ + ser_context ctx = {{0}}; + char *structure = NULL; + + *allocations = NULL; + + if (err->has_error) { + return NULL; + } + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); + ctx.sock = sock; + + structure = ecalloc(1, struct_size); + + zend_llist_add_element(&ctx.keys, &top_name); + zend_llist_add_element(&ctx.allocations, &structure); + + /* main call */ + writer(container, structure, &ctx); + + if (ctx.err.has_error) { + zend_llist_destroy(&ctx.allocations); /* deallocates structure as well */ + structure = NULL; + *err = ctx.err; + } else { + *allocations = emalloc(sizeof **allocations); + **allocations = ctx.allocations; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return structure; +} +zval *to_zval_run_conversions(const char *structure, + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err) +{ + res_context ctx = {{0}, {0}}; + const struct key_value *kv; + zval *zv = NULL; + + if (err->has_error) { + return NULL; + } + + ALLOC_INIT_ZVAL(zv); + + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_add_element(&ctx.keys, &top_name); + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + for (kv = key_value_pairs; kv->key != NULL; kv++) { + zend_hash_update(&ctx.params, kv->key, kv->key_size, + (void*)&kv->value, sizeof(kv->value), NULL); + } + + /* main call */ + reader(structure, zv, &ctx); + + if (ctx.err.has_error) { + zval_ptr_dtor(&zv); + zv = NULL; + *err = ctx.err; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return zv; +} diff --git a/ext/sockets/conversions.h b/ext/sockets/conversions.h new file mode 100644 index 00000000000..70f31ba676e --- /dev/null +++ b/ext/sockets/conversions.h @@ -0,0 +1,78 @@ +#ifndef PHP_SOCK_CONVERSIONS_H +#define PHP_SOCK_CONVERSIONS_H 1 + +#include +#include +#include +#include "php_sockets.h" + +/* TYPE DEFINITIONS */ +struct err_s { + int has_error; + char *msg; + int level; + int should_free; +}; + +struct key_value { + const char *key; + unsigned key_size; + void *value; +}; + +/* the complete types of these two are not relevant to the outside */ +typedef struct _ser_context ser_context; +typedef struct _res_context res_context; + +#define KEY_RECVMSG_RET "recvmsg_ret" + +typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); +typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); + +/* VARIABLE DECLARATIONS */ +extern const struct key_value empty_key_value_list[]; + +/* AUX FUNCTIONS */ +void err_msg_dispose(struct err_s *err TSRMLS_DC); +void allocations_dispose(zend_llist **allocations); + +/* CONVERSION FUNCTIONS */ +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx); +void to_zval_read_int(const char *data, zval *zv, res_context *ctx); + +#ifdef IPV6_PKTINFO +void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx); +void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx); +#endif + +#ifdef SO_PASSCRED +void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx); +void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx); +#endif + +#ifdef SCM_RIGHTS +size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx); +void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx); +void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx); +#endif + +void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx); +void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx); +void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx); + +/* ENTRY POINTS FOR CONVERSIONS */ +void *from_zval_run_conversions(const zval *container, + php_socket *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */); + +zval *to_zval_run_conversions(const char *structure, + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err); + +#endif diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 6b1a528c5b1..6479bf90a85 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -18,18 +18,10 @@ #include #include "php_sockets.h" -#include "sockaddr_conv.h" #include "sendrecvmsg.h" -#include -#include -#include -#include +#include "conversions.h" #include -#include -#include -#include #include -#include #ifdef ZTS #include #endif @@ -47,1460 +39,11 @@ } \ } while (0) -struct err_s { - int has_error; - char *msg; - int level; - int should_free; -}; - -typedef struct { - HashTable params; /* stores pointers; has to be first */ - struct err_s err; - zend_llist keys, - /* common part to res_context ends here */ - allocations; - php_socket *sock; -} ser_context; - -typedef struct { - HashTable params; /* stores pointers; has to be first */ - struct err_s err; - zend_llist keys; -} res_context; - -struct key_value { - const char *key; - unsigned key_size; - void *value; -}; -#define KEY_FILL_SOCKADDR "fill_sockaddr" -#define KEY_RECVMSG_RET "recvmsg_ret" -#define KEY_CMSG_LEN "cmsg_len" -static const struct key_value empty_key_value_list[] = {{0}}; - - -typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); -typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); -typedef size_t (calculate_req_space)(const zval *value, ser_context *ctx); - -typedef struct { - /* zval info */ - const char *name; - unsigned name_size; - int required; - - /* structure info */ - size_t field_offset; /* 0 to pass full structure, e.g. when more than - one field is to be changed; in that case the - callbacks need to know the name of the fields */ - - /* callbacks */ - from_zval_write_field *from_zval; - to_zval_read_field *to_zval; -} field_descriptor; - -typedef struct { - int cmsg_level; /* originating protocol */ - int cmsg_type; /* protocol-specific type */ -} anc_reg_key; - static struct { int initialized; HashTable ht; } ancillary_registry; -typedef socklen_t (*ancillary_size)(void); - -typedef struct { - socklen_t size; /* size of native structure */ - socklen_t var_el_size; /* size of repeatable component */ - calculate_req_space *calc_space; - from_zval_write_field *from_array; - to_zval_read_field *to_array; -} ancillary_reg_entry; - -/* PARAMETERS */ -static int param_get_bool(void *ctx, const char *key, int def) -{ - int **elem; - if (zend_hash_find(ctx, key, strlen(key) + 1, (void**)&elem) == SUCCESS) { - return **elem; - } else { - return def; - } -} - -/* FORWARD DECLARATIONS */ -static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type); - -static inline void *accounted_emalloc(size_t alloc_size, ser_context *ctx) -{ - void *ret = emalloc(alloc_size); - zend_llist_add_element(&ctx->allocations, &ret); - return ret; -} - -static inline void *accounted_ecalloc(size_t nmemb, size_t alloc_size, ser_context *ctx) -{ - void *ret = ecalloc(nmemb, alloc_size); - zend_llist_add_element(&ctx->allocations, &ret); - return ret; -} -static inline void *accounted_safe_ecalloc(size_t nmemb, size_t alloc_size, size_t offset, ser_context *ctx) -{ - void *ret = safe_emalloc(nmemb, alloc_size, offset); - memset(ret, '\0', nmemb * alloc_size + offset); - zend_llist_add_element(&ctx->allocations, &ret); - return ret; -} - -static void do_from_to_zval_err(struct err_s *err, - zend_llist *keys, - const char *what_conv, - const char *fmt, - va_list ap) -{ - smart_str path = {0}; - const char **node; - char *user_msg; - int user_msg_size; - zend_llist_position pos; - - if (err->has_error) { - return; - } - - for (node = zend_llist_get_first_ex(keys, &pos); - node != NULL; - node = zend_llist_get_next_ex(keys, &pos)) { - smart_str_appends(&path, *node); - smart_str_appends(&path, " > "); - } - - if (path.len > 3) { - path.len -= 3; - } - smart_str_0(&path); - - user_msg_size = vspprintf(&user_msg, 0, fmt, ap); - - err->has_error = 1; - err->level = E_WARNING; - spprintf(&err->msg, 0, "error converting %s data (path: %s): %.*s", - what_conv, - path.c && path.c != '\0' ? path.c : "unavailable", - user_msg_size, user_msg); - err->should_free = 1; - - efree(user_msg); - smart_str_free_ex(&path, 0); -} -__attribute__ ((format (printf, 2, 3))) -static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); - va_end(ap); -} -__attribute__ ((format (printf, 2, 3))) -static void do_to_zval_err(res_context *ctx, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); - va_end(ap); -} -static void err_msg_dispose(struct err_s *err TSRMLS_DC) -{ - if (err->msg != NULL) { - php_error_docref0(NULL TSRMLS_CC, err->level, "%s", err->msg); - if (err->should_free) { - efree(err->msg); - } - } -} -static void allocations_dispose(zend_llist **allocations) -{ - zend_llist_destroy(*allocations); - efree(*allocations); - *allocations = NULL; -} - -static unsigned from_array_iterate(const zval *arr, - void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), - void **args, - ser_context *ctx) -{ - HashPosition pos; - unsigned i; - zval **elem; - char buf[sizeof("element #4294967295")]; - char *bufp = buf; - - for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 1; - !ctx->err.has_error - && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; - zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos), i++) { - if (snprintf(buf, sizeof(buf), "element #%u", i) >= sizeof(buf)) { - memcpy(buf, "element", sizeof("element")); - } - zend_llist_add_element(&ctx->keys, &bufp); - - func(elem, i, args, ctx); - - zend_llist_remove_tail(&ctx->keys); - } - - return i -1; -} - -/* Generic Aggregated conversions */ -static void from_zval_write_aggregation(const zval *container, - char *structure, - const field_descriptor *descriptors, - ser_context *ctx) -{ - const field_descriptor *descr; - zval **elem; - - if (Z_TYPE_P(container) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - } - - for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { - if (zend_hash_find(Z_ARRVAL_P(container), - descr->name, descr->name_size, (void**)&elem) == SUCCESS) { - - if (descr->from_zval == NULL) { - do_from_zval_err(ctx, "No information on how to convert value " - "of key '%s'", descr->name); - break; - } - - zend_llist_add_element(&ctx->keys, (void*)&descr->name); - descr->from_zval(*elem, ((char*)structure) + descr->field_offset, ctx); - zend_llist_remove_tail(&ctx->keys); - - } else if (descr->required) { - do_from_zval_err(ctx, "The key '%s' is required", descr->name); - break; - } - } -} -static void to_zval_read_aggregation(const char *structure, - zval *zarr, /* initialized array */ - const field_descriptor *descriptors, - res_context *ctx) -{ - const field_descriptor *descr; - - assert(Z_TYPE_P(zarr) == IS_ARRAY); - assert(Z_ARRVAL_P(zarr) != NULL); - - for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { - zval *new_zv; - - if (descr->to_zval == NULL) { - do_to_zval_err(ctx, "No information on how to convert native " - "field into value for key '%s'", descr->name); - break; - } - - ALLOC_INIT_ZVAL(new_zv); - add_assoc_zval_ex(zarr, descr->name, descr->name_size, new_zv); - - zend_llist_add_element(&ctx->keys, (void*)&descr->name); - descr->to_zval(structure + descr->field_offset, new_zv, ctx); - zend_llist_remove_tail(&ctx->keys); - } -} - -/* CONVERSIONS for integers */ -static long from_zval_integer_common(const zval *arr_value, ser_context *ctx) -{ - long ret = 0; - zval lzval = zval_used_for_init; - - if (Z_TYPE_P(arr_value) != IS_LONG) { - ZVAL_COPY_VALUE(&lzval, arr_value); - zval_copy_ctor(&lzval); - arr_value = &lzval; - } - - switch (Z_TYPE_P(arr_value)) { - case IS_LONG: -long_case: - ret = Z_LVAL_P(arr_value); - break; - - /* if not long we're operating on lzval */ - case IS_DOUBLE: -double_case: - convert_to_long(&lzval); - goto long_case; - - case IS_OBJECT: - case IS_STRING: { - long lval; - double dval; - - convert_to_string(&lzval); - - switch (is_numeric_string(Z_STRVAL(lzval), Z_STRLEN(lzval), &lval, &dval, 0)) { - case IS_DOUBLE: - zval_dtor(&lzval); - Z_TYPE(lzval) = IS_DOUBLE; - Z_DVAL(lzval) = dval; - goto double_case; - - case IS_LONG: - zval_dtor(&lzval); - Z_TYPE(lzval) = IS_LONG; - Z_DVAL(lzval) = lval; - goto long_case; - } - - /* if we get here, we don't have a numeric string */ - do_from_zval_err(ctx, "expected an integer, but got a non numeric " - "string (possibly from a converted object): '%s'", Z_STRVAL_P(arr_value)); - break; - } - - default: - do_from_zval_err(ctx, "%s", "expected an integer, either of a PHP " - "integer type or of a convertible type"); - break; - } - - zval_dtor(&lzval); - - return ret; -} -static void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - int ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval > INT_MAX || lval < INT_MIN) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a native int"); - return; - } - - ival = (int)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_unsigned(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - unsigned ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (sizeof(long) > sizeof(ival) && (lval < 0 || lval > UINT_MAX)) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a native unsigned int"); - return; - } - - ival = (unsigned)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - uint32_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (sizeof(long) > sizeof(uint32_t) && (lval < 0 || lval > 0xFFFFFFFF)) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for an unsigned 32-bit integer"); - return; - } - - ival = (uint32_t)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - uint16_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || lval > 0xFFFF) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for an unsigned 16-bit integer"); - return; - } - - ival = htons((uint16_t)lval); - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - sa_family_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || lval > (sa_family_t)-1) { /* sa_family_t is unsigned */ - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a sa_family_t value"); - return; - } - - ival = (sa_family_t)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_pid_t(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - pid_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || (pid_t)lval != lval) { /* pid_t is signed */ - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a pid_t value"); - return; - } - - ival = (pid_t)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - uid_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - /* uid_t can be signed or unsigned (generally unsigned) */ - if ((uid_t)-1 > (uid_t)0) { - if (sizeof(long) > sizeof(uid_t) && (lval < 0 || (uid_t)lval != lval)) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a uid_t value"); - return; - } - } else { - if (sizeof(long) > sizeof(uid_t) && (uid_t)lval != lval) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a uid_t value"); - return; - } - } - - ival = (uid_t)lval; - memcpy(field, &ival, sizeof(ival)); -} - -static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) -{ - int ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) -{ - unsigned ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) -{ - uint16_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ntohs(ival)); -} -static void to_zval_read_uint32(const char *data, zval *zv, res_context *ctx) -{ - uint32_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) -{ - sa_family_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_pid_t(const char *data, zval *zv, res_context *ctx) -{ - pid_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_uid_t(const char *data, zval *zv, res_context *ctx) -{ - uid_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} - -/* CONVERSIONS for sockaddr */ -static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) -{ - int res; - struct sockaddr_in saddr = {0}; - zval lzval = zval_used_for_init; - TSRMLS_FETCH(); - - if (Z_TYPE_P(zaddr_str) != IS_STRING) { - ZVAL_COPY_VALUE(&lzval, zaddr_str); - zval_copy_ctor(&lzval); - convert_to_string(&lzval); - zaddr_str = &lzval; - } - - res = php_set_inet_addr(&saddr, Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); - if (res) { - memcpy(inaddr, &saddr.sin_addr, sizeof saddr.sin_addr); - } else { - /* error already emitted, but let's emit another more relevant */ - do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET " - "address", Z_STRVAL_P(zaddr_str)); - } - - zval_dtor(&lzval); -} -static void to_zval_read_sin_addr(const char *data, zval *zv, res_context *ctx) -{ - const struct in_addr *addr = (const struct in_addr *)data; - socklen_t size = INET_ADDRSTRLEN; - - Z_TYPE_P(zv) = IS_STRING; - Z_STRVAL_P(zv) = ecalloc(1, size); - Z_STRLEN_P(zv) = 0; - - if (inet_ntop(AF_INET, addr, Z_STRVAL_P(zv), size) == NULL) { - do_to_zval_err(ctx, "could not convert IPv4 address to string " - "(errno %d)", errno); - return; - } - - Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); -} -static const field_descriptor descriptors_sockaddr_in[] = { - {"family", sizeof("family"), 0, offsetof(struct sockaddr_in, sin_family), from_zval_write_sa_family, to_zval_read_sa_family}, - {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in, sin_addr), from_zval_write_sin_addr, to_zval_read_sin_addr}, - {"port", sizeof("port"), 0, offsetof(struct sockaddr_in, sin_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, - {0} -}; -static void from_zval_write_sockaddr_in(const zval *container, char *sockaddr, ser_context *ctx) -{ - from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_in, ctx); -} -static void to_zval_read_sockaddr_in(const char *data, zval *zv, res_context *ctx) -{ - to_zval_read_aggregation(data, zv, descriptors_sockaddr_in, ctx); -} -static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) -{ - int res; - struct sockaddr_in6 saddr6 = {0}; - zval lzval = zval_used_for_init; - TSRMLS_FETCH(); - - if (Z_TYPE_P(zaddr_str) != IS_STRING) { - ZVAL_COPY_VALUE(&lzval, zaddr_str); - zval_copy_ctor(&lzval); - convert_to_string(&lzval); - zaddr_str = &lzval; - } - - res = php_set_inet6_addr(&saddr6, - Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); - if (res) { - memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); - } else { - /* error already emitted, but let's emit another more relevant */ - do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET6 " - "address", Z_STRVAL_P(zaddr_str)); - } - - zval_dtor(&lzval); -} -static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) -{ - const struct in6_addr *addr = (const struct in6_addr *)data; - socklen_t size = INET6_ADDRSTRLEN; - - Z_TYPE_P(zv) = IS_STRING; - Z_STRVAL_P(zv) = ecalloc(1, size); - Z_STRLEN_P(zv) = 0; - - if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { - do_to_zval_err(ctx, "could not convert IPv6 address to string " - "(errno %d)", errno); - return; - } - - Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); -} -static const field_descriptor descriptors_sockaddr_in6[] = { - {"family", sizeof("family"), 0, offsetof(struct sockaddr_in6, sin6_family), from_zval_write_sa_family, to_zval_read_sa_family}, - {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in6, sin6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, - {"port", sizeof("port"), 0, offsetof(struct sockaddr_in6, sin6_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, - {"flowinfo", sizeof("flowinfo"), 0, offsetof(struct sockaddr_in6, sin6_flowinfo), from_zval_write_uint32, to_zval_read_uint32}, - {"scope_id", sizeof("scope_id"), 0, offsetof(struct sockaddr_in6, sin6_scope_id), from_zval_write_uint32, to_zval_read_uint32}, - {0} -}; -static void from_zval_write_sockaddr_in6(const zval *container, char *sockaddr6, ser_context *ctx) -{ - from_zval_write_aggregation(container, sockaddr6, descriptors_sockaddr_in6, ctx); -} -static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *ctx) -{ - to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); -} -static void from_zval_write_sun_path(const zval *path, char *sockaddr_un_c, ser_context *ctx) -{ - zval lzval = zval_used_for_init; - struct sockaddr_un *saddr = (struct sockaddr_un*)sockaddr_un_c; - - if (Z_TYPE_P(path) != IS_STRING) { - ZVAL_COPY_VALUE(&lzval, path); - zval_copy_ctor(&lzval); - convert_to_string(&lzval); - path = &lzval; - } - - if (Z_STRLEN_P(path) >= sizeof(saddr->sun_path)) { - do_from_zval_err(ctx, "the path is too long, the maximum permitted " - "length is %ld", sizeof(saddr->sun_path) - 1); - return; - } - - memcpy(&saddr->sun_path, Z_STRVAL_P(path), Z_STRLEN_P(path)); - saddr->sun_path[Z_STRLEN_P(path)] = '\0'; - - zval_dtor(&lzval); -} -static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) { - struct sockaddr_un *saddr = (struct sockaddr_un*)data; - char *nul_pos; - - nul_pos = memchr(&saddr->sun_path, '\0', sizeof(saddr->sun_path)); - if (nul_pos == NULL) { - do_to_zval_err(ctx, "could not find a NUL in the path"); - return; - } - - ZVAL_STRINGL(zv, saddr->sun_path, nul_pos - (char*)&saddr->sun_path, 1); -} -static const field_descriptor descriptors_sockaddr_un[] = { - {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, - {"path", sizeof("path"), 0, 0, from_zval_write_sun_path, to_zval_read_sun_path}, - {0} -}; -static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) -{ - from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_un, ctx); -} -static void to_zval_read_sockaddr_un(const char *data, zval *zv, res_context *ctx) -{ - to_zval_read_aggregation(data, zv, descriptors_sockaddr_un, ctx); -} -static void from_zval_write_sockaddr_aux(const zval *container, - struct sockaddr **sockaddr_ptr, - socklen_t *sockaddr_len, - ser_context *ctx) -{ - int family; - zval **elem; - int fill_sockaddr; - - if (Z_TYPE_P(container) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return; - } - - fill_sockaddr = param_get_bool(ctx, KEY_FILL_SOCKADDR, 1); - - if (zend_hash_find(Z_ARRVAL_P(container), "family", sizeof("family"), (void**)&elem) == SUCCESS - && Z_TYPE_PP(elem) != IS_NULL) { - const char *node = "family"; - zend_llist_add_element(&ctx->keys, &node); - from_zval_write_int(*elem, (char*)&family, ctx); - zend_llist_remove_tail(&ctx->keys); - } else { - family = ctx->sock->type; - } - - switch (family) { - case AF_INET: - /* though not all OSes support sockaddr_in used in IPv6 sockets */ - if (ctx->sock->type != AF_INET && ctx->sock->type != AF_INET6) { - do_from_zval_err(ctx, "the specified family (number %d) is not " - "supported on this socket", family); - return; - } - *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in), ctx); - *sockaddr_len = sizeof(struct sockaddr_in); - if (fill_sockaddr) { - from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); - (*sockaddr_ptr)->sa_family = AF_INET; - } - break; - - case AF_INET6: - if (ctx->sock->type != AF_INET6) { - do_from_zval_err(ctx, "the specified family (AF_INET6) is not " - "supported on this socket"); - return; - } - *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in6), ctx); - *sockaddr_len = sizeof(struct sockaddr_in6); - if (fill_sockaddr) { - from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); - (*sockaddr_ptr)->sa_family = AF_INET6; - } - break; - - case AF_UNIX: - if (ctx->sock->type != AF_UNIX) { - do_from_zval_err(ctx, "the specified family (AF_UNIX) is not " - "supported on this socket"); - return; - } - *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_un), ctx); - *sockaddr_len = sizeof(struct sockaddr_un); - if (fill_sockaddr) { - from_zval_write_sockaddr_un(container, (char*)*sockaddr_ptr, ctx); - (*sockaddr_ptr)->sa_family = AF_UNIX; - } - break; - - default: - do_from_zval_err(ctx, "%s", "the only families currently supported are " - "AF_INET, AF_INET6 and AF_UNIX"); - break; - } -} -static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_context *ctx) -{ - const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; - - if (saddr->sa_family == 0) { - ZVAL_NULL(zv); - return; - } - - array_init(zv); - - switch (saddr->sa_family) { - case AF_INET: - to_zval_read_sockaddr_in(sockaddr_c, zv, ctx); - break; - - case AF_INET6: - to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); - break; - - case AF_UNIX: - to_zval_read_sockaddr_un(sockaddr_c, zv, ctx); - break; - - default: - do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " - "not supported", - (int)saddr->sa_family); - break; - } -} - -/* CONVERSIONS for cmsghdr */ -/* - * [ level => , type => , data => [],] - * struct cmsghdr { - * socklen_t cmsg_len; // data byte count, including header - * int cmsg_level; // originating protocol - * int cmsg_type; // protocol-specific type - * // followed by unsigned char cmsg_data[]; - * }; - */ -static void from_zval_write_control(const zval *arr, - void **control_buf, - zend_llist_element *alloc, - size_t *control_len, - size_t *offset, - ser_context *ctx) -{ - struct cmsghdr *cmsghdr; - int level, - type; - size_t data_len, - req_space, - space_left; - ancillary_reg_entry *entry; - - static const field_descriptor descriptor_level[] = { - {"level", sizeof("level"), 0, 0, from_zval_write_int, 0}, - {0} - }; - static const field_descriptor descriptor_type[] = { - {"type", sizeof("type"), 0, 0, from_zval_write_int, 0}, - {0} - }; - field_descriptor descriptor_data[] = { - {"data", sizeof("data"), 0, 0, 0, 0}, - {0} - }; - - from_zval_write_aggregation(arr, (char *)&level, descriptor_level, ctx); - if (ctx->err.has_error) { - return; - } - from_zval_write_aggregation(arr, (char *)&type, descriptor_type, ctx); - if (ctx->err.has_error) { - return; - } - - entry = get_ancillary_reg_entry(level, type); - if (entry == NULL) { - do_from_zval_err(ctx, "cmsghdr with level %d and type %d not supported", - level, type); - return; - } - - if (entry->calc_space) { - data_len = entry->calc_space(arr, ctx); - if (ctx->err.has_error) { - return; - } - } else { - data_len = entry->size; - } - req_space = CMSG_SPACE(data_len); - space_left = *control_len - *offset; - assert(*control_len >= *offset); - - if (space_left < req_space) { - *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); - *control_len += 2 * req_space; - memcpy(&alloc->data, *control_buf, sizeof *control_buf); - } - - cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); - cmsghdr->cmsg_level = level; - cmsghdr->cmsg_type = type; - cmsghdr->cmsg_len = CMSG_LEN(data_len); - - descriptor_data[0].from_zval = entry->from_array; - from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); - - *offset += req_space; -} -static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_context *ctx) -{ - HashPosition pos; - char buf[sizeof("element #4294967295")]; - char *bufp = buf; - zval **elem; - uint32_t i; - int num_elems; - void *control_buf; - zend_llist_element *alloc; - size_t control_len, - cur_offset; - struct msghdr *msg = (struct msghdr*)msghdr_c; - - if (Z_TYPE_P(arr) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return; - } - - num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); - if (num_elems == 0) { - return; - } - - /* estimate each message at 20 bytes */ - control_buf = accounted_safe_ecalloc(num_elems, CMSG_SPACE(20), 0, ctx); - alloc = ctx->allocations.tail; - control_len = (size_t)num_elems * CMSG_SPACE(20); - cur_offset = 0; - - for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; - !ctx->err.has_error - && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; - zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { - - if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { - memcpy(buf, "element", sizeof("element")); - } - zend_llist_add_element(&ctx->keys, &bufp); - - from_zval_write_control(*elem, &control_buf, alloc, &control_len, - &cur_offset, ctx); - - zend_llist_remove_tail(&ctx->keys); - } - - msg->msg_control = control_buf; - msg->msg_controllen = cur_offset; /* not control_len, which may be larger */ -} -static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) -{ - const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; - ancillary_reg_entry *entry; - size_t len, - *len_p = &len; - - entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); - if (entry == NULL) { - do_to_zval_err(ctx, "cmsghdr with level %d and type %d not supported", - cmsg->cmsg_level, cmsg->cmsg_type); - return; - } - if (CMSG_LEN(entry->size) > cmsg->cmsg_len) { - do_to_zval_err(ctx, "the cmsghdr structure is unexpectedly small; " - "expected a length of at least %ld, but got %ld", - (long)CMSG_LEN(entry->size), (long)cmsg->cmsg_len); - return; - } - - len = (size_t)cmsg->cmsg_len; /* use another var because type of cmsg_len varies */ - if (zend_hash_add(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), - &len_p, sizeof(len_p), NULL) == FAILURE) { - do_to_zval_err(ctx, "%s", "could not set parameter " KEY_CMSG_LEN); - return; - } - - entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); - - zend_hash_del(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN)); -} -static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) -{ - /* takes a cmsghdr, not a msghdr like from_zval_write_control */ - static const field_descriptor descriptors[] = { - {"level", sizeof("level"), 0, offsetof(struct cmsghdr, cmsg_level), 0, to_zval_read_int}, - {"type", sizeof("type"), 0, offsetof(struct cmsghdr, cmsg_type), 0, to_zval_read_int}, - {"data", sizeof("data"), 0, 0 /* cmsghdr passed */, 0, to_zval_read_cmsg_data}, - {0} - }; - - array_init_size(zv, 3); - to_zval_read_aggregation(cmsghdr_c, zv, descriptors, ctx); -} -static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_context *ctx) -{ - struct msghdr *msg = (struct msghdr *)msghdr_c; - struct cmsghdr *cmsg; - char buf[sizeof("element #4294967295")]; - char *bufp = buf; - uint32_t i = 1; - - /*if (msg->msg_flags & MSG_CTRUNC) { - php_error_docref0(NULL, E_WARNING, "The MSG_CTRUNC flag is present; will not " - "attempt to read control messages"); - ZVAL_FALSE(zv); - return; - }*/ - - array_init(zv); - - for (cmsg = CMSG_FIRSTHDR(msg); - cmsg != NULL && !ctx->err.has_error; - cmsg = CMSG_NXTHDR(msg,cmsg)) { - zval *elem; - - ALLOC_INIT_ZVAL(elem); - add_next_index_zval(zv, elem); - - if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { - memcpy(buf, "element", sizeof("element")); - } - zend_llist_add_element(&ctx->keys, &bufp); - - to_zval_read_control((const char *)cmsg, elem, ctx); - - zend_llist_remove_tail(&ctx->keys); - } -} - -/* CONVERSIONS for msghdr */ -static void from_zval_write_name(const zval *zname_arr, char *msghdr_c, ser_context *ctx) -{ - struct sockaddr *sockaddr; - socklen_t sockaddr_len; - struct msghdr *msghdr = (struct msghdr *)msghdr_c; - - from_zval_write_sockaddr_aux(zname_arr, &sockaddr, &sockaddr_len, ctx); - - msghdr->msg_name = sockaddr; - msghdr->msg_namelen = sockaddr_len; -} -static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx) -{ - void *name = (void*)*(void**)sockaddr_p; - if (name == NULL) { - ZVAL_NULL(zv); - } else { - to_zval_read_sockaddr_aux(name, zv, ctx); - } -} -static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, ser_context *ctx) -{ - long lval; - struct msghdr *msghdr = (struct msghdr *)msghdr_c; - - lval = from_zval_integer_common(elem, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || lval > MAX_USER_BUFF_SIZE) { - do_from_zval_err(ctx, "the buffer size must be between 1 and %ld; " - "given %ld", (long)MAX_USER_BUFF_SIZE, lval); - return; - } - - msghdr->msg_iovlen = 1; - msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); - msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); - msghdr->msg_iov[0].iov_len = (size_t)lval; -} -static void from_zval_write_iov_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) -{ - struct msghdr *msg = args[0]; - size_t len; - - zval_add_ref(elem); - convert_to_string_ex(elem); - - len = Z_STRLEN_PP(elem); - msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); - msg->msg_iov[i - 1].iov_len = len; - memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); - - zval_ptr_dtor(elem); -} -static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) -{ - int num_elem; - struct msghdr *msg = (struct msghdr*)msghdr_c; - - if (Z_TYPE_P(arr) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return; - } - - num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); - if (num_elem == 0) { - return; - } - - msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); - msg->msg_iovlen = (size_t)num_elem; - - from_array_iterate(arr, from_zval_write_iov_array_aux, (void**)&msg, ctx); -} -static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) -{ - struct msghdr *msghdr = (struct msghdr *)msghdr_c; - uint32_t len; - - /* controllen should be an unsigned with at least 32-bit. Let's assume - * this least common denominator - */ - from_zval_write_uint32(elem, (char*)&len, ctx); - if (!ctx->err.has_error && len == 0) { - do_from_zval_err(ctx, "controllen cannot be 0"); - return; - } - msghdr->msg_control = accounted_emalloc(len, ctx); - msghdr->msg_controllen = len; -} -static void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx) -{ - static const field_descriptor descriptors[] = { - {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, - {"iov", sizeof("iov"), 0, 0, from_zval_write_iov_array, 0}, - {"control", sizeof("control"), 0, 0, from_zval_write_control_array, 0}, - {0} - }; - - from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); -} -static void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx) -{ - /* zval to struct msghdr, version for recvmsg(). It differs from the version - * for sendmsg() in that it: - * - has a buffer_size instead of an iov array; - * - has no control element; has a controllen element instead - * struct msghdr { - * void *msg_name; - * socklen_t msg_namelen; - * struct iovec *msg_iov; - * size_t msg_iovlen; - * void *msg_control; - * size_t msg_controllen; //can also be socklen_t - * int msg_flags; - * }; - */ - static const field_descriptor descriptors[] = { - {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, - {"buffer_size", sizeof("buffer_size"), 0, 0, from_zval_write_msghdr_buffer_size, 0}, - {"controllen", sizeof("controllen"), 1, 0, from_zval_write_controllen, 0}, - {0} - }; - struct msghdr *msghdr = (struct msghdr *)msghdr_c; - const int falsev = 0, - *falsevp = &falsev; - - if (zend_hash_add(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR), - (void*)&falsevp, sizeof(falsevp), NULL) == FAILURE) { - do_from_zval_err(ctx, "could not add fill_sockaddr; this is a bug"); - return; - } - - from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); - - zend_hash_del(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR)); - if (ctx->err.has_error) { - return; - } - - if (msghdr->msg_iovlen == 0) { - msghdr->msg_iovlen = 1; - msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); - msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)DEFAULT_BUFF_SIZE, ctx); - msghdr->msg_iov[0].iov_len = (size_t)DEFAULT_BUFF_SIZE; - } -} - -static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) -{ - const struct msghdr *msghdr = (const struct msghdr *)msghdr_c; - size_t iovlen = msghdr->msg_iovlen; - ssize_t **recvmsg_ret, - bytes_left; - uint i; - - if (iovlen > UINT_MAX) { - do_to_zval_err(ctx, "unexpectedly large value for iov_len: %lu", - (unsigned long)iovlen); - } - array_init_size(zv, (uint)iovlen); - - if (zend_hash_find(&ctx->params, KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), - (void**)&recvmsg_ret) == FAILURE) { - do_to_zval_err(ctx, "recvmsg_ret not found in params. This is a bug"); - return; - } - bytes_left = **recvmsg_ret; - - for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { - zval *elem; - size_t len = MIN(msghdr->msg_iov[i].iov_len, bytes_left); - char *buf = safe_emalloc(1, len, 1); - - MAKE_STD_ZVAL(elem); - memcpy(buf, msghdr->msg_iov[i].iov_base, len); - buf[len] = '\0'; - - ZVAL_STRINGL(elem, buf, len, 0); - add_next_index_zval(zv, elem); - bytes_left -= len; - } -} -static void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) -{ - static const field_descriptor descriptors[] = { - {"name", sizeof("name"), 0, offsetof(struct msghdr, msg_name), 0, to_zval_read_name}, - {"control", sizeof("control"), 0, 0, 0, to_zval_read_control_array}, - {"iov", sizeof("iov"), 0, 0, 0, to_zval_read_iov}, - {"flags", sizeof("flags"), 0, offsetof(struct msghdr, msg_flags), 0, to_zval_read_int}, - {0} - }; - - array_init_size(zv, 4); - - to_zval_read_aggregation(msghdr_c, zv, descriptors, ctx); -} - - -/* CONVERSIONS for struct in6_pktinfo */ -#ifdef IPV6_PKTINFO -static const field_descriptor descriptors_in6_pktinfo[] = { - {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, - {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, - {0} -}; -static void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) -{ - from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); -} -static void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) -{ - array_init_size(zv, 2); - - to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); -} -#endif - -/* CONVERSIONS for struct ucred */ -#ifdef SO_PASSCRED -static const field_descriptor descriptors_ucred[] = { - {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, - {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, - /* assume the type gid_t is the same as uid_t: */ - {"gid", sizeof("gid"), 1, offsetof(struct ucred, gid), from_zval_write_uid_t, to_zval_read_uid_t}, - {0} -}; -static void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx) -{ - from_zval_write_aggregation(container, ucred_c, descriptors_ucred, ctx); -} -static void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) -{ - array_init_size(zv, 3); - - to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); -} -#endif - -/* CONVERSIONS for SCM_RIGHTS */ -#ifdef SCM_RIGHTS -static size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) -{ - int num_elems; - - if (Z_TYPE_P(arr) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return (size_t)-1; - } - - num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); - if (num_elems == 0) { - do_from_zval_err(ctx, "%s", "expected at least one element in this array"); - return (size_t)-1; - } - - return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); -} -static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) -{ - int *iarr = args[0]; - - if (Z_TYPE_PP(elem) == IS_RESOURCE) { - php_stream *stream; - php_socket *sock; - - ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, - NULL, php_sockets_le_socket()); - if (sock) { - iarr[i] = sock->bsd_socket; - return; - } - - ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, - NULL, php_file_le_stream(), php_file_le_pstream()); - if (stream == NULL) { - do_from_zval_err(ctx, "resource is not a stream or a socket"); - return; - } - - if (php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&iarr[i], - REPORT_ERRORS) == FAILURE) { - do_from_zval_err(ctx, "cast stream to file descriptor failed"); - return; - } - } else { - do_from_zval_err(ctx, "expected a resource variable"); - } -} -static void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx) -{ - if (Z_TYPE_P(arr) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return; - } - - from_array_iterate(arr, &from_zval_write_fd_array_aux, (void**)&int_arr, ctx); -} -static void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) -{ - size_t **cmsg_len; - int num_elems, - i; - struct cmsghdr *dummy_cmsg = 0; - size_t data_offset; - TSRMLS_FETCH(); - - data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) - - (unsigned char *)dummy_cmsg; - - if (zend_hash_find(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), - (void **)&cmsg_len) == FAILURE) { - do_to_zval_err(ctx, "could not get value of parameter " KEY_CMSG_LEN); - return; - } - - if (**cmsg_len < data_offset) { - do_to_zval_err(ctx, "length of cmsg is smaller than its data member " - "offset (%ld vs %ld)", (long)**cmsg_len, (long)data_offset); - return; - } - num_elems = (**cmsg_len - data_offset) / sizeof(int); - - array_init_size(zv, num_elems); - - for (i = 0; i < num_elems; i++) { - zval *elem; - int fd; - struct stat statbuf; - - MAKE_STD_ZVAL(elem); - - fd = *((int *)data + i); - - /* determine whether we have a socket */ - if (fstat(fd, &statbuf) == -1) { - do_to_zval_err(ctx, "error creating resource for received file " - "descriptor %d: fstat() call failed with errno %d", fd, errno); - efree(elem); - return; - } - if (S_ISSOCK(statbuf.st_mode)) { - php_socket *sock = socket_import_file_descriptor(fd); - zend_register_resource(elem, sock, php_sockets_le_socket()); - } else { - php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); - php_stream_to_zval(stream, elem); - } - - add_next_index_zval(zv, elem); - } -} -#endif - -/* ENTRY POINT for conversions */ -static void free_from_zval_allocation(void *alloc_ptr_ptr) -{ - efree(*(void**)alloc_ptr_ptr); -} -static void *from_zval_run_conversions(const zval *container, - php_socket *sock, - from_zval_write_field *writer, - size_t struct_size, - const char *top_name, - zend_llist **allocations /* out */, - struct err_s *err /* in/out */) -{ - ser_context ctx = {{0}}; - char *structure = NULL; - - *allocations = NULL; - - if (err->has_error) { - return NULL; - } - - zend_hash_init(&ctx.params, 8, NULL, NULL, 0); - zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); - zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); - ctx.sock = sock; - - structure = ecalloc(1, struct_size); - - zend_llist_add_element(&ctx.keys, &top_name); - zend_llist_add_element(&ctx.allocations, &structure); - - /* main call */ - writer(container, structure, &ctx); - - if (ctx.err.has_error) { - zend_llist_destroy(&ctx.allocations); /* deallocates structure as well */ - structure = NULL; - *err = ctx.err; - } else { - *allocations = emalloc(sizeof **allocations); - **allocations = ctx.allocations; - } - - zend_llist_destroy(&ctx.keys); - zend_hash_destroy(&ctx.params); - - return structure; -} -static zval *to_zval_run_conversions(const char *structure, - to_zval_read_field *reader, - const char *top_name, - const struct key_value *key_value_pairs, - struct err_s *err) -{ - res_context ctx = {{0}, {0}}; - const struct key_value *kv; - zval *zv = NULL; - - if (err->has_error) { - return NULL; - } - - ALLOC_INIT_ZVAL(zv); - - zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); - zend_llist_add_element(&ctx.keys, &top_name); - - zend_hash_init(&ctx.params, 8, NULL, NULL, 0); - for (kv = key_value_pairs; kv->key != NULL; kv++) { - zend_hash_update(&ctx.params, kv->key, kv->key_size, - (void*)&kv->value, sizeof(kv->value), NULL); - } - - /* main call */ - reader(structure, zv, &ctx); - - if (ctx.err.has_error) { - zval_ptr_dtor(&zv); - zv = NULL; - *err = ctx.err; - } - - zend_llist_destroy(&ctx.keys); - zend_hash_destroy(&ctx.params); - - return zv; -} #ifdef ZTS static MUTEX_T ancillary_mutex; @@ -1555,7 +98,7 @@ static void destroy_ancillary_registry(void) ancillary_registry.initialized = 0; } } -static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) +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; diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h index 82fb38b4b5f..55dca3c1fbc 100644 --- a/ext/sockets/sendrecvmsg.h +++ b/ext/sockets/sendrecvmsg.h @@ -1,5 +1,10 @@ -#include +#ifndef PHP_SENDRECVMSG_H +#define PHP_SENDRECVMSG_H 1 +#include +#include "conversions.h" + +/* for sockets.c */ PHP_FUNCTION(socket_sendmsg); PHP_FUNCTION(socket_recvmsg); PHP_FUNCTION(socket_cmsg_space); @@ -9,3 +14,23 @@ void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4); int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result); + +/* for conversions.c */ +typedef struct { + int cmsg_level; /* originating protocol */ + int cmsg_type; /* protocol-specific type */ +} anc_reg_key; + +typedef size_t (calculate_req_space)(const zval *value, ser_context *ctx); + +typedef struct { + socklen_t size; /* size of native structure */ + socklen_t var_el_size; /* size of repeatable component */ + calculate_req_space *calc_space; + from_zval_write_field *from_array; + to_zval_read_field *to_array; +} ancillary_reg_entry; + +ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type); + +#endif From bd580db373ce35aa2e60ca452ae4eb1984b0520e Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 1 Jan 2013 23:38:19 +0100 Subject: [PATCH 36/55] Build fixes; accept names for if_index --- ext/sockets/conversions.c | 61 +++++++++++++++++++++------------------ ext/sockets/multicast.c | 4 +-- ext/sockets/multicast.h | 4 +-- ext/sockets/sendrecvmsg.c | 10 +++---- ext/sockets/sendrecvmsg.h | 4 +-- ext/sockets/sockets.c | 10 +++---- 6 files changed, 48 insertions(+), 45 deletions(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 7ca9972ac0d..54631604ab4 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -12,6 +12,9 @@ #include #include +#include +#include + #include #include #include @@ -53,6 +56,8 @@ typedef struct { #define KEY_RECVMSG_RET "recvmsg_ret" #define KEY_CMSG_LEN "cmsg_len" +const struct key_value empty_key_value_list[] = {{0}}; + /* PARAMETERS */ static int param_get_bool(void *ctx, const char *key, int def) { @@ -331,25 +336,6 @@ void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) ival = (int)lval; memcpy(field, &ival, sizeof(ival)); } -static void from_zval_write_unsigned(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - unsigned ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (sizeof(long) > sizeof(ival) && (lval < 0 || lval > UINT_MAX)) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a native unsigned int"); - return; - } - - ival = (unsigned)lval; - memcpy(field, &ival, sizeof(ival)); -} static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) { long lval; @@ -1192,20 +1178,17 @@ void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) /* CONVERSIONS for if_index */ static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context *ctx) { - zval *va; unsigned *out; - unsigned ret; - zval lzval = zval_used_for_init; + unsigned ret; + zval lzval = zval_used_for_init; if (Z_TYPE_P(zv) == IS_LONG) { - if (Z_LVAL_P(zv) < 0 || Z_LVAL_P(zv) > UINT_MAX) { + if (Z_LVAL_P(zv) < 0 || Z_LVAL_P(zv) > UINT_MAX) { /* allow 0 (unspecified interface) */ do_from_zval_err(ctx, "the interface index cannot be negative or " "larger than %u; given %ld", UINT_MAX, Z_LVAL_P(zv)); } else { ret = (unsigned)Z_LVAL_P(zv); } } else { -#if HAVE_IF_NAMETOINDEX - if (Z_TYPE_P(zv) != IS_STRING) { ZVAL_COPY_VALUE(&lzval, zv); zval_copy_ctor(&lzval); @@ -1213,11 +1196,32 @@ static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context zv = &lzval; } +#if HAVE_IF_NAMETOINDEX ret = if_nametoindex(Z_STRVAL_P(zv)); if (ret == 0) { do_from_zval_err(ctx, "no interface with name \"%s\" could be " "found", Z_STRVAL_P(zv)); } +#elif defined(SIOCGIFINDEX) + { + struct ifreq ifr; + if (strlcpy(ifr.ifr_name, Z_STRVAL_P(zv), sizeof(ifr.ifr_name)) + >= sizeof(ifr.ifr_name)) { + do_from_zval_err(ctx, "the interface name \"%s\" is too large ", + Z_STRVAL_P(zv)); + } else if (ioctl(ctx->sock->bsd_socket, SIOCGIFINDEX, &ifr) < 0) { + if (errno == ENODEV) { + do_from_zval_err(ctx, "no interface with name \"%s\" could be " + "found", Z_STRVAL_P(zv)); + } else { + do_from_zval_err(ctx, "error fetching interface index for " + "interface with name \"%s\" (errno %d)", + Z_STRVAL_P(zv), errno); + } + } else { + ret = (unsigned)ifr.ifr_ifindex; + } + } #else do_from_zval_err(ctx, "this platform does not support looking up an interface by " @@ -1236,7 +1240,7 @@ static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context #ifdef IPV6_PKTINFO static const field_descriptor descriptors_in6_pktinfo[] = { {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, - {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, + {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_ifindex, to_zval_read_unsigned}, {0} }; void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) @@ -1294,6 +1298,7 @@ size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) { int *iarr = args[0]; + TSRMLS_FETCH(); if (Z_TYPE_PP(elem) == IS_RESOURCE) { php_stream *stream; @@ -1375,8 +1380,8 @@ void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) return; } if (S_ISSOCK(statbuf.st_mode)) { - php_socket *sock = socket_import_file_descriptor(fd); - zend_register_resource(elem, sock, php_sockets_le_socket()); + php_socket *sock = socket_import_file_descriptor(fd TSRMLS_CC); + zend_register_resource(elem, sock, php_sockets_le_socket() TSRMLS_CC); } else { php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); php_stream_to_zval(stream, elem); diff --git a/ext/sockets/multicast.c b/ext/sockets/multicast.c index dc242693acc..5d29c9c5e26 100644 --- a/ext/sockets/multicast.c +++ b/ext/sockets/multicast.c @@ -250,7 +250,7 @@ mcast_req_fun: int php_do_setsockopt_ip_mcast(php_socket *php_sock, int level, int optname, - zval **arg4) + zval **arg4 TSRMLS_DC) { unsigned int if_index; struct in_addr if_addr; @@ -319,7 +319,7 @@ dosockopt: int php_do_setsockopt_ipv6_mcast(php_socket *php_sock, int level, int optname, - zval **arg4) + zval **arg4 TSRMLS_DC) { unsigned int if_index; void *opt_ptr; diff --git a/ext/sockets/multicast.h b/ext/sockets/multicast.h index c363b584727..1ad4673feb7 100644 --- a/ext/sockets/multicast.h +++ b/ext/sockets/multicast.h @@ -30,12 +30,12 @@ int php_do_setsockopt_ip_mcast(php_socket *php_sock, int level, int optname, - zval **arg4); + zval **arg4 TSRMLS_DC); int php_do_setsockopt_ipv6_mcast(php_socket *php_sock, int level, int optname, - zval **arg4); + zval **arg4 TSRMLS_DC); int php_if_index_to_addr4( unsigned if_index, diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 6479bf90a85..b83b3ae4823 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -272,7 +272,7 @@ PHP_FUNCTION(socket_cmsg_space) RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size)); } -int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4) +int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4 TSRMLS_DC) { struct err_s err = {0}; zend_llist *allocations = NULL; @@ -311,7 +311,7 @@ dosockopt: return retval != 0 ? FAILURE : SUCCESS; } -int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result) +int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result TSRMLS_DC) { struct err_s err = {0}; void *buffer; @@ -340,7 +340,7 @@ int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo", empty_key_value_list, &err); if (err.has_error) { - err_msg_dispose(&err); + err_msg_dispose(&err TSRMLS_CC); res = -1; } else { ZVAL_COPY_VALUE(result, zv); @@ -354,9 +354,7 @@ int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS) { - /* IPv6 ancillary data - * Note that support for sticky options via setsockopt() is not implemented - * yet (where special support is needed, i.e., the optval is not an int). */ + /* IPv6 ancillary data */ #ifdef IPV6_RECVPKTINFO REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h index 55dca3c1fbc..5a3798274fd 100644 --- a/ext/sockets/sendrecvmsg.h +++ b/ext/sockets/sendrecvmsg.h @@ -12,8 +12,8 @@ PHP_FUNCTION(socket_cmsg_space); void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS); void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); -int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4); -int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result); +int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4 TSRMLS_DC); +int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result TSRMLS_DC); /* for conversions.c */ typedef struct { diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 9c57e2d98d2..2aeb4b0fd33 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1878,7 +1878,7 @@ PHP_FUNCTION(socket_get_option) } } } else if (level == IPPROTO_IPV6) { - int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value); + int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value TSRMLS_CC); if (ret == SUCCESS) { return; } else if (ret == FAILURE) { @@ -1981,15 +1981,15 @@ PHP_FUNCTION(socket_set_option) if (level == IPPROTO_IP) { - int res = php_do_setsockopt_ip_mcast(php_sock, level, optname, arg4); + int res = php_do_setsockopt_ip_mcast(php_sock, level, optname, arg4 TSRMLS_CC); HANDLE_SUBCALL(res); } #if HAVE_IPV6 else if (level == IPPROTO_IPV6) { - int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4); + int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4 TSRMLS_CC); if (res == 1) { - res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4); + res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4 TSRMLS_CC); } HANDLE_SUBCALL(res); } @@ -2273,7 +2273,7 @@ PHP_FUNCTION(socket_import_stream) RETURN_FALSE; } - retsock = socket_import_file_descriptor(socket); + retsock = socket_import_file_descriptor(socket TSRMLS_CC); if (retsock == NULL) { RETURN_FALSE; } From c846fcef685c14a42ae770d56340a41d936deae9 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 00:40:17 +0100 Subject: [PATCH 37/55] Fix buf in string -> int conv. --- ext/sockets/conversions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 54631604ab4..9cbc6e5f88e 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -297,7 +297,7 @@ double_case: case IS_LONG: zval_dtor(&lzval); Z_TYPE(lzval) = IS_LONG; - Z_DVAL(lzval) = lval; + Z_LVAL(lzval) = lval; goto long_case; } From f10baf14eda4a6fd0e4c8a24d008975184e31207 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 00:59:05 +0100 Subject: [PATCH 38/55] Payload of HOPLIMIT/TCLASS are 8-bit --- ext/sockets/conversions.c | 30 ++++++++++++++++++++++++++++-- ext/sockets/conversions.h | 4 ++-- ext/sockets/sendrecvmsg.c | 10 ++++++---- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 9cbc6e5f88e..ef1f884210a 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -317,7 +317,7 @@ double_case: return ret; } -void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +static void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) { long lval; int ival; @@ -355,6 +355,25 @@ static void from_zval_write_uint32(const zval *arr_value, char *field, ser_conte ival = (uint32_t)lval; memcpy(field, &ival, sizeof(ival)); } +void from_zval_write_uint8(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint8_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > 0xFF) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 8-bit integer"); + return; + } + + ival = (uint8_t)lval; + memcpy(field, &ival, sizeof(ival)); +} static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) { long lval; @@ -441,7 +460,7 @@ static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_contex memcpy(field, &ival, sizeof(ival)); } -void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) { int ival; memcpy(&ival, data, sizeof(ival)); @@ -455,6 +474,13 @@ static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) ZVAL_LONG(zv, (long)ival); } +void to_zval_read_uint8(const char *data, zval *zv, res_context *ctx) +{ + uint8_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) { uint16_t ival; diff --git a/ext/sockets/conversions.h b/ext/sockets/conversions.h index 70f31ba676e..79ca4ab76e2 100644 --- a/ext/sockets/conversions.h +++ b/ext/sockets/conversions.h @@ -37,8 +37,8 @@ void err_msg_dispose(struct err_s *err TSRMLS_DC); void allocations_dispose(zend_llist **allocations); /* CONVERSION FUNCTIONS */ -void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx); -void to_zval_read_int(const char *data, zval *zv, res_context *ctx); +void from_zval_write_uint8(const zval *arr_value, char *field, ser_context *ctx); +void to_zval_read_uint8(const char *data, zval *zv, res_context *ctx); #ifdef IPV6_PKTINFO void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx); diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index b83b3ae4823..f325b0378a9 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -73,12 +73,14 @@ static void init_ancillary_registry(void) #endif #ifdef IPV6_HOPLIMIT - PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, - to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_uint8, + to_zval_read_uint8, IPPROTO_IPV6, IPV6_HOPLIMIT); #endif - PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, - to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS); +#ifdef IPV6_TCLASS + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_uint8, + to_zval_read_uint8, IPPROTO_IPV6, IPV6_TCLASS); +#endif #ifdef SO_PASSCRED PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, From 5c0a8b1a2a34ec504091e4e105e1c3b79d9fff89 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 15:25:55 +0100 Subject: [PATCH 39/55] Ensure memory is initialized --- ext/sockets/conversions.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index ef1f884210a..d0d0c4b7986 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -858,6 +858,7 @@ static void from_zval_write_control(const zval *arr, if (space_left < req_space) { *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); *control_len += 2 * req_space; + memset(*control_buf, '\0', *control_len - *offset); memcpy(&alloc->data, *control_buf, sizeof *control_buf); } From 95f8d34f9c0980924098ce9554e899e461ce7cec Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 15:26:10 +0100 Subject: [PATCH 40/55] Revert "Payload of HOPLIMIT/TCLASS are 8-bit" This reverts commit 61a5ec7381ba5388a52926779fe3f58af0caea83. I checked Linux and OpenBSD and both use integers to write the IPV6_TCLASS messages and they don't force any endianness. This is despite RFC 3542 explicitly saying the first byte of cmsg_data will have the result. In any case, it doesn't make any difference in little-endian archs. --- ext/sockets/conversions.c | 30 ++---------------------------- ext/sockets/conversions.h | 4 ++-- ext/sockets/sendrecvmsg.c | 10 ++++------ 3 files changed, 8 insertions(+), 36 deletions(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index d0d0c4b7986..fa6d9494867 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -317,7 +317,7 @@ double_case: return ret; } -static void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) { long lval; int ival; @@ -355,25 +355,6 @@ static void from_zval_write_uint32(const zval *arr_value, char *field, ser_conte ival = (uint32_t)lval; memcpy(field, &ival, sizeof(ival)); } -void from_zval_write_uint8(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - uint8_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || lval > 0xFF) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for an unsigned 8-bit integer"); - return; - } - - ival = (uint8_t)lval; - memcpy(field, &ival, sizeof(ival)); -} static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) { long lval; @@ -460,7 +441,7 @@ static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_contex memcpy(field, &ival, sizeof(ival)); } -static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +void to_zval_read_int(const char *data, zval *zv, res_context *ctx) { int ival; memcpy(&ival, data, sizeof(ival)); @@ -474,13 +455,6 @@ static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) ZVAL_LONG(zv, (long)ival); } -void to_zval_read_uint8(const char *data, zval *zv, res_context *ctx) -{ - uint8_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) { uint16_t ival; diff --git a/ext/sockets/conversions.h b/ext/sockets/conversions.h index 79ca4ab76e2..70f31ba676e 100644 --- a/ext/sockets/conversions.h +++ b/ext/sockets/conversions.h @@ -37,8 +37,8 @@ void err_msg_dispose(struct err_s *err TSRMLS_DC); void allocations_dispose(zend_llist **allocations); /* CONVERSION FUNCTIONS */ -void from_zval_write_uint8(const zval *arr_value, char *field, ser_context *ctx); -void to_zval_read_uint8(const char *data, zval *zv, res_context *ctx); +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx); +void to_zval_read_int(const char *data, zval *zv, res_context *ctx); #ifdef IPV6_PKTINFO void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx); diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index f325b0378a9..b83b3ae4823 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -73,14 +73,12 @@ static void init_ancillary_registry(void) #endif #ifdef IPV6_HOPLIMIT - PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_uint8, - to_zval_read_uint8, IPPROTO_IPV6, IPV6_HOPLIMIT); + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, + to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); #endif -#ifdef IPV6_TCLASS - PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_uint8, - to_zval_read_uint8, IPPROTO_IPV6, IPV6_TCLASS); -#endif + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, + to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS); #ifdef SO_PASSCRED PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, From 8561680533c7fd6b66497ed10246fe9e57e9d351 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Wed, 30 Jan 2013 21:40:45 +0100 Subject: [PATCH 41/55] Remove some pre-vista code --- win32/inet.c | 79 ---------------------------------------------------- win32/inet.h | 11 ++------ 2 files changed, 2 insertions(+), 88 deletions(-) diff --git a/win32/inet.c b/win32/inet.c index d424c8a5468..686cf1265c5 100644 --- a/win32/inet.c +++ b/win32/inet.c @@ -1,83 +1,4 @@ -#include "config.w32.h" -#include "php.h" -#include -#include -#include - #include "inet.h" -#if (_WIN32_WINNT < 0x0600) /* Vista/2k8 have these functions */ - - -PHPAPI int inet_pton(int af, const char* src, void* dst) -{ - int address_length; - struct sockaddr_storage sa; - struct sockaddr_in *sin = (struct sockaddr_in *)&sa; - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa; - - switch (af) { - case AF_INET: - address_length = sizeof (struct sockaddr_in); - break; - - case AF_INET6: - address_length = sizeof (struct sockaddr_in6); - break; - - default: - return -1; - } - - if (WSAStringToAddress ((LPTSTR) src, af, NULL, (LPSOCKADDR) &sa, &address_length) == 0) { - switch (af) { - case AF_INET: - memcpy (dst, &sin->sin_addr, sizeof (struct in_addr)); - break; - - case AF_INET6: - memcpy (dst, &sin6->sin6_addr, sizeof (struct in6_addr)); - break; - } - return 1; - } - - return 0; -} - -PHPAPI const char* inet_ntop(int af, const void* src, char* dst, size_t size) -{ - int address_length; - DWORD string_length = size; - struct sockaddr_storage sa; - struct sockaddr_in *sin = (struct sockaddr_in *)&sa; - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa; - - memset (&sa, 0, sizeof (sa)); - switch (af) { - case AF_INET: - address_length = sizeof (struct sockaddr_in); - sin->sin_family = af; - memcpy (&sin->sin_addr, src, sizeof (struct in_addr)); - break; - - case AF_INET6: - address_length = sizeof (struct sockaddr_in6); - sin6->sin6_family = af; - memcpy (&sin6->sin6_addr, src, sizeof (struct in6_addr)); - break; - - default: - return NULL; - } - - if (WSAAddressToString ((LPSOCKADDR) &sa, address_length, NULL, dst, &string_length) == 0) { - return dst; - } - - return NULL; -} - -#endif int inet_aton(const char *cp, struct in_addr *inp) { inp->s_addr = inet_addr(cp); diff --git a/win32/inet.h b/win32/inet.h index 623d114dfd5..d71723759fa 100644 --- a/win32/inet.h +++ b/win32/inet.h @@ -1,11 +1,4 @@ -#if _MSC_VER >= 1500 -# include -#endif -#include - -#if (_WIN32_WINNT <= 0x502) -PHPAPI int inet_pton(int af, const char* src, void* dst); -PHPAPI const char* inet_ntop(int af, const void* src, char* dst, size_t size); -#endif +#include +#include PHPAPI int inet_aton(const char *cp, struct in_addr *inp); From 7066cc726713f0eaf79db9eac0da90bc6d8740b4 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 14:01:31 +0100 Subject: [PATCH 42/55] send/recvmsg() support for Windows --- ext/sockets/config.w32 | 2 +- ext/sockets/conversions.c | 69 ++++++++--- ext/sockets/conversions.h | 10 +- ext/sockets/php_sockets.h | 4 +- ext/sockets/sendrecvmsg.c | 54 ++++++++- ext/sockets/sockaddr_conv.c | 2 +- ext/sockets/sockaddr_conv.h | 10 +- ext/sockets/sockets.c | 30 ++--- .../socket_sendrecvmsg_multi_msg-win32.phpt | 110 ++++++++++++++++++ .../tests/socket_sendrecvmsg_multi_msg.phpt | 19 +-- .../tests/socket_set_option_in6_pktinfo.phpt | 3 +- ext/sockets/windows_common.h | 41 +++++++ 12 files changed, 300 insertions(+), 54 deletions(-) create mode 100644 ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt create mode 100644 ext/sockets/windows_common.h diff --git a/ext/sockets/config.w32 b/ext/sockets/config.w32 index 9c234db8f8d..aeaa8ed425e 100644 --- a/ext/sockets/config.w32 +++ b/ext/sockets/config.w32 @@ -7,7 +7,7 @@ if (PHP_SOCKETS != "no") { if (CHECK_LIB("ws2_32.lib", "sockets", PHP_SOCKETS) && CHECK_LIB("Iphlpapi.lib", "sockets", PHP_SOCKETS) && CHECK_HEADER_ADD_INCLUDE("winsock.h", "CFLAGS_SOCKETS")) { - EXTENSION('sockets', 'sockets.c multicast.c'); + EXTENSION('sockets', 'sockets.c multicast.c conversions.c sockaddr_conv.c sendrecvmsg.c'); AC_DEFINE('HAVE_SOCKETS', 1); PHP_INSTALL_HEADERS("ext/sockets", "php_sockets.h"); } else { diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index fa6d9494867..6c79166806b 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -1,24 +1,67 @@ -#include "conversions.h" #include "sockaddr_conv.h" #include "conversions.h" #include "sendrecvmsg.h" /* for ancillary registry */ +#include "windows_common.h" #include #include -#include -#include -#include -#include -#include - -#include -#include +#ifndef PHP_WIN32 +# include +# include +# include +# include +# include +# include +# include +#else +# include +#endif #include #include #include +#ifdef PHP_WIN32 +typedef unsigned short sa_family_t; +# define msghdr _WSAMSG +/* +struct _WSAMSG { + LPSOCKADDR name; //void *msg_name + INT namelen; //socklen_t msg_namelen + LPWSABUF lpBuffers; //struct iovec *msg_iov + ULONG dwBufferCount; //size_t msg_iovlen + WSABUF Control; //void *msg_control, size_t msg_controllen + DWORD dwFlags; //int msg_flags +} +struct __WSABUF { + u_long len; //size_t iov_len (2nd member) + char FAR *buf; //void *iov_base (1st member) +} +struct _WSACMSGHDR { + UINT cmsg_len; //socklen_t cmsg_len + INT cmsg_level; //int cmsg_level + INT cmsg_type; //int cmsg_type; + followed by UCHAR cmsg_data[] +} +*/ +# define msg_name name +# define msg_namelen namelen +# define msg_iov lpBuffers +# define msg_iovlen dwBufferCount +# define msg_control Control.buf +# define msg_controllen Control.len +# define msg_flags dwFlags +# define iov_base buf +# define iov_len len + +# define cmsghdr _WSACMSGHDR +# ifdef CMSG_DATA +# undef CMSG_DATA +# endif +# define CMSG_DATA WSA_CMSG_DATA +#endif + #define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) #define DEFAULT_BUFF_SIZE 8192 @@ -132,7 +175,7 @@ static void do_from_to_zval_err(struct err_s *err, efree(user_msg); smart_str_free_ex(&path, 0); } -__attribute__ ((format (printf, 2, 3))) +ZEND_ATTRIBUTE_FORMAT(printf, 2 ,3) static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) { va_list ap; @@ -141,7 +184,7 @@ static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); va_end(ap); } -__attribute__ ((format (printf, 2, 3))) +ZEND_ATTRIBUTE_FORMAT(printf, 2 ,3) static void do_to_zval_err(res_context *ctx, const char *fmt, ...) { va_list ap; @@ -958,7 +1001,7 @@ static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_conte for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL && !ctx->err.has_error; - cmsg = CMSG_NXTHDR(msg,cmsg)) { + cmsg = CMSG_NXTHDR(msg, cmsg)) { zval *elem; ALLOC_INIT_ZVAL(elem); @@ -1149,7 +1192,7 @@ static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { zval *elem; - size_t len = MIN(msghdr->msg_iov[i].iov_len, bytes_left); + size_t len = MIN(msghdr->msg_iov[i].iov_len, (size_t)bytes_left); char *buf = safe_emalloc(1, len, 1); MAKE_STD_ZVAL(elem); diff --git a/ext/sockets/conversions.h b/ext/sockets/conversions.h index 70f31ba676e..7d515246a0b 100644 --- a/ext/sockets/conversions.h +++ b/ext/sockets/conversions.h @@ -2,8 +2,14 @@ #define PHP_SOCK_CONVERSIONS_H 1 #include -#include -#include + +#ifndef PHP_WIN32 +# include +# include +#else +# include +#endif + #include "php_sockets.h" /* TYPE DEFINITIONS */ diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 78da0c29e68..a5699c75149 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -26,11 +26,13 @@ #if HAVE_SOCKETS +#include + extern zend_module_entry sockets_module_entry; #define phpext_sockets_ptr &sockets_module_entry #ifdef PHP_WIN32 -#include +#include #else #if HAVE_SYS_SOCKET_H #include diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index b83b3ae4823..f75fdcdedea 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -30,6 +30,44 @@ #define DEFAULT_BUFF_SIZE 8192 #define MAX_ARRAY_KEY_SIZE 128 +#ifdef PHP_WIN32 +#include "windows_common.h" +#include +#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) \ do { \ if ((l) < INT_MIN && (l) > INT_MAX) { \ @@ -158,9 +196,7 @@ PHP_FUNCTION(socket_sendmsg) RETURN_LONG((long)res); } else { - SOCKETS_G(last_error) = errno; - php_error_docref(NULL TSRMLS_CC, E_WARNING, "error in sendmsg [%d]: %s", - errno, sockets_strerror(errno TSRMLS_CC)); + PHP_SOCKET_ERROR(php_sock, "error in sendmsg", errno); RETURN_FALSE; } } @@ -285,6 +321,18 @@ int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, switch (optname) { #ifdef IPV6_PKTINFO case IPV6_PKTINFO: +#ifdef PHP_WIN32 + if (Z_TYPE_PP(arg4) == IS_ARRAY) { + php_error_docref0(NULL TSRMLS_CC, 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) { diff --git a/ext/sockets/sockaddr_conv.c b/ext/sockets/sockaddr_conv.c index 19c61740d0c..a40b6b4936c 100644 --- a/ext/sockets/sockaddr_conv.c +++ b/ext/sockets/sockaddr_conv.c @@ -3,7 +3,7 @@ #include "php_sockets.h" #ifdef PHP_WIN32 -#include +#include "windows_common.h" #else #include #include diff --git a/ext/sockets/sockaddr_conv.h b/ext/sockets/sockaddr_conv.h index 444d749fe73..665c73913f1 100644 --- a/ext/sockets/sockaddr_conv.h +++ b/ext/sockets/sockaddr_conv.h @@ -1,8 +1,16 @@ #ifndef PHP_SOCKADR_CONV_H #define PHP_SOCKADR_CONV_H +#define HAVE_SOCKETS 1 #include -#include "php_sockets.h" +#include "php_sockets.h" /* php_socket */ + +#ifndef PHP_WIN32 +# include +#else +# include +#endif + /* * Convert an IPv6 literal or a hostname info a sockaddr_in6. diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 2aeb4b0fd33..5636cd3cdc9 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -35,32 +35,12 @@ #include "ext/standard/info.h" #include "php_ini.h" #ifdef PHP_WIN32 -# include "win32/inet.h" -# include +# include "windows_common.h" +# include # include # include # include "php_sockets.h" -# include "win32/sockets.h" -# define IS_INVALID_SOCKET(a) (a->bsd_socket == INVALID_SOCKET) -# ifdef EPROTONOSUPPORT -# undef EPROTONOSUPPORT -# endif -# ifdef ECONNRESET -# undef ECONNRESET -# endif -# define EPROTONOSUPPORT WSAEPROTONOSUPPORT -# define ECONNRESET WSAECONNRESET -# ifdef errno -# undef errno -# endif -# define errno WSAGetLastError() -# define h_errno WSAGetLastError() -# define set_errno(a) WSASetLastError(a) -# define close(a) closesocket(a) -# include -# if _WIN32_WINNT >= 0x0600 -# define HAVE_IF_NAMETOINDEX 1 -# endif +# include #else # include # include @@ -650,8 +630,12 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("MSG_TRUNC", MSG_TRUNC, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_PEEK", MSG_PEEK, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_DONTROUTE", MSG_DONTROUTE, CONST_CS | CONST_PERSISTENT); +#ifdef MSG_EOR REGISTER_LONG_CONSTANT("MSG_EOR", MSG_EOR, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef MSG_EOF REGISTER_LONG_CONSTANT("MSG_EOF", MSG_EOF, CONST_CS | CONST_PERSISTENT); +#endif #ifdef MSG_CONFIRM REGISTER_LONG_CONSTANT("MSG_CONFIRM", MSG_CONFIRM, CONST_CS | CONST_PERSISTENT); diff --git a/ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt b/ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt new file mode 100644 index 00000000000..3aba012726f --- /dev/null +++ b/ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt @@ -0,0 +1,110 @@ +--TEST-- +sendmsg()/recvmsg(): test ability to receive multiple messages (WIN32) +--SKIPIF-- + [ "addr" => "::1", "port" => 3000], + "iov" => ["test ", "thing", "\n"], + "control" => [[ + "level" => IPPROTO_IPV6, + "type" => IPV6_PKTINFO, + "data" => [ + 'addr' => '::1', + 'ifindex' => 1 /* we're assuming loopback is 1. Is this a safe assumption? */ + ], + ]] +], 0); +var_dump($r); +checktimeout($s, 500); + +$data = [ + "name" => ["family" => AF_INET6, "addr" => "::1"], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(IPPROTO_IPV6, IPV6_PKTINFO) + + socket_cmsg_space(IPPROTO_IPV6, IPV6_TCLASS), +]; +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); + +--EXPECTF-- +creating send socket +resource(5) of type (Socket) +bool(true) +creating receive socket +resource(6) of type (Socket) +bool(true) +int(11) +Array +( + [name] => Array + ( + [family] => %d + [addr] => ::1 + [port] => 7001 + [flowinfo] => 0 + [scope_id] => 0 + ) + + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [addr] => ::1 + [ifindex] => %d + ) + + ) + + [1] => Array + ( + [level] => %d + [type] => %d + [data] => 0 + ) + + ) + + [iov] => Array + ( + [0] => test thing + + ) + + [flags] => 0 +) diff --git a/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt index 055e263f724..212f7e186f2 100644 --- a/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt +++ b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt @@ -2,12 +2,15 @@ sendmsg()/recvmsg(): test ability to receive multiple messages --SKIPIF-- [[ "level" => IPPROTO_IPV6, "type" => IPV6_TCLASS, - "data" => 42, + "data" => 40, ]] ], 0); var_dump($r); @@ -88,7 +91,7 @@ Array ( [level] => %d [type] => %d - [data] => 42 + [data] => 40 ) ) diff --git a/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt index 53320cad0c3..27b6ae59c5d 100644 --- a/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt +++ b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt @@ -8,7 +8,8 @@ die('skip sockets extension not available.'); if (!defined('IPPROTO_IPV6')) { die('skip IPv6 not available.'); } - +if (substr(PHP_OS, 0, 3) == 'WIN') + die('skip Not for Windows!'); --FILE-- +#include /* conflicting definition of CMSG_DATA */ + +#define HAVE_IF_NAMETOINDEX 1 + +#define IS_INVALID_SOCKET(a) (a->bsd_socket == INVALID_SOCKET) +#ifdef EPROTONOSUPPORT +# undef EPROTONOSUPPORT +#endif +#ifdef ECONNRESET +# undef ECONNRESET +#endif +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#define ECONNRESET WSAECONNRESET +#ifdef errno +# undef errno +#endif +#define errno WSAGetLastError() +#define h_errno WSAGetLastError() +#define set_errno(a) WSASetLastError(a) +#define close(a) closesocket(a) + +#endif \ No newline at end of file From 608254fa5757ee07999d5c4bec660b8c29780938 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 16:24:02 +0100 Subject: [PATCH 43/55] Fix non-Windows build --- ext/sockets/conversions.c | 4 +++- main/php_network.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 6c79166806b..e3ff271f846 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -1,7 +1,9 @@ #include "sockaddr_conv.h" #include "conversions.h" #include "sendrecvmsg.h" /* for ancillary registry */ -#include "windows_common.h" +#ifdef PHP_WIN32 +# include "windows_common.h" +#endif #include #include diff --git a/main/php_network.h b/main/php_network.h index 8ffb51ca407..c1535ee040b 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -21,6 +21,8 @@ #ifndef _PHP_NETWORK_H #define _PHP_NETWORK_H +#include + #ifdef PHP_WIN32 # include "win32/inet.h" #else From e2fc17c833c5122327438c82fc0dc4b689268f59 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 16:34:46 +0100 Subject: [PATCH 44/55] Fix multicast.c not defining errno on Windows Small cleanups in includes as well. --- ext/sockets/multicast.c | 14 +------------- ext/sockets/sockaddr_conv.h | 1 - ext/sockets/windows_common.h | 2 ++ 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/ext/sockets/multicast.c b/ext/sockets/multicast.c index 5d29c9c5e26..c6de3198e46 100644 --- a/ext/sockets/multicast.c +++ b/ext/sockets/multicast.c @@ -28,19 +28,7 @@ #include "php_network.h" #ifdef PHP_WIN32 -# include "win32/inet.h" -# include -# include -# include -# include -# include "php_sockets.h" -# include "win32/sockets.h" -# define NTDDI_XP NTDDI_WINXP /* bug in SDK */ -# include -# undef NTDDI_XP -# if _WIN32_WINNT >= 0x0600 -# define HAVE_IF_NAMETOINDEX 1 -# endif +# include "windows_common.h" #else #include #include diff --git a/ext/sockets/sockaddr_conv.h b/ext/sockets/sockaddr_conv.h index 665c73913f1..8e51edac8f8 100644 --- a/ext/sockets/sockaddr_conv.h +++ b/ext/sockets/sockaddr_conv.h @@ -1,7 +1,6 @@ #ifndef PHP_SOCKADR_CONV_H #define PHP_SOCKADR_CONV_H -#define HAVE_SOCKETS 1 #include #include "php_sockets.h" /* php_socket */ diff --git a/ext/sockets/windows_common.h b/ext/sockets/windows_common.h index 1dc966ac001..c72c6987e68 100644 --- a/ext/sockets/windows_common.h +++ b/ext/sockets/windows_common.h @@ -17,7 +17,9 @@ #define WINDOWS_COMMON_H #include +#define NTDDI_XP NTDDI_WINXP /* bug in SDK */ #include /* conflicting definition of CMSG_DATA */ +#undef NTDDI_XP #define HAVE_IF_NAMETOINDEX 1 From 0110662ae9e89d21c119b3287118e82fd435f779 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sat, 2 Feb 2013 18:32:38 +0100 Subject: [PATCH 45/55] Move macro back to .c file Because it depends on a static function on that .c file. --- ext/sockets/php_sockets.h | 8 -------- ext/sockets/sockets.c | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 3762e026aa5..fabc9c4c3e7 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -64,14 +64,6 @@ PHP_SOCKETS_API int php_sockets_le_socket(void); #define php_sockets_le_socket_name "Socket" -#define PHP_SOCKET_ERROR(socket, msg, errn) \ - do { \ - int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ - (socket)->error = _err; \ - SOCKETS_G(last_error) = _err; \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ - } while (0) - ZEND_BEGIN_MODULE_GLOBALS(sockets) int last_error; char *strerror_buf; diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index c6c5477967c..06bd0ec6b4f 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -114,6 +114,14 @@ static PHP_GINIT_FUNCTION(sockets); static char *php_strerror(int error TSRMLS_DC); +#define PHP_SOCKET_ERROR(socket, msg, errn) \ + do { \ + int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ + (socket)->error = _err; \ + SOCKETS_G(last_error) = _err; \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + } while (0) + #define PHP_NORMAL_READ 0x0001 #define PHP_BINARY_READ 0x0002 From 03b1da5d7ada4a80a91d9953c388fa05f294d711 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sat, 2 Feb 2013 18:39:47 +0100 Subject: [PATCH 46/55] php_strerror in ext/sockets renamed in 5.5 --- ext/sockets/php_sockets.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 5082a9bc1e8..bad83b34ccc 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -72,7 +72,7 @@ PHP_SOCKETS_API int php_sockets_le_socket(void); (socket)->error = _err; \ SOCKETS_G(last_error) = _err; \ if (_err != EAGAIN && _err != EWOULDBLOCK && _err != EINPROGRESS) { \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, sockets_strerror(_err TSRMLS_CC)); \ } \ } while (0) From af1b90d62ba69953de2065864d2a1284314323ba Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sun, 3 Feb 2013 01:22:44 +0100 Subject: [PATCH 47/55] Fix bug and hopefully build on WinSDK 6.1 There build was failing on rmtools on the sockets extension for two reasons: 1. IPV6_TCLASS and IPV6_RECVTCLASS not being defined. These are probably recent additions to SDK. Windows 7 doesn't event seem to have complete support for IPV6_TCLASS, not accepting in WSASendMsg(). The parts that needed this constant were not guarded by #ifdefs. They are now. 2. The constants EWOULDBLOCK and EINPROGRESS not being defined. These were only defined in php_network.h, outside of the extension, and not all source files included this header. Nevertheless, a macro defined in php_sockets.h needed these constants. When this macro was used in files that did not include php_network.h, the compilation would fail. Surprisingly, the build did not fail when using the 7.1 Windows SDK (more likely, the CRT headers used in VC10), as somehow errno.h was being included through some other standard header. This would make the constant EWOULDBLOCK defined; however, it would be defined to the wrong value. In the winsock context, WSAEWOULDBLOCK should be used instead. Because we have difficulty using Windows-only constants in the code, we (re)define EWOULDBLOCK to WSAEWOULDBLOCK. This has the obvious disavantage we may miss problems like this again in the future. --- ext/sockets/php_sockets.h | 3 ++ ext/sockets/sendrecvmsg.c | 6 ++- ext/sockets/windows_common.h | 95 ++++++++++++++++++++++++++++++++---- 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index bad83b34ccc..dd2b9933f6e 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -27,6 +27,9 @@ #if HAVE_SOCKETS #include +#ifdef PHP_WIN32 +# include "windows_common.h" +#endif extern zend_module_entry sockets_module_entry; #define phpext_sockets_ptr &sockets_module_entry diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index f75fdcdedea..50b43ec38a5 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -115,8 +115,10 @@ static void init_ancillary_registry(void) to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); #endif +#ifdef IPV6_TCLASS 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, @@ -416,14 +418,16 @@ void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS) REGISTER_LONG_CONSTANT("IPV6_RECVHOPOPTS", IPV6_RECVHOPOPTS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_RECVDSTOPTS", IPV6_RECVDSTOPTS, CONST_CS | CONST_PERSISTENT); */ +#ifdef IPV6_RECVTCLASS 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); */ - REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, CONST_CS | CONST_PERSISTENT); #ifdef SCM_RIGHTS REGISTER_LONG_CONSTANT("SCM_RIGHTS", SCM_RIGHTS, CONST_CS | CONST_PERSISTENT); diff --git a/ext/sockets/windows_common.h b/ext/sockets/windows_common.h index c72c6987e68..3a9cb591292 100644 --- a/ext/sockets/windows_common.h +++ b/ext/sockets/windows_common.h @@ -24,14 +24,7 @@ #define HAVE_IF_NAMETOINDEX 1 #define IS_INVALID_SOCKET(a) (a->bsd_socket == INVALID_SOCKET) -#ifdef EPROTONOSUPPORT -# undef EPROTONOSUPPORT -#endif -#ifdef ECONNRESET -# undef ECONNRESET -#endif -#define EPROTONOSUPPORT WSAEPROTONOSUPPORT -#define ECONNRESET WSAECONNRESET + #ifdef errno # undef errno #endif @@ -40,4 +33,88 @@ #define set_errno(a) WSASetLastError(a) #define close(a) closesocket(a) -#endif \ No newline at end of file +#ifdef ENETUNREACH /* errno.h probably included */ +# undef EWOULDBLOCK +# undef EINPROGRESS +# undef EALREADY +# undef ENOTSOCK +# undef EDESTADDRREQ +# undef EMSGSIZE +# undef EPROTOTYPE +# undef ENOPROTOOPT +# undef EPROTONOSUPPORT +# undef ESOCKTNOSUPPORT +# undef EOPNOTSUPP +# undef EPFNOSUPPORT +# undef EAFNOSUPPORT +# undef EADDRINUSE +# undef EADDRNOTAVAIL +# undef ENETDOWN +# undef ENETUNREACH +# undef ENETRESET +# undef ECONNABORTED +# undef ECONNRESET +# undef ENOBUFS +# undef EISCONN +# undef ENOTCONN +# undef ESHUTDOWN +# undef ETOOMANYREFS +# undef ETIMEDOUT +# undef ECONNREFUSED +# undef ELOOP +# undef ENAMETOOLONG +# undef EHOSTDOWN +# undef EHOSTUNREACH +# undef ENOTEMPTY +# undef EPROCLIM +# undef EUSERS +# undef EDQUOT +# undef ESTALE +# undef EREMOTE + +# undef EAGAIN +#endif + +/* section disabled in WinSock2.h */ +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EINPROGRESS WSAEINPROGRESS +#define EALREADY WSAEALREADY +#define ENOTSOCK WSAENOTSOCK +#define EDESTADDRREQ WSAEDESTADDRREQ +#define EMSGSIZE WSAEMSGSIZE +#define EPROTOTYPE WSAEPROTOTYPE +#define ENOPROTOOPT WSAENOPROTOOPT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#define EOPNOTSUPP WSAEOPNOTSUPP +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define EADDRINUSE WSAEADDRINUSE +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#define ENETDOWN WSAENETDOWN +#define ENETUNREACH WSAENETUNREACH +#define ENETRESET WSAENETRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNRESET WSAECONNRESET +#define ENOBUFS WSAENOBUFS +#define EISCONN WSAEISCONN +#define ENOTCONN WSAENOTCONN +#define ESHUTDOWN WSAESHUTDOWN +#define ETOOMANYREFS WSAETOOMANYREFS +#define ETIMEDOUT WSAETIMEDOUT +#define ECONNREFUSED WSAECONNREFUSED +#define ELOOP WSAELOOP +#define ENAMETOOLONG WSAENAMETOOLONG +#define EHOSTDOWN WSAEHOSTDOWN +#define EHOSTUNREACH WSAEHOSTUNREACH +#define ENOTEMPTY WSAENOTEMPTY +#define EPROCLIM WSAEPROCLIM +#define EUSERS WSAEUSERS +#define EDQUOT WSAEDQUOT +#define ESTALE WSAESTALE +#define EREMOTE WSAEREMOTE + +/* and an extra one */ +#define EAGAIN WSAEWOULDBLOCK + +#endif From a000920dfb099fac3b58ea344d33f4b6c3ee51a1 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sun, 3 Feb 2013 01:59:35 +0100 Subject: [PATCH 48/55] NEWS/UPGRADING for changes in sockets, intl --- NEWS | 8 ++++++++ UPGRADING | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/NEWS b/NEWS index e79cffed237..11ae31cf44f 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,14 @@ PHP NEWS . Implemented FR #46439 - added CURLFile for safer file uploads. (Stas) +- Intl: + . Cherry-picked UConverter wrapper, which had accidentaly been committed only + to master. + +- Sockets: + . Added recvmsg() and sendmsg() wrappers. (Gustavo) + See https://wiki.php.net/rfc/sendrecvmsg + 24 Jan 2013, PHP 5.5.0 Alpha 4 - Core: diff --git a/UPGRADING b/UPGRADING index 22ad969157c..790803e3957 100755 --- a/UPGRADING +++ b/UPGRADING @@ -157,6 +157,8 @@ PHP 5.5 UPGRADE NOTES Expires headers. (see https://wiki.php.net/rfc/cookie_max-age) - curl_setopt now accepts new option CURLOPT_SAFE_UPLOAD and CURLFile object for safer file uploads (see https://wiki.php.net/rfc/curl-file-upload) +- Functions in the socket extension now do not emit warnings when the errno is + EAGAIN, EWOULDBLOCK or EINPROGRESS. ======================================== 5. New Functions @@ -257,6 +259,11 @@ PHP 5.5 UPGRADE NOTES - IntlDateFormatter::getTimeZone() - IntlDateFormatter::setTimeZone() +- Sockets: + - socket_sendmsg() + - socket_recvmsg() + - socket_cmsg_space() + - SPL: - SplFixedArray::__wakeup() @@ -271,6 +278,7 @@ PHP 5.5 UPGRADE NOTES - IntlBreakIterator - IntlRuleBasedBreakIterator - IntlCodePointBreakIterator + - UConverter - cURL: - CURLFile From 8771c265a43dac2e44aa4ae7af66a6534b0c70ae Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sun, 3 Feb 2013 02:00:48 +0100 Subject: [PATCH 49/55] Fix test on Windows. Windows complains of invalid parameters because the socket is not bound. The test expected the error to be EAGAIN/EWOULDBLOCK. Moved the call down, after the socket is bound. --- ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt index 00d69a855fb..5aeaa0824fa 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt @@ -14,12 +14,14 @@ if (!extension_loaded('sockets')) { if (!socket_set_nonblock($socket)) { die('Unable to set nonblocking mode for socket'); } - var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); //false (EAGAIN - no warning) + $address = '127.0.0.1'; socket_sendto($socket, '', 1, 0, $address); // cause warning if (!socket_bind($socket, $address, 1223)) { die("Unable to bind to $address:1223"); } + + var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); //false (EAGAIN - no warning) $msg = "Ping!"; $len = strlen($msg); @@ -44,9 +46,9 @@ if (!extension_loaded('sockets')) { socket_close($socket); --EXPECTF-- -bool(false) Warning: Wrong parameter count for socket_sendto() in %s on line %d +bool(false) Warning: socket_recvfrom() expects at least 5 parameters, 4 given in %s on line %d From 2f334438836f37b82d739b20a4d0f875278e4766 Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Mon, 24 Dec 2012 14:14:24 +0100 Subject: [PATCH 50/55] ext/filter support for validating MAC addresses. --- ext/filter/filter.c | 2 ++ ext/filter/filter_private.h | 3 ++- ext/filter/logical_filters.c | 48 ++++++++++++++++++++++++++++++++++++ ext/filter/php_filter.h | 1 + ext/filter/tests/008.phpt | 26 ++++++++++--------- ext/filter/tests/033.phpt | 39 +++++++++++++++-------------- ext/filter/tests/033_run.inc | 6 +++-- ext/filter/tests/055.phpt | 34 +++++++++++++++++++++++++ 8 files changed, 125 insertions(+), 34 deletions(-) create mode 100644 ext/filter/tests/055.phpt diff --git a/ext/filter/filter.c b/ext/filter/filter.c index 2aa8dd57d9c..da951feb042 100644 --- a/ext/filter/filter.c +++ b/ext/filter/filter.c @@ -47,6 +47,7 @@ static const filter_list_entry filter_list[] = { { "validate_url", FILTER_VALIDATE_URL, php_filter_validate_url }, { "validate_email", FILTER_VALIDATE_EMAIL, php_filter_validate_email }, { "validate_ip", FILTER_VALIDATE_IP, php_filter_validate_ip }, + { "validate_mac", FILTER_VALIDATE_MAC, php_filter_validate_mac }, { "string", FILTER_SANITIZE_STRING, php_filter_string }, { "stripped", FILTER_SANITIZE_STRING, php_filter_string }, @@ -233,6 +234,7 @@ PHP_MINIT_FUNCTION(filter) REGISTER_LONG_CONSTANT("FILTER_VALIDATE_URL", FILTER_VALIDATE_URL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_VALIDATE_EMAIL", FILTER_VALIDATE_EMAIL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_VALIDATE_IP", FILTER_VALIDATE_IP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FILTER_VALIDATE_MAC", FILTER_VALIDATE_MAC, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_DEFAULT", FILTER_DEFAULT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_UNSAFE_RAW", FILTER_UNSAFE_RAW, CONST_CS | CONST_PERSISTENT); diff --git a/ext/filter/filter_private.h b/ext/filter/filter_private.h index 9bc53a0e475..65e61dfea7f 100644 --- a/ext/filter/filter_private.h +++ b/ext/filter/filter_private.h @@ -63,7 +63,8 @@ #define FILTER_VALIDATE_URL 0x0111 #define FILTER_VALIDATE_EMAIL 0x0112 #define FILTER_VALIDATE_IP 0x0113 -#define FILTER_VALIDATE_LAST 0x0113 +#define FILTER_VALIDATE_MAC 0x0114 +#define FILTER_VALIDATE_LAST 0x0114 #define FILTER_VALIDATE_ALL 0x0100 diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 58d5870c112..52d948b1889 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -784,6 +784,54 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ } /* }}} */ +void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +{ + char *input = Z_STRVAL_P(value); + int input_len = Z_STRLEN_P(value); + int tokens, length, i, offset; + char separator; + long ret = 0; + + if (14 == input_len) { + /* EUI-64 format: Four hexadecimal digits separated by dots. Less + * commonly used but valid nonetheless. + */ + tokens = 3; + length = 4; + separator = '.'; + } else if (17 == input_len && memchr(input + 2, '-', 1)) { + /* IEEE 802 format: Six hexadecimal digits separated by hyphens. */ + tokens = 6; + length = 2; + separator = '-'; + } else if (17 == input_len && memchr(input + 2, ':', 1)) { + /* IEEE 802 format: Six hexadecimal digits separated by colons. */ + tokens = 6; + length = 2; + separator = ':'; + } else { + RETURN_VALIDATION_FAILED; + } + + /* Essentially what we now have is a set of tokens each consisting of + * a hexadecimal number followed by a separator character. (With the + * exception of the last token which does not have the separator.) + */ + for (i = 0; i < tokens; i++) { + offset = i * (length + 1); + + if (i < tokens - 1 && !memchr(input + offset + length, separator, 1)) { + /* The current token did not end with e.g. a "." */ + RETURN_VALIDATION_FAILED + } + if (php_filter_parse_hex(input + offset, length, &ret) < 0) { + /* The current token is no valid hexadecimal digit */ + RETURN_VALIDATION_FAILED + } + } +} +/* }}} */ + /* * Local variables: * tab-width: 4 diff --git a/ext/filter/php_filter.h b/ext/filter/php_filter.h index cbe1c472009..e31f0f08175 100644 --- a/ext/filter/php_filter.h +++ b/ext/filter/php_filter.h @@ -78,6 +78,7 @@ void php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL); void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL); void php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL); void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL); +void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL); void php_filter_string(PHP_INPUT_FILTER_PARAM_DECL); void php_filter_encoded(PHP_INPUT_FILTER_PARAM_DECL); diff --git a/ext/filter/tests/008.phpt b/ext/filter/tests/008.phpt index 8a4340542f2..a499219ee76 100644 --- a/ext/filter/tests/008.phpt +++ b/ext/filter/tests/008.phpt @@ -11,7 +11,7 @@ var_dump(filter_list(array())); echo "Done\n"; ?> --EXPECTF-- -array(19) { +array(20) { [0]=> string(3) "int" [1]=> @@ -27,28 +27,30 @@ array(19) { [6]=> string(11) "validate_ip" [7]=> - string(6) "string" + string(12) "validate_mac" [8]=> - string(8) "stripped" + string(6) "string" [9]=> - string(7) "encoded" + string(8) "stripped" [10]=> - string(13) "special_chars" + string(7) "encoded" [11]=> - string(18) "full_special_chars" + string(13) "special_chars" [12]=> - string(10) "unsafe_raw" + string(18) "full_special_chars" [13]=> - string(5) "email" + string(10) "unsafe_raw" [14]=> - string(3) "url" + string(5) "email" [15]=> - string(10) "number_int" + string(3) "url" [16]=> - string(12) "number_float" + string(10) "number_int" [17]=> - string(12) "magic_quotes" + string(12) "number_float" [18]=> + string(12) "magic_quotes" + [19]=> string(8) "callback" } diff --git a/ext/filter/tests/033.phpt b/ext/filter/tests/033.phpt index 04daa613339..d76f9ab3b8a 100644 --- a/ext/filter/tests/033.phpt +++ b/ext/filter/tests/033.phpt @@ -10,22 +10,23 @@ default_charset=UTF-8 include dirname(__FILE__) . '/033_run.inc'; ?> --EXPECT-- -int 1 123 -boolean 1 -float 1 123 -validate_regexp O'Henry -validate_url http://a.b.c -validate_email foo@bar.com -validate_ip 1.2.3.4 -string PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc() O'Henry 하퍼 -stripped PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc() O'Henry 하퍼 -encoded PHP 1 foo%40bar.com http%3A%2F%2Fa.b.c 1.2.3.4 123 123abc%3C%3E%28%29 O%27Henry %ED%95%98%ED%8D%BC -special_chars PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O'Henry 하퍼 -full_special_chars PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O'Henry 하퍼 -unsafe_raw PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O'Henry 하퍼 -email PHP 1 foo@bar.com httpa.b.c 1.2.3.4 123 123abc O'Henry -url PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O'Henry -number_int 1 1234 123 123 -number_float 1 1234 123 123 -magic_quotes PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O\'Henry 하퍼 -callback PHP 1 FOO@BAR.COM HTTP://A.B.C 1.2.3.4 123 123ABC<>() O'HENRY 하퍼 +int 1 123 +boolean 1 +float 1 123 +validate_regexp O'Henry +validate_url http://a.b.c +validate_email foo@bar.com +validate_ip 1.2.3.4 +validate_mac aa:bb:cc:dd:ee:ff +string PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc() O'Henry 하퍼 aa:bb:cc:dd:ee:ff +stripped PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc() O'Henry 하퍼 aa:bb:cc:dd:ee:ff +encoded PHP 1 foo%40bar.com http%3A%2F%2Fa.b.c 1.2.3.4 123 123abc%3C%3E%28%29 O%27Henry %ED%95%98%ED%8D%BCaa%3Abb%3Acc%3Add%3Aee%3Aff +special_chars PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O'Henry 하퍼 aa:bb:cc:dd:ee:ff +full_special_chars PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O'Henry 하퍼 aa:bb:cc:dd:ee:ff +unsafe_raw PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O'Henry 하퍼 aa:bb:cc:dd:ee:ff +email PHP 1 foo@bar.com httpa.b.c 1.2.3.4 123 123abc O'Henry aabbccddeeff +url PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O'Henry aa:bb:cc:dd:ee:ff +number_int 1 1234 123 123 +number_float 1 1234 123 123 +magic_quotes PHP 1 foo@bar.com http://a.b.c 1.2.3.4 123 123abc<>() O\'Henry 하퍼 aa:bb:cc:dd:ee:ff +callback PHP 1 FOO@BAR.COM HTTP://A.B.C 1.2.3.4 123 123ABC<>() O'HENRY 하퍼 AA:BB:CC:DD:EE:FF \ No newline at end of file diff --git a/ext/filter/tests/033_run.inc b/ext/filter/tests/033_run.inc index e3b67387ca2..ecb2cf7be16 100644 --- a/ext/filter/tests/033_run.inc +++ b/ext/filter/tests/033_run.inc @@ -16,7 +16,8 @@ $data = array( "123", "123abc<>()", "O'Henry", - "하퍼" + "하퍼", + "aa:bb:cc:dd:ee:ff", ); @@ -35,6 +36,7 @@ foreach(filter_list() as $filter) { printf("%-5s",$result[5]); printf("%-20s",$result[6]); printf("%-15s",$result[7]); - printf("%-10s\n",$result[8]); + printf("%-10s",$result[8]); + printf("%-10s\n",$result[9]); } ?> diff --git a/ext/filter/tests/055.phpt b/ext/filter/tests/055.phpt new file mode 100644 index 00000000000..bf94f3515f1 --- /dev/null +++ b/ext/filter/tests/055.phpt @@ -0,0 +1,34 @@ +--TEST-- +filter_var() and FILTER_VALIDATE_MAC +--SKIPIF-- + +--FILE-- + +--EXPECT-- +string(17) "01-23-45-67-89-ab" +string(17) "01-23-45-67-89-AB" +string(17) "01-23-45-67-89-aB" +string(17) "01:23:45:67:89:ab" +string(17) "01:23:45:67:89:AB" +string(17) "01:23:45:67:89:aB" +bool(false) +bool(false) +string(14) "0123.4567.89ab" +Done From 0661d03ebaab6cf1eff156a4dd203299d0385ae9 Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Fri, 28 Dec 2012 15:20:19 +0100 Subject: [PATCH 51/55] There is no need to use memchr() for comparisons in these places. --- ext/filter/logical_filters.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 52d948b1889..fededcf715b 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -799,12 +799,12 @@ void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ tokens = 3; length = 4; separator = '.'; - } else if (17 == input_len && memchr(input + 2, '-', 1)) { + } else if (17 == input_len && input[2] == '-') { /* IEEE 802 format: Six hexadecimal digits separated by hyphens. */ tokens = 6; length = 2; separator = '-'; - } else if (17 == input_len && memchr(input + 2, ':', 1)) { + } else if (17 == input_len && input[2] == ':') { /* IEEE 802 format: Six hexadecimal digits separated by colons. */ tokens = 6; length = 2; @@ -820,7 +820,7 @@ void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ for (i = 0; i < tokens; i++) { offset = i * (length + 1); - if (i < tokens - 1 && !memchr(input + offset + length, separator, 1)) { + if (i < tokens - 1 && input[offset + length] != separator) { /* The current token did not end with e.g. a "." */ RETURN_VALIDATION_FAILED } From 6186b676000f74af92b979382f605ed104b6a4bc Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Sat, 5 Jan 2013 22:46:14 +0100 Subject: [PATCH 52/55] Add option to specific the expected separator character. --- ext/filter/logical_filters.c | 15 ++++++++++++++- ext/filter/tests/055.phpt | 36 +++++++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index fededcf715b..9b436a779e6 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -788,9 +788,18 @@ void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { char *input = Z_STRVAL_P(value); int input_len = Z_STRLEN_P(value); - int tokens, length, i, offset; + int tokens, length, i, offset, exp_separator_set, exp_separator_len; char separator; + char *exp_separator; long ret = 0; + zval **option_val; + + FETCH_STRING_OPTION(exp_separator, "separator"); + + if (exp_separator_set && exp_separator_len != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Separator must be exactly one character long"); + RETURN_VALIDATION_FAILED; + } if (14 == input_len) { /* EUI-64 format: Four hexadecimal digits separated by dots. Less @@ -813,6 +822,10 @@ void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ RETURN_VALIDATION_FAILED; } + if (exp_separator_set && separator != exp_separator[0]) { + RETURN_VALIDATION_FAILED; + } + /* Essentially what we now have is a set of tokens each consisting of * a hexadecimal number followed by a separator character. (With the * exception of the last token which does not have the separator.) diff --git a/ext/filter/tests/055.phpt b/ext/filter/tests/055.phpt index bf94f3515f1..688dbb2b540 100644 --- a/ext/filter/tests/055.phpt +++ b/ext/filter/tests/055.phpt @@ -5,24 +5,32 @@ filter_var() and FILTER_VALIDATE_MAC --FILE-- array("separator" => "-"))), + array("01-23-45-67-89-ab", array("options" => array("separator" => "."))), + array("01-23-45-67-89-ab", array("options" => array("separator" => ":"))), + array("01-23-45-67-89-AB", null), + array("01-23-45-67-89-aB", null), + array("01:23:45:67:89:ab", null), + array("01:23:45:67:89:AB", null), + array("01:23:45:67:89:aB", null), + array("01:23:45-67:89:aB", null), + array("xx:23:45:67:89:aB", null), + array("0123.4567.89ab", null), + array("01-23-45-67-89-ab", array("options" => array("separator" => "--"))), + array("01-23-45-67-89-ab", array("options" => array("separator" => ""))), ); foreach ($values as $value) { - var_dump(filter_var($value, FILTER_VALIDATE_MAC)); + var_dump(filter_var($value[0], FILTER_VALIDATE_MAC, $value[1])); } echo "Done\n"; ?> ---EXPECT-- +--EXPECTF-- string(17) "01-23-45-67-89-ab" +string(17) "01-23-45-67-89-ab" +bool(false) +bool(false) string(17) "01-23-45-67-89-AB" string(17) "01-23-45-67-89-aB" string(17) "01:23:45:67:89:ab" @@ -31,4 +39,10 @@ string(17) "01:23:45:67:89:aB" bool(false) bool(false) string(14) "0123.4567.89ab" + +Warning: filter_var(): Separator must be exactly one character long in %s055.php on line %d +bool(false) + +Warning: filter_var(): Separator must be exactly one character long in %s055.php on line %d +bool(false) Done From 60b5f6d4632260a90e0a59838aa672ba2376bffa Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Sun, 3 Feb 2013 13:27:36 +0100 Subject: [PATCH 53/55] News about FR #49180. --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 11ae31cf44f..c343f4315a5 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,9 @@ PHP NEWS . Added recvmsg() and sendmsg() wrappers. (Gustavo) See https://wiki.php.net/rfc/sendrecvmsg +- Filter: + . Implemented FR #49180 - added MAC address validation. (Martin) + 24 Jan 2013, PHP 5.5.0 Alpha 4 - Core: From 7369a683797faa262b5aa859cf7347b14e74405c Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Sun, 3 Feb 2013 10:57:31 -0200 Subject: [PATCH 54/55] - Fixed ZTS build --- ext/filter/logical_filters.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 9b436a779e6..b8df2183b9d 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -837,7 +837,7 @@ void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ /* The current token did not end with e.g. a "." */ RETURN_VALIDATION_FAILED } - if (php_filter_parse_hex(input + offset, length, &ret) < 0) { + if (php_filter_parse_hex(input + offset, length, &ret TSRMLS_CC) < 0) { /* The current token is no valid hexadecimal digit */ RETURN_VALIDATION_FAILED } From 53159f6e05bc0c7879e2cb2fc275c3269ff239f1 Mon Sep 17 00:00:00 2001 From: Xinchen Hui Date: Mon, 4 Feb 2013 14:20:50 +0800 Subject: [PATCH 55/55] implicit declaration of zend_throw_exception --- ext/curl/curl_file.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/curl/curl_file.c b/ext/curl/curl_file.c index 27176784abc..b7cc4495015 100644 --- a/ext/curl/curl_file.c +++ b/ext/curl/curl_file.c @@ -23,6 +23,7 @@ #endif #include "php.h" +#include "Zend/zend_exceptions.h" #include "php_curl.h" #if HAVE_CURL