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