xref: /PHP-7.2/ext/sockets/sendrecvmsg.c (revision 0d6c2448)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2018 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_docref0(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 		zend_llist_destroy(allocations);
201 		efree(allocations);
202 
203 		RETURN_LONG((zend_long)res);
204 	} else {
205 		PHP_SOCKET_ERROR(php_sock, "error in sendmsg", errno);
206 		RETURN_FALSE;
207 	}
208 }
209 
PHP_FUNCTION(socket_recvmsg)210 PHP_FUNCTION(socket_recvmsg)
211 {
212 	zval			*zsocket,
213 					*zmsg;
214 	zend_long			flags = 0;
215 	php_socket		*php_sock;
216 	ssize_t			res;
217 	struct msghdr	*msghdr;
218 	zend_llist		*allocations;
219 	struct err_s	err = {0};
220 
221 	//ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
222 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra/|l",
223 			&zsocket, &zmsg, &flags) == FAILURE) {
224 		return;
225 	}
226 
227 	LONG_CHECK_VALID_INT(flags);
228 
229 	if ((php_sock = (php_socket *)zend_fetch_resource(Z_RES_P(zsocket),
230 					php_sockets_le_socket_name, php_sockets_le_socket())) == NULL) {
231 		RETURN_FALSE;
232 	}
233 
234 	msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_recv,
235 			sizeof(*msghdr), "msghdr", &allocations, &err);
236 
237 	if (err.has_error) {
238 		err_msg_dispose(&err);
239 		RETURN_FALSE;
240 	}
241 
242 	res = recvmsg(php_sock->bsd_socket, msghdr, (int)flags);
243 
244 	if (res != -1) {
245 		zval *zres, tmp;
246 		struct key_value kv[] = {
247 				{KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), &res},
248 				{0}
249 		};
250 
251 
252 		zres = to_zval_run_conversions((char *)msghdr, to_zval_read_msghdr,
253 				"msghdr", kv, &err, &tmp);
254 
255 		/* we don;t need msghdr anymore; free it */
256 		msghdr = NULL;
257 		allocations_dispose(&allocations);
258 
259 		zval_dtor(zmsg);
260 		if (!err.has_error) {
261 			ZVAL_COPY_VALUE(zmsg, zres);
262 		} else {
263 			err_msg_dispose(&err);
264 			ZVAL_FALSE(zmsg);
265 			/* no need to destroy/free zres -- it's NULL in this circumstance */
266 			assert(zres == NULL);
267 		}
268 	} else {
269 		SOCKETS_G(last_error) = errno;
270 		php_error_docref(NULL, E_WARNING, "error in recvmsg [%d]: %s",
271 				errno, sockets_strerror(errno));
272 		RETURN_FALSE;
273 	}
274 
275 	RETURN_LONG((zend_long)res);
276 }
277 
PHP_FUNCTION(socket_cmsg_space)278 PHP_FUNCTION(socket_cmsg_space)
279 {
280 	zend_long				level,
281 						type,
282 						n = 0;
283 	ancillary_reg_entry	*entry;
284 
285 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|l",
286 			&level, &type, &n) == FAILURE) {
287 		return;
288 	}
289 
290 	LONG_CHECK_VALID_INT(level);
291 	LONG_CHECK_VALID_INT(type);
292 	LONG_CHECK_VALID_INT(n);
293 
294 	if (n < 0) {
295 		php_error_docref0(NULL, E_WARNING, "The third argument "
296 				"cannot be negative");
297 		return;
298 	}
299 
300 	entry = get_ancillary_reg_entry(level, type);
301 	if (entry == NULL) {
302 		php_error_docref0(NULL, E_WARNING, "The pair level " ZEND_LONG_FMT "/type " ZEND_LONG_FMT " is "
303 				"not supported by PHP", level, type);
304 		return;
305 	}
306 
307 	if (entry->var_el_size > 0 && n > (zend_long)((ZEND_LONG_MAX - entry->size -
308 			CMSG_SPACE(0) - 15L) / entry->var_el_size)) {
309 		/* the -15 is to account for any padding CMSG_SPACE may add after the data */
310 		php_error_docref0(NULL, E_WARNING, "The value for the "
311 				"third argument (" ZEND_LONG_FMT ") is too large", n);
312 		return;
313 	}
314 
315 	RETURN_LONG((zend_long)CMSG_SPACE(entry->size + n * entry->var_el_size));
316 }
317 
318 #if HAVE_IPV6
php_do_setsockopt_ipv6_rfc3542(php_socket * php_sock,int level,int optname,zval * arg4)319 int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *arg4)
320 {
321 	struct err_s	err = {0};
322 	zend_llist		*allocations = NULL;
323 	void			*opt_ptr;
324 	socklen_t		optlen;
325 	int				retval;
326 
327 	assert(level == IPPROTO_IPV6);
328 
329 	switch (optname) {
330 #ifdef IPV6_PKTINFO
331 	case IPV6_PKTINFO:
332 #ifdef PHP_WIN32
333 		if (Z_TYPE_P(arg4) == IS_ARRAY) {
334 			php_error_docref0(NULL, E_WARNING, "Windows does not "
335 					"support sticky IPV6_PKTINFO");
336 			return FAILURE;
337 		} else {
338 			/* windows has no IPV6_RECVPKTINFO, and uses IPV6_PKTINFO
339 			 * for the same effect. We define IPV6_RECVPKTINFO to be
340 			 * IPV6_PKTINFO, so assume the assume user used IPV6_RECVPKTINFO */
341 			return 1;
342 		}
343 #endif
344 		opt_ptr = from_zval_run_conversions(arg4, php_sock, from_zval_write_in6_pktinfo,
345 				sizeof(struct in6_pktinfo),	"in6_pktinfo", &allocations, &err);
346 		if (err.has_error) {
347 			err_msg_dispose(&err);
348 			return FAILURE;
349 		}
350 
351 		optlen = sizeof(struct in6_pktinfo);
352 		goto dosockopt;
353 #endif
354 	}
355 
356 	/* we also support IPV6_TCLASS, but that can be handled by the default
357 	 * integer optval handling in the caller */
358 	return 1;
359 
360 dosockopt:
361 	retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen);
362 	if (retval != 0) {
363 		PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno);
364 	}
365 	allocations_dispose(&allocations);
366 
367 	return retval != 0 ? FAILURE : SUCCESS;
368 }
369 
php_do_getsockopt_ipv6_rfc3542(php_socket * php_sock,int level,int optname,zval * result)370 int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result)
371 {
372 	struct err_s		err = {0};
373 	void				*buffer;
374 	socklen_t			size;
375 	int					res;
376 	to_zval_read_field	*reader;
377 
378 	assert(level == IPPROTO_IPV6);
379 
380 	switch (optname) {
381 #ifdef IPV6_PKTINFO
382 	case IPV6_PKTINFO:
383 		size = sizeof(struct in6_pktinfo);
384 		reader = &to_zval_read_in6_pktinfo;
385 		break;
386 #endif
387 	default:
388 		return 1;
389 	}
390 
391 	buffer = ecalloc(1, size);
392 	res = getsockopt(php_sock->bsd_socket, level, optname, buffer, &size);
393 	if (res != 0) {
394 		PHP_SOCKET_ERROR(php_sock, "unable to get socket option", errno);
395 	} else {
396 		zval tmp;
397 		zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo",
398 				empty_key_value_list, &err, &tmp);
399 		if (err.has_error) {
400 			err_msg_dispose(&err);
401 			res = -1;
402 		} else {
403 			ZVAL_COPY_VALUE(result, zv);
404 		}
405 	}
406 	efree(buffer);
407 
408 	return res == 0 ? SUCCESS : FAILURE;
409 }
410 #endif /* HAVE_IPV6 */
411 
php_socket_sendrecvmsg_init(INIT_FUNC_ARGS)412 void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS)
413 {
414 	/* IPv6 ancillary data */
415 #if defined(IPV6_RECVPKTINFO) && HAVE_IPV6
416 	REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO",		IPV6_RECVPKTINFO,	CONST_CS | CONST_PERSISTENT);
417 	REGISTER_LONG_CONSTANT("IPV6_PKTINFO",          IPV6_PKTINFO,       CONST_CS | CONST_PERSISTENT);
418 #endif
419 #if defined(IPV6_RECVHOPLIMIT) && HAVE_IPV6
420 	REGISTER_LONG_CONSTANT("IPV6_RECVHOPLIMIT",		IPV6_RECVHOPLIMIT,	CONST_CS | CONST_PERSISTENT);
421 	REGISTER_LONG_CONSTANT("IPV6_HOPLIMIT",         IPV6_HOPLIMIT,      CONST_CS | CONST_PERSISTENT);
422 #endif
423 	/* would require some effort:
424 	REGISTER_LONG_CONSTANT("IPV6_RECVRTHDR",		IPV6_RECVRTHDR,		CONST_CS | CONST_PERSISTENT);
425 	REGISTER_LONG_CONSTANT("IPV6_RECVHOPOPTS",		IPV6_RECVHOPOPTS,	CONST_CS | CONST_PERSISTENT);
426 	REGISTER_LONG_CONSTANT("IPV6_RECVDSTOPTS",		IPV6_RECVDSTOPTS,	CONST_CS | CONST_PERSISTENT);
427 	*/
428 #if defined(IPV6_RECVTCLASS) && HAVE_IPV6
429 	REGISTER_LONG_CONSTANT("IPV6_RECVTCLASS",		IPV6_RECVTCLASS,	CONST_CS | CONST_PERSISTENT);
430 	REGISTER_LONG_CONSTANT("IPV6_TCLASS",			IPV6_TCLASS,		CONST_CS | CONST_PERSISTENT);
431 #endif
432 
433 	/*
434 	REGISTER_LONG_CONSTANT("IPV6_RTHDR",			IPV6_RTHDR,			CONST_CS | CONST_PERSISTENT);
435 	REGISTER_LONG_CONSTANT("IPV6_HOPOPTS",			IPV6_HOPOPTS,		CONST_CS | CONST_PERSISTENT);
436 	REGISTER_LONG_CONSTANT("IPV6_DSTOPTS",			IPV6_DSTOPTS,		CONST_CS | CONST_PERSISTENT);
437 	*/
438 
439 #ifdef SCM_RIGHTS
440 	REGISTER_LONG_CONSTANT("SCM_RIGHTS",			SCM_RIGHTS,			CONST_CS | CONST_PERSISTENT);
441 #endif
442 #ifdef SO_PASSCRED
443 	REGISTER_LONG_CONSTANT("SCM_CREDENTIALS",		SCM_CREDENTIALS,	CONST_CS | CONST_PERSISTENT);
444 	REGISTER_LONG_CONSTANT("SO_PASSCRED",			SO_PASSCRED,		CONST_CS | CONST_PERSISTENT);
445 #endif
446 
447 #ifdef ZTS
448 	ancillary_mutex = tsrm_mutex_alloc();
449 #endif
450 }
451 
php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS)452 void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS)
453 {
454 #ifdef ZTS
455 	tsrm_mutex_free(ancillary_mutex);
456 #endif
457 
458 	destroy_ancillary_registry();
459 }
460