xref: /PHP-7.4/ext/sockets/sendrecvmsg.c (revision 6a1bd57d)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Gustavo Lopes    <cataphract@php.net>                       |
16    +----------------------------------------------------------------------+
17  */
18 
19 #include <php.h>
20 #include "php_sockets.h"
21 #include "sendrecvmsg.h"
22 #include "conversions.h"
23 #include <limits.h>
24 #include <Zend/zend_llist.h>
25 #ifdef ZTS
26 #include <TSRM/TSRM.h>
27 #endif
28 
29 #define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024))
30 #define DEFAULT_BUFF_SIZE 8192
31 #define MAX_ARRAY_KEY_SIZE 128
32 
33 #ifdef PHP_WIN32
34 #include "windows_common.h"
35 #include <Mswsock.h>
36 #define IPV6_RECVPKTINFO	IPV6_PKTINFO
37 #define IPV6_RECVHOPLIMIT	IPV6_HOPLIMIT
38 #define msghdr _WSAMSG
39 
40 static GUID WSARecvMsg_GUID = WSAID_WSARECVMSG;
41 static __declspec(thread) LPFN_WSARECVMSG WSARecvMsg = NULL;
recvmsg(int sockfd,struct msghdr * msg,int flags)42 inline ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags)
43 {
44 	DWORD	recvd = 0,
45 			bytesReturned;
46 
47 	if (WSARecvMsg == NULL)	{
48 		int res = WSAIoctl((SOCKET) sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER,
49 			&WSARecvMsg_GUID, sizeof(WSARecvMsg_GUID),
50 			&WSARecvMsg, sizeof(WSARecvMsg),
51 			&bytesReturned, NULL, NULL);
52 		if (res != 0) {
53 			return -1;
54 		}
55 	}
56 
57 	msg->dwFlags = (DWORD)flags;
58 	return WSARecvMsg((SOCKET)sockfd, msg, &recvd, NULL, NULL) == 0
59 		? (ssize_t)recvd
60 		: -1;
61 }
sendmsg(int sockfd,const struct msghdr * msg,int flags)62 inline ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
63 {
64 	DWORD sent = 0;
65 	return WSASendMsg((SOCKET)sockfd, (struct msghdr*)msg, (DWORD)flags, &sent, NULL, NULL) == 0
66 		? (ssize_t)sent
67 		: -1;
68 }
69 #endif
70 
71 #define LONG_CHECK_VALID_INT(l) \
72 	do { \
73 		if ((l) < INT_MIN && (l) > INT_MAX) { \
74 			php_error_docref(NULL, E_WARNING, "The value " ZEND_LONG_FMT " does not fit inside " \
75 					"the boundaries of a native integer", (l)); \
76 			return; \
77 		} \
78 	} while (0)
79 
80 static struct {
81 	int			initialized;
82 	HashTable	ht;
83 } ancillary_registry;
84 
85 
ancillary_registery_free_elem(zval * el)86 static void ancillary_registery_free_elem(zval *el) {
87 	pefree(Z_PTR_P(el), 1);
88 }
89 
90 #ifdef ZTS
91 static MUTEX_T ancillary_mutex;
92 #endif
init_ancillary_registry(void)93 static void init_ancillary_registry(void)
94 {
95 	ancillary_reg_entry entry;
96 	anc_reg_key key;
97 	ancillary_registry.initialized = 1;
98 
99 	zend_hash_init(&ancillary_registry.ht, 32, NULL, ancillary_registery_free_elem, 1);
100 
101 #define PUT_ENTRY(sizev, var_size, calc, from, to, level, type) \
102 	entry.size			= sizev; \
103 	entry.var_el_size	= var_size; \
104 	entry.calc_space	= calc; \
105 	entry.from_array	= from; \
106 	entry.to_array		= to; \
107 	key.cmsg_level		= level; \
108 	key.cmsg_type		= type; \
109 	zend_hash_str_update_mem(&ancillary_registry.ht, (char*)&key, sizeof(key), (void*)&entry, sizeof(entry))
110 
111 #if defined(IPV6_PKTINFO) && HAVE_IPV6
112 	PUT_ENTRY(sizeof(struct in6_pktinfo), 0, 0, from_zval_write_in6_pktinfo,
113 			to_zval_read_in6_pktinfo, IPPROTO_IPV6, IPV6_PKTINFO);
114 #endif
115 
116 #if defined(IPV6_HOPLIMIT) && HAVE_IPV6
117 	PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int,
118 			to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT);
119 #endif
120 
121 #if defined(IPV6_TCLASS) && HAVE_IPV6
122 	PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int,
123 			to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS);
124 #endif
125 
126 #ifdef SO_PASSCRED
127 	PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred,
128 			to_zval_read_ucred, SOL_SOCKET, SCM_CREDENTIALS);
129 #endif
130 
131 #ifdef SCM_RIGHTS
132 	PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_fd_array,
133 			to_zval_read_fd_array, SOL_SOCKET, SCM_RIGHTS);
134 #endif
135 
136 }
destroy_ancillary_registry(void)137 static void destroy_ancillary_registry(void)
138 {
139 	if (ancillary_registry.initialized) {
140 		zend_hash_destroy(&ancillary_registry.ht);
141 		ancillary_registry.initialized = 0;
142 	}
143 }
get_ancillary_reg_entry(int cmsg_level,int msg_type)144 ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type)
145 {
146 	anc_reg_key			key = { cmsg_level, msg_type };
147 	ancillary_reg_entry	*entry;
148 
149 #ifdef ZTS
150 	tsrm_mutex_lock(ancillary_mutex);
151 #endif
152 	if (!ancillary_registry.initialized) {
153 		init_ancillary_registry();
154 	}
155 #ifdef ZTS
156 	tsrm_mutex_unlock(ancillary_mutex);
157 #endif
158 
159 	if ((entry = zend_hash_str_find_ptr(&ancillary_registry.ht, (char*)&key, sizeof(key))) != NULL) {
160 		return entry;
161 	} else {
162 		return NULL;
163 	}
164 }
165 
PHP_FUNCTION(socket_sendmsg)166 PHP_FUNCTION(socket_sendmsg)
167 {
168 	zval			*zsocket,
169 					*zmsg;
170 	zend_long			flags = 0;
171 	php_socket		*php_sock;
172 	struct msghdr	*msghdr;
173 	zend_llist		*allocations;
174 	struct err_s	err = {0};
175 	ssize_t			res;
176 
177 	/* zmsg should be passed by ref */
178 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra|l", &zsocket, &zmsg, &flags) == FAILURE) {
179 		return;
180 	}
181 
182 	LONG_CHECK_VALID_INT(flags);
183 
184 	if ((php_sock = (php_socket *)zend_fetch_resource(Z_RES_P(zsocket),
185 					php_sockets_le_socket_name, php_sockets_le_socket())) == NULL) {
186 		RETURN_FALSE;
187 	}
188 
189 	msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_send,
190 			sizeof(*msghdr), "msghdr", &allocations, &err);
191 
192 	if (err.has_error) {
193 		err_msg_dispose(&err);
194 		RETURN_FALSE;
195 	}
196 
197 	res = sendmsg(php_sock->bsd_socket, msghdr, (int)flags);
198 
199 	if (res != -1) {
200 		RETVAL_LONG((zend_long)res);
201 	} else {
202 		PHP_SOCKET_ERROR(php_sock, "error in sendmsg", errno);
203 		RETVAL_FALSE;
204 	}
205 
206 	allocations_dispose(&allocations);
207 }
208 
PHP_FUNCTION(socket_recvmsg)209 PHP_FUNCTION(socket_recvmsg)
210 {
211 	zval			*zsocket,
212 					*zmsg;
213 	zend_long			flags = 0;
214 	php_socket		*php_sock;
215 	ssize_t			res;
216 	struct msghdr	*msghdr;
217 	zend_llist		*allocations;
218 	struct err_s	err = {0};
219 
220 	//ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
221 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra|l",
222 			&zsocket, &zmsg, &flags) == FAILURE) {
223 		return;
224 	}
225 
226 	LONG_CHECK_VALID_INT(flags);
227 
228 	if ((php_sock = (php_socket *)zend_fetch_resource(Z_RES_P(zsocket),
229 					php_sockets_le_socket_name, php_sockets_le_socket())) == NULL) {
230 		RETURN_FALSE;
231 	}
232 
233 	msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_recv,
234 			sizeof(*msghdr), "msghdr", &allocations, &err);
235 
236 	if (err.has_error) {
237 		err_msg_dispose(&err);
238 		RETURN_FALSE;
239 	}
240 
241 	res = recvmsg(php_sock->bsd_socket, msghdr, (int)flags);
242 
243 	if (res != -1) {
244 		zval *zres, tmp;
245 		struct key_value kv[] = {
246 				{KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), &res},
247 				{0}
248 		};
249 
250 
251 		zres = to_zval_run_conversions((char *)msghdr, to_zval_read_msghdr,
252 				"msghdr", kv, &err, &tmp);
253 
254 		/* we don;t need msghdr anymore; free it */
255 		msghdr = NULL;
256 
257 		zval_ptr_dtor(zmsg);
258 		if (!err.has_error) {
259 			ZVAL_COPY_VALUE(zmsg, zres);
260 		} else {
261 			err_msg_dispose(&err);
262 			ZVAL_FALSE(zmsg);
263 			/* no need to destroy/free zres -- it's NULL in this circumstance */
264 			assert(zres == NULL);
265 		}
266 		RETVAL_LONG((zend_long)res);
267 	} else {
268 		SOCKETS_G(last_error) = errno;
269 		php_error_docref(NULL, E_WARNING, "error in recvmsg [%d]: %s",
270 				errno, sockets_strerror(errno));
271 		RETVAL_FALSE;
272 	}
273 
274 	allocations_dispose(&allocations);
275 }
276 
PHP_FUNCTION(socket_cmsg_space)277 PHP_FUNCTION(socket_cmsg_space)
278 {
279 	zend_long				level,
280 						type,
281 						n = 0;
282 	ancillary_reg_entry	*entry;
283 
284 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|l",
285 			&level, &type, &n) == FAILURE) {
286 		return;
287 	}
288 
289 	LONG_CHECK_VALID_INT(level);
290 	LONG_CHECK_VALID_INT(type);
291 	LONG_CHECK_VALID_INT(n);
292 
293 	if (n < 0) {
294 		php_error_docref(NULL, E_WARNING, "The third argument "
295 				"cannot be negative");
296 		return;
297 	}
298 
299 	entry = get_ancillary_reg_entry(level, type);
300 	if (entry == NULL) {
301 		php_error_docref(NULL, E_WARNING, "The pair level " ZEND_LONG_FMT "/type " ZEND_LONG_FMT " is "
302 				"not supported by PHP", level, type);
303 		return;
304 	}
305 
306 	if (entry->var_el_size > 0 && n > (zend_long)((ZEND_LONG_MAX - entry->size -
307 			CMSG_SPACE(0) - 15L) / entry->var_el_size)) {
308 		/* the -15 is to account for any padding CMSG_SPACE may add after the data */
309 		php_error_docref(NULL, E_WARNING, "The value for the "
310 				"third argument (" ZEND_LONG_FMT ") is too large", n);
311 		return;
312 	}
313 
314 	RETURN_LONG((zend_long)CMSG_SPACE(entry->size + n * entry->var_el_size));
315 }
316 
317 #if HAVE_IPV6
php_do_setsockopt_ipv6_rfc3542(php_socket * php_sock,int level,int optname,zval * arg4)318 int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *arg4)
319 {
320 	struct err_s	err = {0};
321 	zend_llist		*allocations = NULL;
322 	void			*opt_ptr;
323 	socklen_t		optlen;
324 	int				retval;
325 
326 	assert(level == IPPROTO_IPV6);
327 
328 	switch (optname) {
329 #ifdef IPV6_PKTINFO
330 	case IPV6_PKTINFO:
331 #ifdef PHP_WIN32
332 		if (Z_TYPE_P(arg4) == IS_ARRAY) {
333 			php_error_docref(NULL, E_WARNING, "Windows does not "
334 					"support sticky IPV6_PKTINFO");
335 			return FAILURE;
336 		} else {
337 			/* windows has no IPV6_RECVPKTINFO, and uses IPV6_PKTINFO
338 			 * for the same effect. We define IPV6_RECVPKTINFO to be
339 			 * IPV6_PKTINFO, so assume the assume user used IPV6_RECVPKTINFO */
340 			return 1;
341 		}
342 #endif
343 		opt_ptr = from_zval_run_conversions(arg4, php_sock, from_zval_write_in6_pktinfo,
344 				sizeof(struct in6_pktinfo),	"in6_pktinfo", &allocations, &err);
345 		if (err.has_error) {
346 			err_msg_dispose(&err);
347 			return FAILURE;
348 		}
349 
350 		optlen = sizeof(struct in6_pktinfo);
351 		goto dosockopt;
352 #endif
353 	}
354 
355 	/* we also support IPV6_TCLASS, but that can be handled by the default
356 	 * integer optval handling in the caller */
357 	return 1;
358 
359 dosockopt:
360 	retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen);
361 	if (retval != 0) {
362 		PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno);
363 	}
364 	allocations_dispose(&allocations);
365 
366 	return retval != 0 ? FAILURE : SUCCESS;
367 }
368 
php_do_getsockopt_ipv6_rfc3542(php_socket * php_sock,int level,int optname,zval * result)369 int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result)
370 {
371 	struct err_s		err = {0};
372 	void				*buffer;
373 	socklen_t			size;
374 	int					res;
375 	to_zval_read_field	*reader;
376 
377 	assert(level == IPPROTO_IPV6);
378 
379 	switch (optname) {
380 #ifdef IPV6_PKTINFO
381 	case IPV6_PKTINFO:
382 		size = sizeof(struct in6_pktinfo);
383 		reader = &to_zval_read_in6_pktinfo;
384 		break;
385 #endif
386 	default:
387 		return 1;
388 	}
389 
390 	buffer = ecalloc(1, size);
391 	res = getsockopt(php_sock->bsd_socket, level, optname, buffer, &size);
392 	if (res != 0) {
393 		PHP_SOCKET_ERROR(php_sock, "unable to get socket option", errno);
394 	} else {
395 		zval tmp;
396 		zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo",
397 				empty_key_value_list, &err, &tmp);
398 		if (err.has_error) {
399 			err_msg_dispose(&err);
400 			res = -1;
401 		} else {
402 			ZVAL_COPY_VALUE(result, zv);
403 		}
404 	}
405 	efree(buffer);
406 
407 	return res == 0 ? SUCCESS : FAILURE;
408 }
409 #endif /* HAVE_IPV6 */
410 
php_socket_sendrecvmsg_init(INIT_FUNC_ARGS)411 void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS)
412 {
413 	/* IPv6 ancillary data */
414 #if defined(IPV6_RECVPKTINFO) && HAVE_IPV6
415 	REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO",		IPV6_RECVPKTINFO,	CONST_CS | CONST_PERSISTENT);
416 	REGISTER_LONG_CONSTANT("IPV6_PKTINFO",          IPV6_PKTINFO,       CONST_CS | CONST_PERSISTENT);
417 #endif
418 #if defined(IPV6_RECVHOPLIMIT) && HAVE_IPV6
419 	REGISTER_LONG_CONSTANT("IPV6_RECVHOPLIMIT",		IPV6_RECVHOPLIMIT,	CONST_CS | CONST_PERSISTENT);
420 	REGISTER_LONG_CONSTANT("IPV6_HOPLIMIT",         IPV6_HOPLIMIT,      CONST_CS | CONST_PERSISTENT);
421 #endif
422 	/* would require some effort:
423 	REGISTER_LONG_CONSTANT("IPV6_RECVRTHDR",		IPV6_RECVRTHDR,		CONST_CS | CONST_PERSISTENT);
424 	REGISTER_LONG_CONSTANT("IPV6_RECVHOPOPTS",		IPV6_RECVHOPOPTS,	CONST_CS | CONST_PERSISTENT);
425 	REGISTER_LONG_CONSTANT("IPV6_RECVDSTOPTS",		IPV6_RECVDSTOPTS,	CONST_CS | CONST_PERSISTENT);
426 	*/
427 #if defined(IPV6_RECVTCLASS) && HAVE_IPV6
428 	REGISTER_LONG_CONSTANT("IPV6_RECVTCLASS",		IPV6_RECVTCLASS,	CONST_CS | CONST_PERSISTENT);
429 	REGISTER_LONG_CONSTANT("IPV6_TCLASS",			IPV6_TCLASS,		CONST_CS | CONST_PERSISTENT);
430 #endif
431 
432 	/*
433 	REGISTER_LONG_CONSTANT("IPV6_RTHDR",			IPV6_RTHDR,			CONST_CS | CONST_PERSISTENT);
434 	REGISTER_LONG_CONSTANT("IPV6_HOPOPTS",			IPV6_HOPOPTS,		CONST_CS | CONST_PERSISTENT);
435 	REGISTER_LONG_CONSTANT("IPV6_DSTOPTS",			IPV6_DSTOPTS,		CONST_CS | CONST_PERSISTENT);
436 	*/
437 
438 #ifdef SCM_RIGHTS
439 	REGISTER_LONG_CONSTANT("SCM_RIGHTS",			SCM_RIGHTS,			CONST_CS | CONST_PERSISTENT);
440 #endif
441 #ifdef SO_PASSCRED
442 	REGISTER_LONG_CONSTANT("SCM_CREDENTIALS",		SCM_CREDENTIALS,	CONST_CS | CONST_PERSISTENT);
443 	REGISTER_LONG_CONSTANT("SO_PASSCRED",			SO_PASSCRED,		CONST_CS | CONST_PERSISTENT);
444 #endif
445 
446 #ifdef ZTS
447 	ancillary_mutex = tsrm_mutex_alloc();
448 #endif
449 }
450 
php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS)451 void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS)
452 {
453 #ifdef ZTS
454 	tsrm_mutex_free(ancillary_mutex);
455 #endif
456 
457 	destroy_ancillary_registry();
458 }
459