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) && 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) && 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) && 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 ANC_CREDS_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 #if 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