xref: /PHP-8.4/ext/sockets/sendrecvmsg.c (revision 2d66562e)
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 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, arg_pos) \
72 	do { \
73 		if ((l) < INT_MIN || (l) > INT_MAX) { \
74 			zend_argument_value_error((arg_pos), "must be between %d and %d", INT_MIN, INT_MAX); \
75 			RETURN_THROWS(); \
76 		} \
77 	} while (0)
78 
79 static struct {
80 	int			initialized;
81 	HashTable	ht;
82 } ancillary_registry;
83 
84 
ancillary_registery_free_elem(zval * el)85 static void ancillary_registery_free_elem(zval *el) {
86 	pefree(Z_PTR_P(el), 1);
87 }
88 
89 #ifdef ZTS
90 static MUTEX_T ancillary_mutex;
91 #endif
init_ancillary_registry(void)92 static void init_ancillary_registry(void)
93 {
94 	ancillary_reg_entry entry;
95 	anc_reg_key key;
96 	ancillary_registry.initialized = 1;
97 
98 	zend_hash_init(&ancillary_registry.ht, 32, NULL, ancillary_registery_free_elem, 1);
99 
100 #define PUT_ENTRY(sizev, var_size, calc, from, to, level, type) \
101 	entry.size			= sizev; \
102 	entry.var_el_size	= var_size; \
103 	entry.calc_space	= calc; \
104 	entry.from_array	= from; \
105 	entry.to_array		= to; \
106 	key.cmsg_level		= level; \
107 	key.cmsg_type		= type; \
108 	zend_hash_str_update_mem(&ancillary_registry.ht, (char*)&key, sizeof(key), (void*)&entry, sizeof(entry))
109 
110 #if defined(IPV6_PKTINFO) && defined(HAVE_IPV6)
111 	PUT_ENTRY(sizeof(struct in6_pktinfo), 0, 0, from_zval_write_in6_pktinfo,
112 			to_zval_read_in6_pktinfo, IPPROTO_IPV6, IPV6_PKTINFO);
113 #endif
114 
115 #if defined(IPV6_HOPLIMIT) && defined(HAVE_IPV6)
116 	PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int,
117 			to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT);
118 #endif
119 
120 #if defined(IPV6_TCLASS) && defined(HAVE_IPV6)
121 	PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int,
122 			to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS);
123 #endif
124 
125 #ifdef SO_PASSCRED
126 #ifdef HAVE_STRUCT_UCRED
127 	PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred,
128 			to_zval_read_ucred, SOL_SOCKET, SCM_CREDENTIALS);
129 #else
130 	PUT_ENTRY(sizeof(struct cmsgcred), 0, 0, from_zval_write_ucred,
131 			to_zval_read_ucred, SOL_SOCKET, SCM_CREDS);
132 #endif
133 #endif
134 
135 #if defined(LOCAL_CREDS_PERSISTENT)
136 	PUT_ENTRY(SOCKCRED2SIZE(1), 1, 0, from_zval_write_ucred,
137 			to_zval_read_ucred, SOL_SOCKET, SCM_CREDS2);
138 #elif defined(LOCAL_CREDS)
139 	PUT_ENTRY(SOCKCREDSIZE(1), 1, 0, from_zval_write_ucred,
140 			to_zval_read_ucred, SOL_SOCKET, SCM_CREDS);
141 #endif
142 
143 #ifdef SCM_RIGHTS
144 	PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_fd_array,
145 			to_zval_read_fd_array, SOL_SOCKET, SCM_RIGHTS);
146 #endif
147 
148 }
destroy_ancillary_registry(void)149 static void destroy_ancillary_registry(void)
150 {
151 	if (ancillary_registry.initialized) {
152 		zend_hash_destroy(&ancillary_registry.ht);
153 		ancillary_registry.initialized = 0;
154 	}
155 }
get_ancillary_reg_entry(int cmsg_level,int msg_type)156 ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type)
157 {
158 	anc_reg_key			key = { cmsg_level, msg_type };
159 	ancillary_reg_entry	*entry;
160 
161 #ifdef ZTS
162 	tsrm_mutex_lock(ancillary_mutex);
163 #endif
164 	if (!ancillary_registry.initialized) {
165 		init_ancillary_registry();
166 	}
167 #ifdef ZTS
168 	tsrm_mutex_unlock(ancillary_mutex);
169 #endif
170 
171 	if ((entry = zend_hash_str_find_ptr(&ancillary_registry.ht, (char*)&key, sizeof(key))) != NULL) {
172 		return entry;
173 	} else {
174 		return NULL;
175 	}
176 }
177 
PHP_FUNCTION(socket_sendmsg)178 PHP_FUNCTION(socket_sendmsg)
179 {
180 	zval			*zsocket,
181 					*zmsg;
182 	zend_long			flags = 0;
183 	php_socket		*php_sock;
184 	struct msghdr	*msghdr;
185 	zend_llist		*allocations;
186 	struct err_s	err = {0};
187 	ssize_t			res;
188 
189 	/* zmsg should be passed by ref */
190 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "Oa|l", &zsocket, socket_ce, &zmsg, &flags) == FAILURE) {
191 		RETURN_THROWS();
192 	}
193 
194 	LONG_CHECK_VALID_INT(flags, 3);
195 
196 	php_sock = Z_SOCKET_P(zsocket);
197 	ENSURE_SOCKET_VALID(php_sock);
198 
199 	msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_send,
200 			sizeof(*msghdr), "msghdr", &allocations, &err);
201 
202 	if (err.has_error) {
203 		err_msg_dispose(&err);
204 		RETURN_FALSE;
205 	}
206 
207 	res = sendmsg(php_sock->bsd_socket, msghdr, (int)flags);
208 
209 	if (res != -1) {
210 		RETVAL_LONG((zend_long)res);
211 	} else {
212 		PHP_SOCKET_ERROR(php_sock, "Error in sendmsg", errno);
213 		RETVAL_FALSE;
214 	}
215 
216 	allocations_dispose(&allocations);
217 }
218 
PHP_FUNCTION(socket_recvmsg)219 PHP_FUNCTION(socket_recvmsg)
220 {
221 	zval			*zsocket,
222 					*zmsg;
223 	zend_long			flags = 0;
224 	php_socket		*php_sock;
225 	ssize_t			res;
226 	struct msghdr	*msghdr;
227 	zend_llist		*allocations;
228 	struct err_s	err = {0};
229 
230 	//ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
231 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "Oa|l", &zsocket, socket_ce, &zmsg, &flags) == FAILURE) {
232 		RETURN_THROWS();
233 	}
234 
235 	LONG_CHECK_VALID_INT(flags, 3);
236 
237 	php_sock = Z_SOCKET_P(zsocket);
238 	ENSURE_SOCKET_VALID(php_sock);
239 
240 	msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_recv,
241 			sizeof(*msghdr), "msghdr", &allocations, &err);
242 
243 	if (err.has_error) {
244 		err_msg_dispose(&err);
245 		RETURN_FALSE;
246 	}
247 
248 	res = recvmsg(php_sock->bsd_socket, msghdr, (int)flags);
249 
250 	if (res != -1) {
251 		zval *zres, tmp;
252 		struct key_value kv[] = {
253 				{KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), &res},
254 				{0}
255 		};
256 
257 
258 		zres = to_zval_run_conversions((char *)msghdr, to_zval_read_msghdr,
259 				"msghdr", kv, &err, &tmp);
260 
261 		/* we don;t need msghdr anymore; free it */
262 		msghdr = NULL;
263 
264 		zval_ptr_dtor(zmsg);
265 		if (!err.has_error) {
266 			ZVAL_COPY_VALUE(zmsg, zres);
267 		} else {
268 			err_msg_dispose(&err);
269 			ZVAL_FALSE(zmsg);
270 			/* no need to destroy/free zres -- it's NULL in this circumstance */
271 			assert(zres == NULL);
272 		}
273 		RETVAL_LONG((zend_long)res);
274 	} else {
275 		SOCKETS_G(last_error) = errno;
276 		php_error_docref(NULL, E_WARNING, "Error in recvmsg [%d]: %s",
277 				errno, sockets_strerror(errno));
278 		RETVAL_FALSE;
279 	}
280 
281 	allocations_dispose(&allocations);
282 }
283 
PHP_FUNCTION(socket_cmsg_space)284 PHP_FUNCTION(socket_cmsg_space)
285 {
286 	zend_long				level,
287 						type,
288 						n = 0;
289 	ancillary_reg_entry	*entry;
290 
291 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|l",
292 			&level, &type, &n) == FAILURE) {
293 		RETURN_THROWS();
294 	}
295 
296 	LONG_CHECK_VALID_INT(level, 1);
297 	LONG_CHECK_VALID_INT(type, 2);
298 	LONG_CHECK_VALID_INT(n, 3);
299 
300 	if (n < 0) {
301 		zend_argument_value_error(3, "must be greater than or equal to 0");
302 		RETURN_THROWS();
303 	}
304 
305 	entry = get_ancillary_reg_entry(level, type);
306 	if (entry == NULL) {
307 		zend_value_error("Pair level " ZEND_LONG_FMT " and/or type " ZEND_LONG_FMT " is not supported",
308 			level, type);
309 		RETURN_THROWS();
310 	}
311 
312 	if (entry->var_el_size > 0) {
313 		/* Leading underscore to avoid symbol collision on AIX. */
314 		size_t _rem_size = ZEND_LONG_MAX - entry->size;
315 		size_t n_max = _rem_size / entry->var_el_size;
316 		size_t size = entry->size + n * entry->var_el_size;
317 		size_t total_size = CMSG_SPACE(size);
318 		if (n > n_max /* zend_long overflow */
319 			|| total_size > ZEND_LONG_MAX
320 			|| total_size < size /* align overflow */) {
321 			zend_argument_value_error(3, "is too large");
322 			RETURN_THROWS();
323 		}
324 	}
325 
326 	RETURN_LONG((zend_long)CMSG_SPACE(entry->size + n * entry->var_el_size));
327 }
328 
329 #ifdef HAVE_IPV6
php_do_setsockopt_ipv6_rfc3542(php_socket * php_sock,int level,int optname,zval * arg4)330 int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *arg4)
331 {
332 	struct err_s	err = {0};
333 	zend_llist		*allocations = NULL;
334 	void			*opt_ptr;
335 	socklen_t		optlen;
336 	int				retval;
337 
338 	assert(level == IPPROTO_IPV6);
339 
340 	switch (optname) {
341 #ifdef IPV6_PKTINFO
342 	case IPV6_PKTINFO:
343 #ifdef PHP_WIN32
344 		if (Z_TYPE_P(arg4) == IS_ARRAY) {
345 			php_error_docref(NULL, E_WARNING, "Windows does not "
346 					"support sticky IPV6_PKTINFO");
347 			return FAILURE;
348 		} else {
349 			/* windows has no IPV6_RECVPKTINFO, and uses IPV6_PKTINFO
350 			 * for the same effect. We define IPV6_RECVPKTINFO to be
351 			 * IPV6_PKTINFO, so assume the assume user used IPV6_RECVPKTINFO */
352 			return 1;
353 		}
354 #endif
355 		opt_ptr = from_zval_run_conversions(arg4, php_sock, from_zval_write_in6_pktinfo,
356 				sizeof(struct in6_pktinfo),	"in6_pktinfo", &allocations, &err);
357 		if (err.has_error) {
358 			err_msg_dispose(&err);
359 			return FAILURE;
360 		}
361 
362 		optlen = sizeof(struct in6_pktinfo);
363 		goto dosockopt;
364 #endif
365 	}
366 
367 	/* we also support IPV6_TCLASS, but that can be handled by the default
368 	 * integer optval handling in the caller */
369 	return 1;
370 
371 dosockopt:
372 	retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen);
373 	if (retval != 0) {
374 		PHP_SOCKET_ERROR(php_sock, "Unable to set socket option", errno);
375 	}
376 	allocations_dispose(&allocations);
377 
378 	return retval != 0 ? FAILURE : SUCCESS;
379 }
380 
php_do_getsockopt_ipv6_rfc3542(php_socket * php_sock,int level,int optname,zval * result)381 int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result)
382 {
383 	struct err_s		err = {0};
384 	void				*buffer;
385 	socklen_t			size;
386 	int					res;
387 	to_zval_read_field	*reader;
388 
389 	assert(level == IPPROTO_IPV6);
390 
391 	switch (optname) {
392 #ifdef IPV6_PKTINFO
393 	case IPV6_PKTINFO:
394 		size = sizeof(struct in6_pktinfo);
395 		reader = &to_zval_read_in6_pktinfo;
396 		break;
397 #endif
398 	default:
399 		return 1;
400 	}
401 
402 	buffer = ecalloc(1, size);
403 	res = getsockopt(php_sock->bsd_socket, level, optname, buffer, &size);
404 	if (res != 0) {
405 		PHP_SOCKET_ERROR(php_sock, "unable to get socket option", errno);
406 	} else {
407 		zval tmp;
408 		zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo",
409 				empty_key_value_list, &err, &tmp);
410 		if (err.has_error) {
411 			err_msg_dispose(&err);
412 			res = -1;
413 		} else {
414 			ZVAL_COPY_VALUE(result, zv);
415 		}
416 	}
417 	efree(buffer);
418 
419 	return res == 0 ? SUCCESS : FAILURE;
420 }
421 #endif /* HAVE_IPV6 */
422 
php_socket_sendrecvmsg_init(INIT_FUNC_ARGS)423 void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS)
424 {
425 #ifdef ZTS
426 	ancillary_mutex = tsrm_mutex_alloc();
427 #endif
428 }
429 
php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS)430 void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS)
431 {
432 #ifdef ZTS
433 	tsrm_mutex_free(ancillary_mutex);
434 #endif
435 
436 	destroy_ancillary_registry();
437 }
438