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