/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_io.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 | case PHP_STREAM_AS_FD_FOR_COPY: |
527 | 0 | if (ret) { |
528 | 0 | php_io_fd *copy_fd = (php_io_fd *) ret; |
529 | 0 | copy_fd->socket = sock->socket; |
530 | 0 | copy_fd->fd_type = PHP_IO_FD_SOCKET; |
531 | 0 | copy_fd->timeout = sock->timeout; |
532 | 0 | copy_fd->is_blocked = sock->is_blocked; |
533 | 0 | } |
534 | 0 | return SUCCESS; |
535 | 0 | default: |
536 | 0 | return FAILURE; |
537 | 0 | } |
538 | 0 | } |
539 | | |
540 | | /* These may look identical, but we need them this way so that |
541 | | * we can determine which type of socket we are dealing with |
542 | | * by inspecting stream->ops. |
543 | | * A "useful" side-effect is that the user's scripts can then |
544 | | * make similar decisions using stream_get_meta_data. |
545 | | * */ |
546 | | const php_stream_ops php_stream_generic_socket_ops = { |
547 | | php_sockop_write, php_sockop_read, |
548 | | php_sockop_close, php_sockop_flush, |
549 | | "generic_socket", |
550 | | NULL, /* seek */ |
551 | | php_sockop_cast, |
552 | | php_sockop_stat, |
553 | | php_sockop_set_option, |
554 | | }; |
555 | | |
556 | | |
557 | | const php_stream_ops php_stream_socket_ops = { |
558 | | php_sockop_write, php_sockop_read, |
559 | | php_sockop_close, php_sockop_flush, |
560 | | "tcp_socket", |
561 | | NULL, /* seek */ |
562 | | php_sockop_cast, |
563 | | php_sockop_stat, |
564 | | php_tcp_sockop_set_option, |
565 | | }; |
566 | | |
567 | | static const php_stream_ops php_stream_udp_socket_ops = { |
568 | | php_sockop_write, php_sockop_read, |
569 | | php_sockop_close, php_sockop_flush, |
570 | | "udp_socket", |
571 | | NULL, /* seek */ |
572 | | php_sockop_cast, |
573 | | php_sockop_stat, |
574 | | php_tcp_sockop_set_option, |
575 | | }; |
576 | | |
577 | | #ifdef AF_UNIX |
578 | | static const php_stream_ops php_stream_unix_socket_ops = { |
579 | | php_sockop_write, php_sockop_read, |
580 | | php_sockop_close, php_sockop_flush, |
581 | | "unix_socket", |
582 | | NULL, /* seek */ |
583 | | php_sockop_cast, |
584 | | php_sockop_stat, |
585 | | php_tcp_sockop_set_option, |
586 | | }; |
587 | | static const php_stream_ops php_stream_unixdg_socket_ops = { |
588 | | php_sockop_write, php_sockop_read, |
589 | | php_sockop_close, php_sockop_flush, |
590 | | "udg_socket", |
591 | | NULL, /* seek */ |
592 | | php_sockop_cast, |
593 | | php_sockop_stat, |
594 | | php_tcp_sockop_set_option, |
595 | | }; |
596 | | #endif |
597 | | |
598 | | |
599 | | /* network socket operations */ |
600 | | |
601 | | #ifdef AF_UNIX |
602 | | static inline int parse_unix_address(php_stream *stream, php_stream_xport_param *xparam, |
603 | | struct sockaddr_un *unix_addr) |
604 | 0 | { |
605 | 0 | memset(unix_addr, 0, sizeof(*unix_addr)); |
606 | 0 | unix_addr->sun_family = AF_UNIX; |
607 | | |
608 | | /* Abstract namespace does not need to be NUL-terminated, while path-based |
609 | | * sockets should be. */ |
610 | 0 | bool is_abstract_ns = xparam->inputs.namelen > 0 && xparam->inputs.name[0] == '\0'; |
611 | 0 | unsigned long max_length = is_abstract_ns ? sizeof(unix_addr->sun_path) : sizeof(unix_addr->sun_path) - 1; |
612 | | |
613 | | /* we need to be binary safe on systems that support an abstract |
614 | | * namespace */ |
615 | 0 | if (xparam->inputs.namelen > max_length) { |
616 | | /* On linux, when the path begins with a NUL byte we are |
617 | | * referring to an abstract namespace. In theory we should |
618 | | * allow an extra byte below, since we don't need the NULL. |
619 | | * BUT, to get into this branch of code, the name is too long, |
620 | | * so we don't care. */ |
621 | 0 | xparam->inputs.namelen = max_length; |
622 | 0 | php_stream_notice(stream, InvalidPath, |
623 | 0 | "socket path exceeded the maximum allowed length of %lu bytes and was truncated", |
624 | 0 | max_length); |
625 | 0 | } |
626 | |
|
627 | 0 | memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen); |
628 | |
|
629 | 0 | return 1; |
630 | 0 | } |
631 | | #endif |
632 | | |
633 | | static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err) |
634 | 0 | { |
635 | 0 | const char *colon; |
636 | 0 | char *host = NULL; |
637 | |
|
638 | 0 | if (memchr(str, '\0', str_len)) { |
639 | 0 | *err = ZSTR_INIT_LITERAL("The hostname must not contain null bytes", 0); |
640 | 0 | return NULL; |
641 | 0 | } |
642 | | |
643 | 0 | #ifdef HAVE_IPV6 |
644 | 0 | if (*(str) == '[' && str_len > 1) { |
645 | | /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ |
646 | 0 | const char *p = memchr(str + 1, ']', str_len - 2); |
647 | 0 | if (!p || *(p + 1) != ':') { |
648 | 0 | if (get_err) { |
649 | 0 | *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); |
650 | 0 | } |
651 | 0 | return NULL; |
652 | 0 | } |
653 | 0 | *portno = atoi(p + 2); |
654 | 0 | return estrndup(str + 1, p - str - 1); |
655 | 0 | } |
656 | 0 | #endif |
657 | 0 | if (str_len) { |
658 | 0 | colon = memchr(str, ':', str_len - 1); |
659 | 0 | } else { |
660 | 0 | colon = NULL; |
661 | 0 | } |
662 | 0 | if (colon) { |
663 | 0 | *portno = atoi(colon + 1); |
664 | 0 | host = estrndup(str, colon - str); |
665 | 0 | } else { |
666 | 0 | if (get_err) { |
667 | 0 | *err = strpprintf(0, "Failed to parse address \"%s\"", str); |
668 | 0 | } |
669 | 0 | return NULL; |
670 | 0 | } |
671 | | |
672 | 0 | return host; |
673 | 0 | } |
674 | | |
675 | | static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno) |
676 | 0 | { |
677 | 0 | return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text); |
678 | 0 | } |
679 | | |
680 | | static int php_sockop_parse_buffer_sizes(php_stream *stream, php_stream_xport_param *xparam, |
681 | | php_sockvals *sockvals) |
682 | 0 | { |
683 | 0 | zval *tmpzval; |
684 | |
|
685 | 0 | if (!PHP_STREAM_CONTEXT(stream)) { |
686 | 0 | return 0; |
687 | 0 | } |
688 | | |
689 | 0 | #ifdef SO_RCVBUF |
690 | 0 | if ((tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_rcvbuf")) != NULL) { |
691 | 0 | zend_long bufsize = zval_get_long(tmpzval); |
692 | |
|
693 | 0 | if (bufsize < 1 || bufsize > INT_MAX) { |
694 | 0 | if (xparam->want_errortext) { |
695 | 0 | xparam->outputs.error_text = strpprintf(0, "so_rcvbuf context option must be between 1 and %d", INT_MAX); |
696 | 0 | } |
697 | 0 | return -1; |
698 | 0 | } |
699 | | |
700 | 0 | sockvals->mask |= PHP_SOCKVAL_SO_RCVBUF; |
701 | 0 | sockvals->rcvbuf = (int) bufsize; |
702 | 0 | } |
703 | 0 | #endif |
704 | | |
705 | 0 | #ifdef SO_SNDBUF |
706 | 0 | if ((tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_sndbuf")) != NULL) { |
707 | 0 | zend_long bufsize = zval_get_long(tmpzval); |
708 | |
|
709 | 0 | if (bufsize < 1 || bufsize > INT_MAX) { |
710 | 0 | if (xparam->want_errortext) { |
711 | 0 | xparam->outputs.error_text = strpprintf(0, "so_sndbuf context option must be between 1 and %d", INT_MAX); |
712 | 0 | } |
713 | 0 | return -1; |
714 | 0 | } |
715 | | |
716 | 0 | sockvals->mask |= PHP_SOCKVAL_SO_SNDBUF; |
717 | 0 | sockvals->sndbuf = (int) bufsize; |
718 | 0 | } |
719 | 0 | #endif |
720 | | |
721 | 0 | return 0; |
722 | 0 | } |
723 | | |
724 | | static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock, |
725 | | php_stream_xport_param *xparam) |
726 | 0 | { |
727 | 0 | char *host = NULL; |
728 | 0 | int portno, err; |
729 | 0 | long sockopts = STREAM_SOCKOP_NONE; |
730 | 0 | zval *tmpzval = NULL; |
731 | 0 | php_sockvals sockvals = {0}; |
732 | |
|
733 | 0 | #ifdef AF_UNIX |
734 | 0 | if (PHP_STREAM_XPORT_IS_UNIX(stream)) { |
735 | 0 | struct sockaddr_un unix_addr; |
736 | |
|
737 | 0 | sock->socket = socket(PF_UNIX, PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? SOCK_STREAM : SOCK_DGRAM, 0); |
738 | |
|
739 | 0 | if (sock->socket == SOCK_ERR) { |
740 | 0 | if (xparam->want_errortext) { |
741 | 0 | char errstr[256]; |
742 | 0 | xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s", |
743 | 0 | PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? "" : " datagram", |
744 | 0 | php_socket_strerror_s(errno, errstr, sizeof(errstr))); |
745 | 0 | } |
746 | 0 | return -1; |
747 | 0 | } |
748 | | |
749 | 0 | parse_unix_address(stream, xparam, &unix_addr); |
750 | |
|
751 | 0 | int result = bind(sock->socket, (const struct sockaddr *)&unix_addr, |
752 | 0 | (socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen); |
753 | 0 | if (result == -1 && xparam->want_errortext) { |
754 | 0 | char errstr[256]; |
755 | 0 | xparam->outputs.error_text = strpprintf(0, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); |
756 | 0 | } |
757 | 0 | return result; |
758 | 0 | } |
759 | 0 | #endif |
760 | | |
761 | 0 | host = parse_ip_address(xparam, &portno); |
762 | |
|
763 | 0 | if (host == NULL) { |
764 | 0 | return -1; |
765 | 0 | } |
766 | | |
767 | 0 | if (php_sockop_parse_buffer_sizes(stream, xparam, &sockvals) == -1) { |
768 | 0 | efree(host); |
769 | 0 | return -1; |
770 | 0 | } |
771 | | |
772 | 0 | #ifdef IPV6_V6ONLY |
773 | 0 | if (PHP_STREAM_CONTEXT(stream) |
774 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL |
775 | 0 | && Z_TYPE_P(tmpzval) != IS_NULL |
776 | 0 | ) { |
777 | 0 | sockopts |= STREAM_SOCKOP_IPV6_V6ONLY; |
778 | 0 | sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval); |
779 | 0 | } |
780 | 0 | #endif |
781 | |
|
782 | 0 | #ifdef SO_REUSEPORT |
783 | 0 | if (PHP_STREAM_CONTEXT(stream) |
784 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL |
785 | 0 | && zend_is_true(tmpzval) |
786 | 0 | ) { |
787 | 0 | sockopts |= STREAM_SOCKOP_SO_REUSEPORT; |
788 | 0 | } |
789 | 0 | #endif |
790 | |
|
791 | 0 | #ifdef SO_REUSEADDR |
792 | | /* SO_REUSEADDR is enabled by default so this option is just to disable it if set to false. */ |
793 | 0 | if (!PHP_STREAM_CONTEXT(stream) |
794 | 0 | || (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseaddr")) == NULL |
795 | 0 | || zend_is_true(tmpzval) |
796 | 0 | ) { |
797 | 0 | sockopts |= STREAM_SOCKOP_SO_REUSEADDR; |
798 | 0 | } |
799 | 0 | #endif |
800 | |
|
801 | 0 | #ifdef SO_BROADCAST |
802 | 0 | if (PHP_STREAM_XPORT_IS_UDP(stream) /* SO_BROADCAST is only applicable for UDP */ |
803 | 0 | && PHP_STREAM_CONTEXT(stream) |
804 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL |
805 | 0 | && zend_is_true(tmpzval) |
806 | 0 | ) { |
807 | 0 | sockopts |= STREAM_SOCKOP_SO_BROADCAST; |
808 | 0 | } |
809 | 0 | #endif |
810 | |
|
811 | 0 | #ifdef SO_LINGER |
812 | 0 | if (PHP_STREAM_XPORT_IS_TCP(stream) |
813 | 0 | && PHP_STREAM_CONTEXT(stream) |
814 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL |
815 | 0 | ) { |
816 | 0 | sockvals.mask |= PHP_SOCKVAL_SO_LINGER; |
817 | 0 | sockvals.linger = (int)zval_get_long(tmpzval); |
818 | 0 | } |
819 | 0 | #endif |
820 | |
|
821 | 0 | #ifdef SO_KEEPALIVE |
822 | 0 | if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */ |
823 | 0 | && PHP_STREAM_CONTEXT(stream) |
824 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_keepalive")) != NULL |
825 | 0 | && zend_is_true(tmpzval) |
826 | 0 | ) { |
827 | 0 | sockopts |= STREAM_SOCKOP_SO_KEEPALIVE; |
828 | 0 | } |
829 | 0 | #endif |
830 | | |
831 | | /* Parse TCP keepalive parameters - only for TCP streams */ |
832 | 0 | if (PHP_STREAM_XPORT_IS_TCP(stream)) { |
833 | 0 | #if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE) |
834 | 0 | if (PHP_STREAM_CONTEXT(stream) |
835 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle")) != NULL |
836 | 0 | ) { |
837 | 0 | sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE; |
838 | 0 | sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval); |
839 | 0 | } |
840 | 0 | #endif |
841 | |
|
842 | 0 | #ifdef TCP_KEEPINTVL |
843 | 0 | if (PHP_STREAM_CONTEXT(stream) |
844 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepintvl")) != NULL |
845 | 0 | ) { |
846 | 0 | sockvals.mask |= PHP_SOCKVAL_TCP_KEEPINTVL; |
847 | 0 | sockvals.keepalive.keepintvl = (int)zval_get_long(tmpzval); |
848 | 0 | } |
849 | 0 | #endif |
850 | |
|
851 | 0 | #ifdef TCP_KEEPCNT |
852 | 0 | if (PHP_STREAM_CONTEXT(stream) |
853 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepcnt")) != NULL |
854 | 0 | ) { |
855 | 0 | sockvals.mask |= PHP_SOCKVAL_TCP_KEEPCNT; |
856 | 0 | sockvals.keepalive.keepcnt = (int)zval_get_long(tmpzval); |
857 | 0 | } |
858 | 0 | #endif |
859 | 0 | } |
860 | |
|
861 | 0 | sock->socket = php_network_bind_socket_to_local_addr_ex(host, portno, |
862 | 0 | PHP_STREAM_XPORT_IS_UDP(stream) ? SOCK_DGRAM : SOCK_STREAM, |
863 | 0 | sockopts, |
864 | 0 | sockvals.mask ? &sockvals : NULL, |
865 | 0 | xparam->want_errortext ? &xparam->outputs.error_text : NULL, |
866 | 0 | &err |
867 | 0 | ); |
868 | |
|
869 | 0 | if (host) { |
870 | 0 | efree(host); |
871 | 0 | } |
872 | |
|
873 | 0 | return sock->socket == -1 ? -1 : 0; |
874 | 0 | } |
875 | | |
876 | | static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock, |
877 | | php_stream_xport_param *xparam) |
878 | 0 | { |
879 | 0 | char *host = NULL, *bindto = NULL; |
880 | 0 | int portno, bindport = 0; |
881 | 0 | int err = 0; |
882 | 0 | int ret; |
883 | 0 | zval *tmpzval = NULL; |
884 | 0 | long sockopts = STREAM_SOCKOP_NONE; |
885 | 0 | php_sockvals sockvals = {0}; |
886 | |
|
887 | 0 | #ifdef AF_UNIX |
888 | 0 | if (PHP_STREAM_XPORT_IS_UNIX(stream)) { |
889 | 0 | struct sockaddr_un unix_addr; |
890 | |
|
891 | 0 | sock->socket = socket(PF_UNIX, PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? SOCK_STREAM : SOCK_DGRAM, 0); |
892 | |
|
893 | 0 | if (sock->socket == SOCK_ERR) { |
894 | 0 | if (xparam->want_errortext) { |
895 | 0 | xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket"); |
896 | 0 | } |
897 | 0 | return -1; |
898 | 0 | } |
899 | | |
900 | 0 | parse_unix_address(stream, xparam, &unix_addr); |
901 | |
|
902 | 0 | ret = php_network_connect_socket(sock->socket, |
903 | 0 | (const struct sockaddr *)&unix_addr, (socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen, |
904 | 0 | xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout, |
905 | 0 | xparam->want_errortext ? &xparam->outputs.error_text : NULL, |
906 | 0 | &err); |
907 | |
|
908 | 0 | xparam->outputs.error_code = err; |
909 | |
|
910 | 0 | goto out; |
911 | 0 | } |
912 | 0 | #endif |
913 | | |
914 | 0 | host = parse_ip_address(xparam, &portno); |
915 | |
|
916 | 0 | if (host == NULL) { |
917 | 0 | return -1; |
918 | 0 | } |
919 | | |
920 | 0 | if (php_sockop_parse_buffer_sizes(stream, xparam, &sockvals) == -1) { |
921 | 0 | efree(host); |
922 | 0 | return -1; |
923 | 0 | } |
924 | | |
925 | 0 | if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) { |
926 | 0 | if (Z_TYPE_P(tmpzval) != IS_STRING) { |
927 | 0 | if (xparam->want_errortext) { |
928 | 0 | xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string."); |
929 | 0 | } |
930 | 0 | efree(host); |
931 | 0 | return -1; |
932 | 0 | } |
933 | 0 | bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text); |
934 | 0 | } |
935 | | |
936 | 0 | #ifdef SO_BROADCAST |
937 | 0 | if (PHP_STREAM_XPORT_IS_UDP(stream) /* SO_BROADCAST is only applicable for UDP */ |
938 | 0 | && PHP_STREAM_CONTEXT(stream) |
939 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL |
940 | 0 | && zend_is_true(tmpzval) |
941 | 0 | ) { |
942 | 0 | sockopts |= STREAM_SOCKOP_SO_BROADCAST; |
943 | 0 | } |
944 | 0 | #endif |
945 | |
|
946 | 0 | if (PHP_STREAM_XPORT_IS_TCP(stream) /* TCP_NODELAY is only applicable for TCP */ |
947 | 0 | && PHP_STREAM_CONTEXT(stream) |
948 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL |
949 | 0 | && zend_is_true(tmpzval) |
950 | 0 | ) { |
951 | 0 | sockopts |= STREAM_SOCKOP_TCP_NODELAY; |
952 | 0 | } |
953 | |
|
954 | 0 | #ifdef SO_LINGER |
955 | 0 | if (PHP_STREAM_XPORT_IS_TCP(stream) |
956 | 0 | && PHP_STREAM_CONTEXT(stream) |
957 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL |
958 | 0 | ) { |
959 | 0 | sockvals.mask |= PHP_SOCKVAL_SO_LINGER; |
960 | 0 | sockvals.linger = (int)zval_get_long(tmpzval); |
961 | 0 | } |
962 | 0 | #endif |
963 | |
|
964 | 0 | #ifdef SO_KEEPALIVE |
965 | 0 | if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */ |
966 | 0 | && PHP_STREAM_CONTEXT(stream) |
967 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_keepalive")) != NULL |
968 | 0 | && zend_is_true(tmpzval) |
969 | 0 | ) { |
970 | 0 | sockopts |= STREAM_SOCKOP_SO_KEEPALIVE; |
971 | 0 | } |
972 | 0 | #endif |
973 | | |
974 | | /* Parse TCP keepalive parameters - only for TCP streams */ |
975 | 0 | if (PHP_STREAM_XPORT_IS_TCP(stream)) { |
976 | 0 | #if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE) |
977 | 0 | if (PHP_STREAM_CONTEXT(stream) |
978 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle")) != NULL |
979 | 0 | ) { |
980 | 0 | sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE; |
981 | 0 | sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval); |
982 | 0 | } |
983 | 0 | #endif |
984 | |
|
985 | 0 | #ifdef TCP_KEEPINTVL |
986 | 0 | if (PHP_STREAM_CONTEXT(stream) |
987 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepintvl")) != NULL |
988 | 0 | ) { |
989 | 0 | sockvals.mask |= PHP_SOCKVAL_TCP_KEEPINTVL; |
990 | 0 | sockvals.keepalive.keepintvl = (int)zval_get_long(tmpzval); |
991 | 0 | } |
992 | 0 | #endif |
993 | |
|
994 | 0 | #ifdef TCP_KEEPCNT |
995 | 0 | if (PHP_STREAM_CONTEXT(stream) |
996 | 0 | && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepcnt")) != NULL |
997 | 0 | ) { |
998 | 0 | sockvals.mask |= PHP_SOCKVAL_TCP_KEEPCNT; |
999 | 0 | sockvals.keepalive.keepcnt = (int)zval_get_long(tmpzval); |
1000 | 0 | } |
1001 | 0 | #endif |
1002 | 0 | } |
1003 | | |
1004 | | /* Note: the test here for php_stream_udp_socket_ops is important, because we |
1005 | | * want the default to be TCP sockets so that the openssl extension can |
1006 | | * re-use this code. */ |
1007 | |
|
1008 | 0 | sock->socket = php_network_connect_socket_to_host_ex(host, portno, |
1009 | 0 | PHP_STREAM_XPORT_IS_UDP(stream) ? SOCK_DGRAM : SOCK_STREAM, |
1010 | 0 | xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, |
1011 | 0 | xparam->inputs.timeout, |
1012 | 0 | xparam->want_errortext ? &xparam->outputs.error_text : NULL, |
1013 | 0 | &err, |
1014 | 0 | bindto, |
1015 | 0 | bindport, |
1016 | 0 | sockopts, |
1017 | 0 | sockvals.mask ? &sockvals : NULL |
1018 | 0 | ); |
1019 | |
|
1020 | 0 | ret = sock->socket == -1 ? -1 : 0; |
1021 | 0 | xparam->outputs.error_code = err; |
1022 | |
|
1023 | 0 | if (host) { |
1024 | 0 | efree(host); |
1025 | 0 | } |
1026 | 0 | if (bindto) { |
1027 | 0 | efree(bindto); |
1028 | 0 | } |
1029 | |
|
1030 | 0 | #ifdef AF_UNIX |
1031 | 0 | out: |
1032 | 0 | #endif |
1033 | |
|
1034 | 0 | if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) { |
1035 | | /* indicates pending connection */ |
1036 | 0 | return 1; |
1037 | 0 | } |
1038 | | |
1039 | 0 | return ret; |
1040 | 0 | } |
1041 | | |
1042 | | static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock, |
1043 | | php_stream_xport_param *xparam STREAMS_DC) |
1044 | 0 | { |
1045 | 0 | php_sockvals sockvals = {0}; |
1046 | 0 | zval *tmpzval = NULL; |
1047 | |
|
1048 | 0 | xparam->outputs.client = NULL; |
1049 | |
|
1050 | 0 | if (PHP_STREAM_CONTEXT(stream)) { |
1051 | 0 | tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay"); |
1052 | 0 | if (tmpzval != NULL && zend_is_true(tmpzval)) { |
1053 | 0 | sockvals.mask |= PHP_SOCKVAL_TCP_NODELAY; |
1054 | 0 | sockvals.tcp_nodelay = 1; |
1055 | 0 | } |
1056 | 0 | tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle"); |
1057 | 0 | if (tmpzval != NULL) { |
1058 | 0 | sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE; |
1059 | 0 | sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval); |
1060 | 0 | } |
1061 | 0 | } |
1062 | |
|
1063 | 0 | php_socket_t clisock = php_network_accept_incoming_ex(sock->socket, |
1064 | 0 | xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, |
1065 | 0 | xparam->want_addr ? &xparam->outputs.addr : NULL, |
1066 | 0 | xparam->want_addr ? &xparam->outputs.addrlen : NULL, |
1067 | 0 | xparam->inputs.timeout, |
1068 | 0 | xparam->want_errortext ? &xparam->outputs.error_text : NULL, |
1069 | 0 | &xparam->outputs.error_code, |
1070 | 0 | &sockvals); |
1071 | |
|
1072 | 0 | if (clisock != SOCK_ERR) { |
1073 | 0 | php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata)); |
1074 | |
|
1075 | 0 | memcpy(clisockdata, sock, sizeof(*clisockdata)); |
1076 | 0 | clisockdata->socket = clisock; |
1077 | 0 | #ifdef __linux__ |
1078 | | /* O_NONBLOCK is not inherited on Linux */ |
1079 | 0 | clisockdata->is_blocked = true; |
1080 | 0 | #endif |
1081 | |
|
1082 | 0 | xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); |
1083 | 0 | if (xparam->outputs.client) { |
1084 | 0 | xparam->outputs.client->ctx = stream->ctx; |
1085 | 0 | if (stream->ctx) { |
1086 | 0 | GC_ADDREF(stream->ctx); |
1087 | 0 | } |
1088 | 0 | } |
1089 | 0 | } |
1090 | |
|
1091 | 0 | return xparam->outputs.client == NULL ? -1 : 0; |
1092 | 0 | } |
1093 | | |
1094 | | static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) |
1095 | 0 | { |
1096 | 0 | php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
1097 | 0 | php_stream_xport_param *xparam; |
1098 | |
|
1099 | 0 | switch(option) { |
1100 | 0 | case PHP_STREAM_OPTION_XPORT_API: |
1101 | 0 | xparam = (php_stream_xport_param *)ptrparam; |
1102 | |
|
1103 | 0 | switch(xparam->op) { |
1104 | 0 | case STREAM_XPORT_OP_CONNECT: |
1105 | 0 | case STREAM_XPORT_OP_CONNECT_ASYNC: |
1106 | 0 | xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam); |
1107 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
1108 | | |
1109 | 0 | case STREAM_XPORT_OP_BIND: |
1110 | 0 | xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam); |
1111 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
1112 | | |
1113 | | |
1114 | 0 | case STREAM_XPORT_OP_ACCEPT: |
1115 | 0 | xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC); |
1116 | 0 | return PHP_STREAM_OPTION_RETURN_OK; |
1117 | 0 | default: |
1118 | | /* fall through */ |
1119 | 0 | ; |
1120 | 0 | } |
1121 | 0 | } |
1122 | 0 | return php_sockop_set_option(stream, option, value, ptrparam); |
1123 | 0 | } |
1124 | | |
1125 | | |
1126 | | PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen, |
1127 | | const char *resourcename, size_t resourcenamelen, |
1128 | | const char *persistent_id, int options, int flags, |
1129 | | struct timeval *timeout, |
1130 | | php_stream_context *context STREAMS_DC) |
1131 | 0 | { |
1132 | 0 | php_stream *stream = NULL; |
1133 | 0 | php_netstream_data_t *sock; |
1134 | 0 | const php_stream_ops *ops; |
1135 | | |
1136 | | /* which type of socket ? */ |
1137 | 0 | if (strncmp(proto, "tcp", protolen) == 0) { |
1138 | 0 | ops = &php_stream_socket_ops; |
1139 | 0 | } else if (strncmp(proto, "udp", protolen) == 0) { |
1140 | 0 | ops = &php_stream_udp_socket_ops; |
1141 | 0 | } |
1142 | 0 | #ifdef AF_UNIX |
1143 | 0 | else if (strncmp(proto, "unix", protolen) == 0) { |
1144 | 0 | ops = &php_stream_unix_socket_ops; |
1145 | 0 | } else if (strncmp(proto, "udg", protolen) == 0) { |
1146 | 0 | ops = &php_stream_unixdg_socket_ops; |
1147 | 0 | } |
1148 | 0 | #endif |
1149 | 0 | else { |
1150 | | /* should never happen */ |
1151 | 0 | return NULL; |
1152 | 0 | } |
1153 | | |
1154 | 0 | sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0); |
1155 | 0 | memset(sock, 0, sizeof(php_netstream_data_t)); |
1156 | |
|
1157 | 0 | sock->is_blocked = true; |
1158 | 0 | sock->timeout.tv_sec = FG(default_socket_timeout); |
1159 | 0 | sock->timeout.tv_usec = 0; |
1160 | | |
1161 | | /* we don't know the socket until we have determined if we are binding or |
1162 | | * connecting */ |
1163 | 0 | sock->socket = -1; |
1164 | |
|
1165 | 0 | stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+"); |
1166 | |
|
1167 | 0 | if (stream == NULL) { |
1168 | 0 | pefree(sock, persistent_id ? 1 : 0); |
1169 | 0 | return NULL; |
1170 | 0 | } |
1171 | | |
1172 | 0 | return stream; |
1173 | 0 | } |