/src/php-src/main/streams/xp_socket.c
Line | Count | Source (jump to first uncovered line) |
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 | | | Author: Wez Furlong <wez@thebrainroom.com> | |
14 | | +----------------------------------------------------------------------+ |
15 | | */ |
16 | | |
17 | | #include "php.h" |
18 | | #include "ext/standard/file.h" |
19 | | #include "streams/php_streams_int.h" |
20 | | #include "php_network.h" |
21 | | |
22 | | #if defined(PHP_WIN32) || defined(__riscos__) |
23 | | # undef AF_UNIX |
24 | | #endif |
25 | | |
26 | | #ifdef AF_UNIX |
27 | | #include <sys/un.h> |
28 | | #endif |
29 | | |
30 | | #ifndef MSG_DONTWAIT |
31 | | # define MSG_DONTWAIT 0 |
32 | | #endif |
33 | | |
34 | | #ifndef MSG_PEEK |
35 | | # define MSG_PEEK 0 |
36 | | #endif |
37 | | |
38 | | #ifdef PHP_WIN32 |
39 | | /* send/recv family on windows expects int */ |
40 | | # define XP_SOCK_BUF_SIZE(sz) (((sz) > INT_MAX) ? INT_MAX : (int)(sz)) |
41 | | #else |
42 | 0 | # define XP_SOCK_BUF_SIZE(sz) (sz) |
43 | | #endif |
44 | | |
45 | | const php_stream_ops php_stream_generic_socket_ops; |
46 | | PHPAPI const php_stream_ops php_stream_socket_ops; |
47 | | static const php_stream_ops php_stream_udp_socket_ops; |
48 | | #ifdef AF_UNIX |
49 | | static const php_stream_ops php_stream_unix_socket_ops; |
50 | | static const php_stream_ops php_stream_unixdg_socket_ops; |
51 | | #endif |
52 | | |
53 | | |
54 | | static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam); |
55 | | |
56 | | /* {{{ Generic socket stream operations */ |
57 | | static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t count) |
58 | 0 | { |
59 | 0 | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
60 | 0 | ssize_t didwrite; |
61 | 0 | struct timeval *ptimeout; |
62 | |
|
63 | 0 | if (!sock || sock->socket == -1) { |
64 | 0 | return 0; |
65 | 0 | } |
66 | | |
67 | 0 | if (sock->timeout.tv_sec == -1) |
68 | 0 | ptimeout = NULL; |
69 | 0 | else |
70 | 0 | ptimeout = &sock->timeout; |
71 | |
|
72 | 0 | retry: |
73 | 0 | didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0); |
74 | |
|
75 | 0 | if (didwrite <= 0) { |
76 | 0 | char *estr; |
77 | 0 | int err = php_socket_errno(); |
78 | |
|
79 | 0 | if (PHP_IS_TRANSIENT_ERROR(err)) { |
80 | 0 | if (sock->is_blocked) { |
81 | 0 | int retval; |
82 | |
|
83 | 0 | sock->timeout_event = 0; |
84 | |
|
85 | 0 | do { |
86 | 0 | retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout); |
87 | |
|
88 | 0 | if (retval == 0) { |
89 | 0 | sock->timeout_event = 1; |
90 | 0 | break; |
91 | 0 | } |
92 | | |
93 | 0 | if (retval > 0) { |
94 | | /* writable now; retry */ |
95 | 0 | goto retry; |
96 | 0 | } |
97 | | |
98 | 0 | err = php_socket_errno(); |
99 | 0 | } while (err == EINTR); |
100 | 0 | } else { |
101 | | /* EWOULDBLOCK/EAGAIN is not an error for a non-blocking stream. |
102 | | * Report zero byte write instead. */ |
103 | 0 | return 0; |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | 0 | if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { |
108 | 0 | estr = php_socket_strerror(err, NULL, 0); |
109 | 0 | php_error_docref(NULL, E_NOTICE, |
110 | 0 | "Send of " ZEND_LONG_FMT " bytes failed with errno=%d %s", |
111 | 0 | (zend_long)count, err, estr); |
112 | 0 | efree(estr); |
113 | 0 | } |
114 | 0 | } |
115 | | |
116 | 0 | if (didwrite > 0) { |
117 | 0 | php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0); |
118 | 0 | } |
119 | |
|
120 | 0 | return didwrite; |
121 | 0 | } |
122 | | |
123 | | static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock, bool has_buffered_data) |
124 | 0 | { |
125 | 0 | int retval; |
126 | 0 | struct timeval *ptimeout, zero_timeout; |
127 | |
|
128 | 0 | if (!sock || sock->socket == -1) { |
129 | 0 | return; |
130 | 0 | } |
131 | | |
132 | 0 | sock->timeout_event = 0; |
133 | |
|
134 | 0 | if (has_buffered_data) { |
135 | | /* If there is already buffered data, use no timeout. */ |
136 | 0 | zero_timeout.tv_sec = 0; |
137 | 0 | zero_timeout.tv_usec = 0; |
138 | 0 | ptimeout = &zero_timeout; |
139 | 0 | } else if (sock->timeout.tv_sec == -1) { |
140 | 0 | ptimeout = NULL; |
141 | 0 | } else { |
142 | 0 | ptimeout = &sock->timeout; |
143 | 0 | } |
144 | |
|
145 | 0 | while(1) { |
146 | 0 | retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout); |
147 | |
|
148 | 0 | if (retval == 0) |
149 | 0 | sock->timeout_event = 1; |
150 | |
|
151 | 0 | if (retval >= 0) |
152 | 0 | break; |
153 | | |
154 | 0 | if (php_socket_errno() != EINTR) |
155 | 0 | break; |
156 | 0 | } |
157 | 0 | } |
158 | | |
159 | | static ssize_t php_sockop_read(php_stream *stream, char *buf, size_t count) |
160 | 0 | { |
161 | 0 | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
162 | |
|
163 | 0 | if (!sock || sock->socket == -1) { |
164 | 0 | return -1; |
165 | 0 | } |
166 | | |
167 | 0 | int recv_flags = 0; |
168 | | /* Special handling for blocking read. */ |
169 | 0 | if (sock->is_blocked) { |
170 | | /* Find out if there is any data buffered from the previous read. */ |
171 | 0 | bool has_buffered_data = stream->has_buffered_data; |
172 | | /* No need to wait if there is any data buffered or no timeout. */ |
173 | 0 | bool dont_wait = has_buffered_data || |
174 | 0 | (sock->timeout.tv_sec == 0 && sock->timeout.tv_usec == 0); |
175 | | /* Set MSG_DONTWAIT if no wait is needed or there is unlimited timeout which was |
176 | | * added by fix for #41984 committed in 9343c5404. */ |
177 | 0 | if (dont_wait || sock->timeout.tv_sec != -1) { |
178 | 0 | recv_flags = MSG_DONTWAIT; |
179 | 0 | } |
180 | | /* If the wait is needed or it is a platform without MSG_DONTWAIT support (e.g. Windows), |
181 | | * then poll for data. */ |
182 | 0 | if (!dont_wait || MSG_DONTWAIT == 0) { |
183 | 0 | php_sock_stream_wait_for_data(stream, sock, has_buffered_data); |
184 | 0 | if (sock->timeout_event) { |
185 | | /* It is ok to timeout if there is any data buffered so return 0, otherwise -1. */ |
186 | 0 | return has_buffered_data ? 0 : -1; |
187 | 0 | } |
188 | 0 | } |
189 | 0 | } |
190 | | |
191 | 0 | ssize_t nr_bytes = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(count), recv_flags); |
192 | 0 | int err = php_socket_errno(); |
193 | |
|
194 | 0 | if (nr_bytes < 0) { |
195 | 0 | if (PHP_IS_TRANSIENT_ERROR(err)) { |
196 | 0 | nr_bytes = 0; |
197 | 0 | } else { |
198 | 0 | stream->eof = 1; |
199 | 0 | } |
200 | 0 | } else if (nr_bytes == 0) { |
201 | 0 | stream->eof = 1; |
202 | 0 | } |
203 | |
|
204 | 0 | if (nr_bytes > 0) { |
205 | 0 | php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0); |
206 | 0 | } |
207 | |
|
208 | 0 | return nr_bytes; |
209 | 0 | } |
210 | | |
211 | | |
212 | | static int php_sockop_close(php_stream *stream, int close_handle) |
213 | 0 | { |
214 | 0 | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
215 | | #ifdef PHP_WIN32 |
216 | | int n; |
217 | | #endif |
218 | |
|
219 | 0 | if (!sock) { |
220 | 0 | return 0; |
221 | 0 | } |
222 | | |
223 | 0 | if (close_handle) { |
224 | |
|
225 | | #ifdef PHP_WIN32 |
226 | | if (sock->socket == -1) |
227 | | sock->socket = SOCK_ERR; |
228 | | #endif |
229 | 0 | if (sock->socket != SOCK_ERR) { |
230 | | #ifdef PHP_WIN32 |
231 | | /* prevent more data from coming in */ |
232 | | shutdown(sock->socket, SHUT_RD); |
233 | | |
234 | | /* try to make sure that the OS sends all data before we close the connection. |
235 | | * Essentially, we are waiting for the socket to become writeable, which means |
236 | | * that all pending data has been sent. |
237 | | * We use a small timeout which should encourage the OS to send the data, |
238 | | * but at the same time avoid hanging indefinitely. |
239 | | * */ |
240 | | do { |
241 | | n = php_pollfd_for_ms(sock->socket, POLLOUT, 500); |
242 | | } while (n == -1 && php_socket_errno() == EINTR); |
243 | | #endif |
244 | 0 | closesocket(sock->socket); |
245 | 0 | sock->socket = SOCK_ERR; |
246 | 0 | } |
247 | |
|
248 | 0 | } |
249 | |
|
250 | 0 | pefree(sock, php_stream_is_persistent(stream)); |
251 | |
|
252 | 0 | return 0; |
253 | 0 | } |
254 | | |
255 | | static int php_sockop_flush(php_stream *stream) |
256 | 0 | { |
257 | | #if 0 |
258 | | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
259 | | return fsync(sock->socket); |
260 | | #endif |
261 | 0 | return 0; |
262 | 0 | } |
263 | | |
264 | | static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb) |
265 | 0 | { |
266 | | #ifdef ZEND_WIN32 |
267 | | return 0; |
268 | | #else |
269 | 0 | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
270 | |
|
271 | 0 | return zend_fstat(sock->socket, &ssb->sb); |
272 | 0 | #endif |
273 | 0 | } |
274 | | |
275 | | static inline int sock_sendto(php_netstream_data_t *sock, const char *buf, size_t buflen, int flags, |
276 | | struct sockaddr *addr, socklen_t addrlen |
277 | | ) |
278 | 0 | { |
279 | 0 | int ret; |
280 | 0 | if (addr) { |
281 | 0 | ret = sendto(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, addr, XP_SOCK_BUF_SIZE(addrlen)); |
282 | |
|
283 | 0 | return (ret == SOCK_CONN_ERR) ? -1 : ret; |
284 | 0 | } |
285 | | #ifdef PHP_WIN32 |
286 | | return ((ret = send(sock->socket, buf, buflen > INT_MAX ? INT_MAX : (int)buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret; |
287 | | #else |
288 | 0 | return ((ret = send(sock->socket, buf, buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret; |
289 | 0 | #endif |
290 | 0 | } |
291 | | |
292 | | static inline int sock_recvfrom(php_netstream_data_t *sock, char *buf, size_t buflen, int flags, |
293 | | zend_string **textaddr, |
294 | | struct sockaddr **addr, socklen_t *addrlen |
295 | | ) |
296 | 0 | { |
297 | 0 | int ret; |
298 | 0 | int want_addr = textaddr || addr; |
299 | |
|
300 | 0 | if (want_addr) { |
301 | 0 | php_sockaddr_storage sa; |
302 | 0 | socklen_t sl = sizeof(sa); |
303 | 0 | ret = recvfrom(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, (struct sockaddr*)&sa, &sl); |
304 | 0 | ret = (ret == SOCK_CONN_ERR) ? -1 : ret; |
305 | | #ifdef PHP_WIN32 |
306 | | /* POSIX discards excess bytes without signalling failure; emulate this on Windows */ |
307 | | if (ret == -1 && WSAGetLastError() == WSAEMSGSIZE) { |
308 | | ret = buflen; |
309 | | } |
310 | | #endif |
311 | 0 | if (sl) { |
312 | 0 | php_network_populate_name_from_sockaddr((struct sockaddr*)&sa, sl, |
313 | 0 | textaddr, addr, addrlen); |
314 | 0 | } else { |
315 | 0 | if (textaddr) { |
316 | 0 | *textaddr = ZSTR_EMPTY_ALLOC(); |
317 | 0 | } |
318 | 0 | if (addr) { |
319 | 0 | *addr = NULL; |
320 | 0 | *addrlen = 0; |
321 | 0 | } |
322 | 0 | } |
323 | 0 | } else { |
324 | 0 | ret = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags); |
325 | 0 | ret = (ret == SOCK_CONN_ERR) ? -1 : ret; |
326 | 0 | } |
327 | |
|
328 | 0 | return ret; |
329 | 0 | } |
330 | | |
331 | | static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) |
332 | 0 | { |
333 | 0 | int oldmode, flags; |
334 | 0 | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
335 | 0 | php_stream_xport_param *xparam; |
336 | |
|
337 | 0 | if (!sock) { |
338 | 0 | return PHP_STREAM_OPTION_RETURN_NOTIMPL; |
339 | 0 | } |
340 | | |
341 | 0 | switch(option) { |
342 | 0 | case PHP_STREAM_OPTION_CHECK_LIVENESS: |
343 | 0 | { |
344 | 0 | struct timeval tv; |
345 | 0 | char buf; |
346 | 0 | int alive = 1; |
347 | |
|
348 | 0 | if (value == -1) { |
349 | 0 | if (sock->timeout.tv_sec == -1) { |
350 | 0 | tv.tv_sec = FG(default_socket_timeout); |
351 | 0 | tv.tv_usec = 0; |
352 | 0 | } else { |
353 | 0 | tv = sock->timeout; |
354 | 0 | } |
355 | 0 | } else { |
356 | 0 | tv.tv_sec = value; |
357 | 0 | tv.tv_usec = 0; |
358 | 0 | } |
359 | |
|
360 | 0 | if (sock->socket == -1) { |
361 | 0 | alive = 0; |
362 | 0 | } else if ( |
363 | 0 | ( |
364 | 0 | value == 0 && |
365 | 0 | !(stream->flags & PHP_STREAM_FLAG_NO_IO) && |
366 | 0 | ((MSG_DONTWAIT != 0) || !sock->is_blocked) |
367 | 0 | ) || |
368 | 0 | php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0 |
369 | 0 | ) { |
370 | | /* the poll() call was skipped if the socket is non-blocking (or MSG_DONTWAIT is available) and if the timeout is zero */ |
371 | | #ifdef PHP_WIN32 |
372 | | int ret; |
373 | | #else |
374 | 0 | ssize_t ret; |
375 | 0 | #endif |
376 | |
|
377 | 0 | ret = recv(sock->socket, &buf, sizeof(buf), MSG_PEEK|MSG_DONTWAIT); |
378 | 0 | if (0 == ret) { |
379 | | /* the counterpart did properly shutdown */ |
380 | 0 | alive = 0; |
381 | 0 | } else if (0 > ret) { |
382 | 0 | int err = php_socket_errno(); |
383 | 0 | if (err != EWOULDBLOCK && err != EMSGSIZE && err != EAGAIN) { |
384 | | /* there was an unrecoverable error */ |
385 | 0 | alive = 0; |
386 | 0 | } |
387 | 0 | } |
388 | 0 | } |
389 | 0 | return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; |
390 | 0 | } |
391 | | |
392 | 0 | case PHP_STREAM_OPTION_BLOCKING: |
393 | 0 | oldmode = sock->is_blocked; |
394 | 0 | if (SUCCESS == php_set_sock_blocking(sock->socket, value)) { |
395 | 0 | sock->is_blocked = value; |
396 | 0 | return oldmode; |
397 | 0 | } |
398 | 0 | return PHP_STREAM_OPTION_RETURN_ERR; |
399 | | |
400 | 0 | case PHP_STREAM_OPTION_READ_TIMEOUT: |
401 | 0 | sock->timeout = *(struct timeval*)ptrparam; |
402 | 0 | sock->timeout_event = 0; |
403 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
404 | | |
405 | 0 | case PHP_STREAM_OPTION_META_DATA_API: |
406 | 0 | add_assoc_bool((zval *)ptrparam, "timed_out", sock->timeout_event); |
407 | 0 | add_assoc_bool((zval *)ptrparam, "blocked", sock->is_blocked); |
408 | 0 | add_assoc_bool((zval *)ptrparam, "eof", stream->eof); |
409 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
410 | | |
411 | 0 | case PHP_STREAM_OPTION_XPORT_API: |
412 | 0 | xparam = (php_stream_xport_param *)ptrparam; |
413 | |
|
414 | 0 | switch (xparam->op) { |
415 | 0 | case STREAM_XPORT_OP_LISTEN: |
416 | 0 | xparam->outputs.returncode = (listen(sock->socket, xparam->inputs.backlog) == 0) ? 0: -1; |
417 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
418 | | |
419 | 0 | case STREAM_XPORT_OP_GET_NAME: |
420 | 0 | xparam->outputs.returncode = php_network_get_sock_name(sock->socket, |
421 | 0 | xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, |
422 | 0 | xparam->want_addr ? &xparam->outputs.addr : NULL, |
423 | 0 | xparam->want_addr ? &xparam->outputs.addrlen : NULL |
424 | 0 | ); |
425 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
426 | | |
427 | 0 | case STREAM_XPORT_OP_GET_PEER_NAME: |
428 | 0 | xparam->outputs.returncode = php_network_get_peer_name(sock->socket, |
429 | 0 | xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, |
430 | 0 | xparam->want_addr ? &xparam->outputs.addr : NULL, |
431 | 0 | xparam->want_addr ? &xparam->outputs.addrlen : NULL |
432 | 0 | ); |
433 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
434 | | |
435 | 0 | case STREAM_XPORT_OP_SEND: |
436 | 0 | flags = 0; |
437 | 0 | if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { |
438 | 0 | flags |= MSG_OOB; |
439 | 0 | } |
440 | 0 | xparam->outputs.returncode = sock_sendto(sock, |
441 | 0 | xparam->inputs.buf, xparam->inputs.buflen, |
442 | 0 | flags, |
443 | 0 | xparam->inputs.addr, |
444 | 0 | xparam->inputs.addrlen); |
445 | 0 | if (xparam->outputs.returncode == -1) { |
446 | 0 | char *err = php_socket_strerror(php_socket_errno(), NULL, 0); |
447 | 0 | php_error_docref(NULL, E_WARNING, |
448 | 0 | "%s\n", err); |
449 | 0 | efree(err); |
450 | 0 | } |
451 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
452 | | |
453 | 0 | case STREAM_XPORT_OP_RECV: |
454 | 0 | flags = 0; |
455 | 0 | if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { |
456 | 0 | flags |= MSG_OOB; |
457 | 0 | } |
458 | 0 | if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) { |
459 | 0 | flags |= MSG_PEEK; |
460 | 0 | } |
461 | 0 | xparam->outputs.returncode = sock_recvfrom(sock, |
462 | 0 | xparam->inputs.buf, xparam->inputs.buflen, |
463 | 0 | flags, |
464 | 0 | xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, |
465 | 0 | xparam->want_addr ? &xparam->outputs.addr : NULL, |
466 | 0 | xparam->want_addr ? &xparam->outputs.addrlen : NULL |
467 | 0 | ); |
468 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
469 | | |
470 | | |
471 | 0 | #ifdef HAVE_SHUTDOWN |
472 | | # ifndef SHUT_RD |
473 | | # define SHUT_RD 0 |
474 | | # endif |
475 | | # ifndef SHUT_WR |
476 | | # define SHUT_WR 1 |
477 | | # endif |
478 | | # ifndef SHUT_RDWR |
479 | | # define SHUT_RDWR 2 |
480 | | # endif |
481 | 0 | case STREAM_XPORT_OP_SHUTDOWN: { |
482 | 0 | static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR}; |
483 | |
|
484 | 0 | xparam->outputs.returncode = shutdown(sock->socket, shutdown_how[xparam->how]); |
485 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
486 | 0 | } |
487 | 0 | #endif |
488 | | |
489 | 0 | default: |
490 | 0 | break; |
491 | 0 | } |
492 | 0 | } |
493 | | |
494 | 0 | return PHP_STREAM_OPTION_RETURN_NOTIMPL; |
495 | 0 | } |
496 | | |
497 | | static int php_sockop_cast(php_stream *stream, int castas, void **ret) |
498 | 0 | { |
499 | 0 | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
500 | |
|
501 | 0 | if (!sock) { |
502 | 0 | return FAILURE; |
503 | 0 | } |
504 | | |
505 | 0 | switch(castas) { |
506 | 0 | case PHP_STREAM_AS_STDIO: |
507 | 0 | if (ret) { |
508 | 0 | *(FILE**)ret = fdopen(sock->socket, stream->mode); |
509 | 0 | if (*ret) |
510 | 0 | return SUCCESS; |
511 | 0 | return FAILURE; |
512 | 0 | } |
513 | 0 | return SUCCESS; |
514 | 0 | case PHP_STREAM_AS_FD_FOR_SELECT: |
515 | 0 | case PHP_STREAM_AS_FD: |
516 | 0 | case PHP_STREAM_AS_SOCKETD: |
517 | 0 | if (ret) |
518 | 0 | *(php_socket_t *)ret = sock->socket; |
519 | 0 | return SUCCESS; |
520 | 0 | default: |
521 | 0 | return FAILURE; |
522 | 0 | } |
523 | 0 | } |
524 | | /* }}} */ |
525 | | |
526 | | /* These may look identical, but we need them this way so that |
527 | | * we can determine which type of socket we are dealing with |
528 | | * by inspecting stream->ops. |
529 | | * A "useful" side-effect is that the user's scripts can then |
530 | | * make similar decisions using stream_get_meta_data. |
531 | | * */ |
532 | | const php_stream_ops php_stream_generic_socket_ops = { |
533 | | php_sockop_write, php_sockop_read, |
534 | | php_sockop_close, php_sockop_flush, |
535 | | "generic_socket", |
536 | | NULL, /* seek */ |
537 | | php_sockop_cast, |
538 | | php_sockop_stat, |
539 | | php_sockop_set_option, |
540 | | }; |
541 | | |
542 | | |
543 | | const php_stream_ops php_stream_socket_ops = { |
544 | | php_sockop_write, php_sockop_read, |
545 | | php_sockop_close, php_sockop_flush, |
546 | | "tcp_socket", |
547 | | NULL, /* seek */ |
548 | | php_sockop_cast, |
549 | | php_sockop_stat, |
550 | | php_tcp_sockop_set_option, |
551 | | }; |
552 | | |
553 | | static const php_stream_ops php_stream_udp_socket_ops = { |
554 | | php_sockop_write, php_sockop_read, |
555 | | php_sockop_close, php_sockop_flush, |
556 | | "udp_socket", |
557 | | NULL, /* seek */ |
558 | | php_sockop_cast, |
559 | | php_sockop_stat, |
560 | | php_tcp_sockop_set_option, |
561 | | }; |
562 | | |
563 | | #ifdef AF_UNIX |
564 | | static const php_stream_ops php_stream_unix_socket_ops = { |
565 | | php_sockop_write, php_sockop_read, |
566 | | php_sockop_close, php_sockop_flush, |
567 | | "unix_socket", |
568 | | NULL, /* seek */ |
569 | | php_sockop_cast, |
570 | | php_sockop_stat, |
571 | | php_tcp_sockop_set_option, |
572 | | }; |
573 | | static const php_stream_ops php_stream_unixdg_socket_ops = { |
574 | | php_sockop_write, php_sockop_read, |
575 | | php_sockop_close, php_sockop_flush, |
576 | | "udg_socket", |
577 | | NULL, /* seek */ |
578 | | php_sockop_cast, |
579 | | php_sockop_stat, |
580 | | php_tcp_sockop_set_option, |
581 | | }; |
582 | | #endif |
583 | | |
584 | | |
585 | | /* network socket operations */ |
586 | | |
587 | | #ifdef AF_UNIX |
588 | | static inline int parse_unix_address(php_stream_xport_param *xparam, struct sockaddr_un *unix_addr) |
589 | 0 | { |
590 | 0 | memset(unix_addr, 0, sizeof(*unix_addr)); |
591 | 0 | unix_addr->sun_family = AF_UNIX; |
592 | | |
593 | | /* Abstract namespace does not need to be NUL-terminated, while path-based |
594 | | * sockets should be. */ |
595 | 0 | bool is_abstract_ns = xparam->inputs.namelen > 0 && xparam->inputs.name[0] == '\0'; |
596 | 0 | unsigned long max_length = is_abstract_ns ? sizeof(unix_addr->sun_path) : sizeof(unix_addr->sun_path) - 1; |
597 | | |
598 | | /* we need to be binary safe on systems that support an abstract |
599 | | * namespace */ |
600 | 0 | if (xparam->inputs.namelen > max_length) { |
601 | | /* On linux, when the path begins with a NUL byte we are |
602 | | * referring to an abstract namespace. In theory we should |
603 | | * allow an extra byte below, since we don't need the NULL. |
604 | | * BUT, to get into this branch of code, the name is too long, |
605 | | * so we don't care. */ |
606 | 0 | xparam->inputs.namelen = max_length; |
607 | 0 | php_error_docref(NULL, E_NOTICE, |
608 | 0 | "socket path exceeded the maximum allowed length of %lu bytes " |
609 | 0 | "and was truncated", max_length); |
610 | 0 | } |
611 | |
|
612 | 0 | memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen); |
613 | |
|
614 | 0 | return 1; |
615 | 0 | } |
616 | | #endif |
617 | | |
618 | | static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err) |
619 | 0 | { |
620 | 0 | char *colon; |
621 | 0 | char *host = NULL; |
622 | |
|
623 | 0 | #ifdef HAVE_IPV6 |
624 | 0 | char *p; |
625 | |
|
626 | 0 | if (*(str) == '[' && str_len > 1) { |
627 | | /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ |
628 | 0 | p = memchr(str + 1, ']', str_len - 2); |
629 | 0 | if (!p || *(p + 1) != ':') { |
630 | 0 | if (get_err) { |
631 | 0 | *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); |
632 | 0 | } |
633 | 0 | return NULL; |
634 | 0 | } |
635 | 0 | *portno = atoi(p + 2); |
636 | 0 | return estrndup(str + 1, p - str - 1); |
637 | 0 | } |
638 | 0 | #endif |
639 | 0 | if (str_len) { |
640 | 0 | colon = memchr(str, ':', str_len - 1); |
641 | 0 | } else { |
642 | 0 | colon = NULL; |
643 | 0 | } |
644 | 0 | if (colon) { |
645 | 0 | *portno = atoi(colon + 1); |
646 | 0 | host = estrndup(str, colon - str); |
647 | 0 | } else { |
648 | 0 | if (get_err) { |
649 | 0 | *err = strpprintf(0, "Failed to parse address \"%s\"", str); |
650 | 0 | } |
651 | 0 | return NULL; |
652 | 0 | } |
653 | | |
654 | 0 | return host; |
655 | 0 | } |
656 | | |
657 | | static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno) |
658 | 0 | { |
659 | 0 | return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text); |
660 | 0 | } |
661 | | |
662 | | static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock, |
663 | | php_stream_xport_param *xparam) |
664 | 0 | { |
665 | 0 | char *host = NULL; |
666 | 0 | int portno, err; |
667 | 0 | long sockopts = STREAM_SOCKOP_NONE; |
668 | 0 | zval *tmpzval = NULL; |
669 | |
|
670 | 0 | #ifdef AF_UNIX |
671 | 0 | if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { |
672 | 0 | struct sockaddr_un unix_addr; |
673 | |
|
674 | 0 | sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); |
675 | |
|
676 | 0 | if (sock->socket == SOCK_ERR) { |
677 | 0 | if (xparam->want_errortext) { |
678 | 0 | xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s", |
679 | 0 | stream->ops == &php_stream_unix_socket_ops ? "" : "datagram", |
680 | 0 | strerror(errno)); |
681 | 0 | } |
682 | 0 | return -1; |
683 | 0 | } |
684 | | |
685 | 0 | parse_unix_address(xparam, &unix_addr); |
686 | |
|
687 | 0 | return bind(sock->socket, (const struct sockaddr *)&unix_addr, |
688 | 0 | (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen); |
689 | 0 | } |
690 | 0 | #endif |
691 | | |
692 | 0 | host = parse_ip_address(xparam, &portno); |
693 | |
|
694 | 0 | if (host == NULL) { |
695 | 0 | return -1; |
696 | 0 | } |
697 | | |
698 | 0 | #ifdef IPV6_V6ONLY |
699 | 0 | if (PHP_STREAM_CONTEXT(stream) |
700 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL |
701 | 0 | && Z_TYPE_P(tmpzval) != IS_NULL |
702 | 0 | ) { |
703 | 0 | sockopts |= STREAM_SOCKOP_IPV6_V6ONLY; |
704 | 0 | sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval); |
705 | 0 | } |
706 | 0 | #endif |
707 | |
|
708 | 0 | #ifdef SO_REUSEPORT |
709 | 0 | if (PHP_STREAM_CONTEXT(stream) |
710 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL |
711 | 0 | && zend_is_true(tmpzval) |
712 | 0 | ) { |
713 | 0 | sockopts |= STREAM_SOCKOP_SO_REUSEPORT; |
714 | 0 | } |
715 | 0 | #endif |
716 | |
|
717 | 0 | #ifdef SO_BROADCAST |
718 | 0 | if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */ |
719 | 0 | && PHP_STREAM_CONTEXT(stream) |
720 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL |
721 | 0 | && zend_is_true(tmpzval) |
722 | 0 | ) { |
723 | 0 | sockopts |= STREAM_SOCKOP_SO_BROADCAST; |
724 | 0 | } |
725 | 0 | #endif |
726 | |
|
727 | 0 | sock->socket = php_network_bind_socket_to_local_addr(host, portno, |
728 | 0 | stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, |
729 | 0 | sockopts, |
730 | 0 | xparam->want_errortext ? &xparam->outputs.error_text : NULL, |
731 | 0 | &err |
732 | 0 | ); |
733 | |
|
734 | 0 | if (host) { |
735 | 0 | efree(host); |
736 | 0 | } |
737 | |
|
738 | 0 | return sock->socket == -1 ? -1 : 0; |
739 | 0 | } |
740 | | |
741 | | static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock, |
742 | | php_stream_xport_param *xparam) |
743 | 0 | { |
744 | 0 | char *host = NULL, *bindto = NULL; |
745 | 0 | int portno, bindport = 0; |
746 | 0 | int err = 0; |
747 | 0 | int ret; |
748 | 0 | zval *tmpzval = NULL; |
749 | 0 | long sockopts = STREAM_SOCKOP_NONE; |
750 | |
|
751 | 0 | #ifdef AF_UNIX |
752 | 0 | if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { |
753 | 0 | struct sockaddr_un unix_addr; |
754 | |
|
755 | 0 | sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); |
756 | |
|
757 | 0 | if (sock->socket == SOCK_ERR) { |
758 | 0 | if (xparam->want_errortext) { |
759 | 0 | xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket"); |
760 | 0 | } |
761 | 0 | return -1; |
762 | 0 | } |
763 | | |
764 | 0 | parse_unix_address(xparam, &unix_addr); |
765 | |
|
766 | 0 | ret = php_network_connect_socket(sock->socket, |
767 | 0 | (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen, |
768 | 0 | xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout, |
769 | 0 | xparam->want_errortext ? &xparam->outputs.error_text : NULL, |
770 | 0 | &err); |
771 | |
|
772 | 0 | xparam->outputs.error_code = err; |
773 | |
|
774 | 0 | goto out; |
775 | 0 | } |
776 | 0 | #endif |
777 | | |
778 | 0 | host = parse_ip_address(xparam, &portno); |
779 | |
|
780 | 0 | if (host == NULL) { |
781 | 0 | return -1; |
782 | 0 | } |
783 | | |
784 | 0 | if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) { |
785 | 0 | if (Z_TYPE_P(tmpzval) != IS_STRING) { |
786 | 0 | if (xparam->want_errortext) { |
787 | 0 | xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string."); |
788 | 0 | } |
789 | 0 | efree(host); |
790 | 0 | return -1; |
791 | 0 | } |
792 | 0 | bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text); |
793 | 0 | } |
794 | | |
795 | 0 | #ifdef SO_BROADCAST |
796 | 0 | if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */ |
797 | 0 | && PHP_STREAM_CONTEXT(stream) |
798 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL |
799 | 0 | && zend_is_true(tmpzval) |
800 | 0 | ) { |
801 | 0 | sockopts |= STREAM_SOCKOP_SO_BROADCAST; |
802 | 0 | } |
803 | 0 | #endif |
804 | |
|
805 | 0 | if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */ |
806 | 0 | #ifdef AF_UNIX |
807 | 0 | && stream->ops != &php_stream_unix_socket_ops |
808 | 0 | && stream->ops != &php_stream_unixdg_socket_ops |
809 | 0 | #endif |
810 | 0 | && PHP_STREAM_CONTEXT(stream) |
811 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL |
812 | 0 | && zend_is_true(tmpzval) |
813 | 0 | ) { |
814 | 0 | sockopts |= STREAM_SOCKOP_TCP_NODELAY; |
815 | 0 | } |
816 | | |
817 | | /* Note: the test here for php_stream_udp_socket_ops is important, because we |
818 | | * want the default to be TCP sockets so that the openssl extension can |
819 | | * re-use this code. */ |
820 | |
|
821 | 0 | sock->socket = php_network_connect_socket_to_host(host, portno, |
822 | 0 | stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, |
823 | 0 | xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, |
824 | 0 | xparam->inputs.timeout, |
825 | 0 | xparam->want_errortext ? &xparam->outputs.error_text : NULL, |
826 | 0 | &err, |
827 | 0 | bindto, |
828 | 0 | bindport, |
829 | 0 | sockopts |
830 | 0 | ); |
831 | |
|
832 | 0 | ret = sock->socket == -1 ? -1 : 0; |
833 | 0 | xparam->outputs.error_code = err; |
834 | |
|
835 | 0 | if (host) { |
836 | 0 | efree(host); |
837 | 0 | } |
838 | 0 | if (bindto) { |
839 | 0 | efree(bindto); |
840 | 0 | } |
841 | |
|
842 | 0 | #ifdef AF_UNIX |
843 | 0 | out: |
844 | 0 | #endif |
845 | |
|
846 | 0 | if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) { |
847 | | /* indicates pending connection */ |
848 | 0 | return 1; |
849 | 0 | } |
850 | | |
851 | 0 | return ret; |
852 | 0 | } |
853 | | |
854 | | static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock, |
855 | | php_stream_xport_param *xparam STREAMS_DC) |
856 | 0 | { |
857 | 0 | int clisock; |
858 | 0 | bool nodelay = 0; |
859 | 0 | zval *tmpzval = NULL; |
860 | |
|
861 | 0 | xparam->outputs.client = NULL; |
862 | |
|
863 | 0 | if ((NULL != PHP_STREAM_CONTEXT(stream)) && |
864 | 0 | (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL && |
865 | 0 | zend_is_true(tmpzval)) { |
866 | 0 | nodelay = 1; |
867 | 0 | } |
868 | |
|
869 | 0 | clisock = php_network_accept_incoming(sock->socket, |
870 | 0 | xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, |
871 | 0 | xparam->want_addr ? &xparam->outputs.addr : NULL, |
872 | 0 | xparam->want_addr ? &xparam->outputs.addrlen : NULL, |
873 | 0 | xparam->inputs.timeout, |
874 | 0 | xparam->want_errortext ? &xparam->outputs.error_text : NULL, |
875 | 0 | &xparam->outputs.error_code, |
876 | 0 | nodelay); |
877 | |
|
878 | 0 | if (clisock >= 0) { |
879 | 0 | php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata)); |
880 | |
|
881 | 0 | memcpy(clisockdata, sock, sizeof(*clisockdata)); |
882 | 0 | clisockdata->socket = clisock; |
883 | 0 | #ifdef __linux__ |
884 | | /* O_NONBLOCK is not inherited on Linux */ |
885 | 0 | clisockdata->is_blocked = 1; |
886 | 0 | #endif |
887 | |
|
888 | 0 | xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); |
889 | 0 | if (xparam->outputs.client) { |
890 | 0 | xparam->outputs.client->ctx = stream->ctx; |
891 | 0 | if (stream->ctx) { |
892 | 0 | GC_ADDREF(stream->ctx); |
893 | 0 | } |
894 | 0 | } |
895 | 0 | } |
896 | |
|
897 | 0 | return xparam->outputs.client == NULL ? -1 : 0; |
898 | 0 | } |
899 | | |
900 | | static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) |
901 | 0 | { |
902 | 0 | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
903 | 0 | php_stream_xport_param *xparam; |
904 | |
|
905 | 0 | switch(option) { |
906 | 0 | case PHP_STREAM_OPTION_XPORT_API: |
907 | 0 | xparam = (php_stream_xport_param *)ptrparam; |
908 | |
|
909 | 0 | switch(xparam->op) { |
910 | 0 | case STREAM_XPORT_OP_CONNECT: |
911 | 0 | case STREAM_XPORT_OP_CONNECT_ASYNC: |
912 | 0 | xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam); |
913 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
914 | | |
915 | 0 | case STREAM_XPORT_OP_BIND: |
916 | 0 | xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam); |
917 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
918 | | |
919 | | |
920 | 0 | case STREAM_XPORT_OP_ACCEPT: |
921 | 0 | xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC); |
922 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
923 | 0 | default: |
924 | | /* fall through */ |
925 | 0 | ; |
926 | 0 | } |
927 | 0 | } |
928 | 0 | return php_sockop_set_option(stream, option, value, ptrparam); |
929 | 0 | } |
930 | | |
931 | | |
932 | | PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen, |
933 | | const char *resourcename, size_t resourcenamelen, |
934 | | const char *persistent_id, int options, int flags, |
935 | | struct timeval *timeout, |
936 | | php_stream_context *context STREAMS_DC) |
937 | 0 | { |
938 | 0 | php_stream *stream = NULL; |
939 | 0 | php_netstream_data_t *sock; |
940 | 0 | const php_stream_ops *ops; |
941 | | |
942 | | /* which type of socket ? */ |
943 | 0 | if (strncmp(proto, "tcp", protolen) == 0) { |
944 | 0 | ops = &php_stream_socket_ops; |
945 | 0 | } else if (strncmp(proto, "udp", protolen) == 0) { |
946 | 0 | ops = &php_stream_udp_socket_ops; |
947 | 0 | } |
948 | 0 | #ifdef AF_UNIX |
949 | 0 | else if (strncmp(proto, "unix", protolen) == 0) { |
950 | 0 | ops = &php_stream_unix_socket_ops; |
951 | 0 | } else if (strncmp(proto, "udg", protolen) == 0) { |
952 | 0 | ops = &php_stream_unixdg_socket_ops; |
953 | 0 | } |
954 | 0 | #endif |
955 | 0 | else { |
956 | | /* should never happen */ |
957 | 0 | return NULL; |
958 | 0 | } |
959 | | |
960 | 0 | sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0); |
961 | 0 | memset(sock, 0, sizeof(php_netstream_data_t)); |
962 | |
|
963 | 0 | sock->is_blocked = 1; |
964 | 0 | sock->timeout.tv_sec = FG(default_socket_timeout); |
965 | 0 | sock->timeout.tv_usec = 0; |
966 | | |
967 | | /* we don't know the socket until we have determined if we are binding or |
968 | | * connecting */ |
969 | 0 | sock->socket = -1; |
970 | |
|
971 | 0 | stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+"); |
972 | |
|
973 | 0 | if (stream == NULL) { |
974 | 0 | pefree(sock, persistent_id ? 1 : 0); |
975 | 0 | return NULL; |
976 | 0 | } |
977 | | |
978 | 0 | return stream; |
979 | 0 | } |