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