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