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