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