xref: /PHP-7.4/main/streams/xp_socket.c (revision dfb3a799)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) The PHP Group                                          |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author: Wez Furlong <wez@thebrainroom.com>                           |
16   +----------------------------------------------------------------------+
17 */
18 
19 #include "php.h"
20 #include "ext/standard/file.h"
21 #include "streams/php_streams_int.h"
22 #include "php_network.h"
23 
24 #if defined(PHP_WIN32) || defined(__riscos__)
25 # undef AF_UNIX
26 #endif
27 
28 #if defined(AF_UNIX)
29 #include <sys/un.h>
30 #endif
31 
32 #ifndef MSG_DONTWAIT
33 # define MSG_DONTWAIT 0
34 #endif
35 
36 #ifndef MSG_PEEK
37 # define MSG_PEEK 0
38 #endif
39 
40 #ifdef PHP_WIN32
41 /* send/recv family on windows expects int */
42 # define XP_SOCK_BUF_SIZE(sz) (((sz) > INT_MAX) ? INT_MAX : (int)(sz))
43 #else
44 # define XP_SOCK_BUF_SIZE(sz) (sz)
45 #endif
46 
47 const php_stream_ops php_stream_generic_socket_ops;
48 PHPAPI const php_stream_ops php_stream_socket_ops;
49 const php_stream_ops php_stream_udp_socket_ops;
50 #ifdef AF_UNIX
51 const php_stream_ops php_stream_unix_socket_ops;
52 const php_stream_ops php_stream_unixdg_socket_ops;
53 #endif
54 
55 
56 static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam);
57 
58 /* {{{ Generic socket stream operations */
php_sockop_write(php_stream * stream,const char * buf,size_t count)59 static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t count)
60 {
61 	php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
62 	ssize_t didwrite;
63 	struct timeval *ptimeout;
64 
65 	if (!sock || sock->socket == -1) {
66 		return 0;
67 	}
68 
69 	if (sock->timeout.tv_sec == -1)
70 		ptimeout = NULL;
71 	else
72 		ptimeout = &sock->timeout;
73 
74 retry:
75 	didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0);
76 
77 	if (didwrite <= 0) {
78 		char *estr;
79 		int err = php_socket_errno();
80 		if (err == EWOULDBLOCK || err == EAGAIN) {
81 			if (sock->is_blocked) {
82 				int retval;
83 
84 				sock->timeout_event = 0;
85 
86 				do {
87 					retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout);
88 
89 					if (retval == 0) {
90 						sock->timeout_event = 1;
91 						break;
92 					}
93 
94 					if (retval > 0) {
95 						/* writable now; retry */
96 						goto retry;
97 					}
98 
99 					err = php_socket_errno();
100 				} while (err == EINTR);
101 			} else {
102 				/* EWOULDBLOCK/EAGAIN is not an error for a non-blocking stream.
103 				 * Report zero byte write instead. */
104 				return 0;
105 			}
106 		}
107 
108 		estr = php_socket_strerror(err, NULL, 0);
109 		php_error_docref(NULL, E_NOTICE, "send of " ZEND_LONG_FMT " bytes failed with errno=%d %s",
110 				(zend_long)count, err, estr);
111 		efree(estr);
112 	}
113 
114 	if (didwrite > 0) {
115 		php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0);
116 	}
117 
118 	return didwrite;
119 }
120 
php_sock_stream_wait_for_data(php_stream * stream,php_netstream_data_t * sock)121 static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock)
122 {
123 	int retval;
124 	struct timeval *ptimeout;
125 
126 	if (!sock || sock->socket == -1) {
127 		return;
128 	}
129 
130 	sock->timeout_event = 0;
131 
132 	if (sock->timeout.tv_sec == -1)
133 		ptimeout = NULL;
134 	else
135 		ptimeout = &sock->timeout;
136 
137 	while(1) {
138 		retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout);
139 
140 		if (retval == 0)
141 			sock->timeout_event = 1;
142 
143 		if (retval >= 0)
144 			break;
145 
146 		if (php_socket_errno() != EINTR)
147 			break;
148 	}
149 }
150 
php_sockop_read(php_stream * stream,char * buf,size_t count)151 static ssize_t php_sockop_read(php_stream *stream, char *buf, size_t count)
152 {
153 	php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
154 	ssize_t nr_bytes = 0;
155 	int err;
156 
157 	if (!sock || sock->socket == -1) {
158 		return -1;
159 	}
160 
161 	if (sock->is_blocked) {
162 		php_sock_stream_wait_for_data(stream, sock);
163 		if (sock->timeout_event)
164 			return 0;
165 	}
166 
167 	nr_bytes = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && sock->timeout.tv_sec != -1) ? MSG_DONTWAIT : 0);
168 	err = php_socket_errno();
169 
170 	if (nr_bytes < 0) {
171 		if (err == EAGAIN || err == EWOULDBLOCK) {
172 			nr_bytes = 0;
173 		} else {
174 			stream->eof = 1;
175 		}
176 	} else if (nr_bytes == 0) {
177 		stream->eof = 1;
178 	}
179 
180 	if (nr_bytes > 0) {
181 		php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0);
182 	}
183 
184 	return nr_bytes;
185 }
186 
187 
php_sockop_close(php_stream * stream,int close_handle)188 static int php_sockop_close(php_stream *stream, int close_handle)
189 {
190 	php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
191 #ifdef PHP_WIN32
192 	int n;
193 #endif
194 
195 	if (!sock) {
196 		return 0;
197 	}
198 
199 	if (close_handle) {
200 
201 #ifdef PHP_WIN32
202 		if (sock->socket == -1)
203 			sock->socket = SOCK_ERR;
204 #endif
205 		if (sock->socket != SOCK_ERR) {
206 #ifdef PHP_WIN32
207 			/* prevent more data from coming in */
208 			shutdown(sock->socket, SHUT_RD);
209 
210 			/* try to make sure that the OS sends all data before we close the connection.
211 			 * Essentially, we are waiting for the socket to become writeable, which means
212 			 * that all pending data has been sent.
213 			 * We use a small timeout which should encourage the OS to send the data,
214 			 * but at the same time avoid hanging indefinitely.
215 			 * */
216 			do {
217 				n = php_pollfd_for_ms(sock->socket, POLLOUT, 500);
218 			} while (n == -1 && php_socket_errno() == EINTR);
219 #endif
220 			closesocket(sock->socket);
221 			sock->socket = SOCK_ERR;
222 		}
223 
224 	}
225 
226 	pefree(sock, php_stream_is_persistent(stream));
227 
228 	return 0;
229 }
230 
php_sockop_flush(php_stream * stream)231 static int php_sockop_flush(php_stream *stream)
232 {
233 #if 0
234 	php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
235 	return fsync(sock->socket);
236 #endif
237 	return 0;
238 }
239 
php_sockop_stat(php_stream * stream,php_stream_statbuf * ssb)240 static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb)
241 {
242 #if ZEND_WIN32
243 	return 0;
244 #else
245 	php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
246 
247 	return zend_fstat(sock->socket, &ssb->sb);
248 #endif
249 }
250 
sock_sendto(php_netstream_data_t * sock,const char * buf,size_t buflen,int flags,struct sockaddr * addr,socklen_t addrlen)251 static inline int sock_sendto(php_netstream_data_t *sock, const char *buf, size_t buflen, int flags,
252 		struct sockaddr *addr, socklen_t addrlen
253 		)
254 {
255 	int ret;
256 	if (addr) {
257 		ret = sendto(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, addr, XP_SOCK_BUF_SIZE(addrlen));
258 
259 		return (ret == SOCK_CONN_ERR) ? -1 : ret;
260 	}
261 #ifdef PHP_WIN32
262 	return ((ret = send(sock->socket, buf, buflen > INT_MAX ? INT_MAX : (int)buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret;
263 #else
264 	return ((ret = send(sock->socket, buf, buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret;
265 #endif
266 }
267 
sock_recvfrom(php_netstream_data_t * sock,char * buf,size_t buflen,int flags,zend_string ** textaddr,struct sockaddr ** addr,socklen_t * addrlen)268 static inline int sock_recvfrom(php_netstream_data_t *sock, char *buf, size_t buflen, int flags,
269 		zend_string **textaddr,
270 		struct sockaddr **addr, socklen_t *addrlen
271 		)
272 {
273 	int ret;
274 	int want_addr = textaddr || addr;
275 
276 	if (want_addr) {
277 		php_sockaddr_storage sa;
278 		socklen_t sl = sizeof(sa);
279 		ret = recvfrom(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, (struct sockaddr*)&sa, &sl);
280 		ret = (ret == SOCK_CONN_ERR) ? -1 : ret;
281 #ifdef PHP_WIN32
282 		/* POSIX discards excess bytes without signalling failure; emulate this on Windows */
283 		if (ret == -1 && WSAGetLastError() == WSAEMSGSIZE) {
284 			ret = buflen;
285 		}
286 #endif
287 		if (sl) {
288 			php_network_populate_name_from_sockaddr((struct sockaddr*)&sa, sl,
289 					textaddr, addr, addrlen);
290 		} else {
291 			if (textaddr) {
292 				*textaddr = ZSTR_EMPTY_ALLOC();
293 			}
294 			if (addr) {
295 				*addr = NULL;
296 				*addrlen = 0;
297 			}
298 		}
299 	} else {
300 		ret = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags);
301 		ret = (ret == SOCK_CONN_ERR) ? -1 : ret;
302 	}
303 
304 	return ret;
305 }
306 
php_sockop_set_option(php_stream * stream,int option,int value,void * ptrparam)307 static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
308 {
309 	int oldmode, flags;
310 	php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
311 	php_stream_xport_param *xparam;
312 
313 	if (!sock) {
314 		return PHP_STREAM_OPTION_RETURN_NOTIMPL;
315 	}
316 
317 	switch(option) {
318 		case PHP_STREAM_OPTION_CHECK_LIVENESS:
319 			{
320 				struct timeval tv;
321 				char buf;
322 				int alive = 1;
323 
324 				if (value == -1) {
325 					if (sock->timeout.tv_sec == -1) {
326 						tv.tv_sec = FG(default_socket_timeout);
327 						tv.tv_usec = 0;
328 					} else {
329 						tv = sock->timeout;
330 					}
331 				} else {
332 					tv.tv_sec = value;
333 					tv.tv_usec = 0;
334 				}
335 
336 				if (sock->socket == -1) {
337 					alive = 0;
338 				} else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) {
339 #ifdef PHP_WIN32
340 					int ret;
341 #else
342 					ssize_t ret;
343 #endif
344 					int err;
345 
346 					ret = recv(sock->socket, &buf, sizeof(buf), MSG_PEEK);
347 					err = php_socket_errno();
348 					if (0 == ret || /* the counterpart did properly shutdown*/
349 						(0 > ret && err != EWOULDBLOCK && err != EAGAIN && err != EMSGSIZE)) { /* there was an unrecoverable error */
350 						alive = 0;
351 					}
352 				}
353 				return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
354 			}
355 
356 		case PHP_STREAM_OPTION_BLOCKING:
357 			oldmode = sock->is_blocked;
358 			if (SUCCESS == php_set_sock_blocking(sock->socket, value)) {
359 				sock->is_blocked = value;
360 				return oldmode;
361 			}
362 			return PHP_STREAM_OPTION_RETURN_ERR;
363 
364 		case PHP_STREAM_OPTION_READ_TIMEOUT:
365 			sock->timeout = *(struct timeval*)ptrparam;
366 			sock->timeout_event = 0;
367 			return PHP_STREAM_OPTION_RETURN_OK;
368 
369 		case PHP_STREAM_OPTION_META_DATA_API:
370 			add_assoc_bool((zval *)ptrparam, "timed_out", sock->timeout_event);
371 			add_assoc_bool((zval *)ptrparam, "blocked", sock->is_blocked);
372 			add_assoc_bool((zval *)ptrparam, "eof", stream->eof);
373 			return PHP_STREAM_OPTION_RETURN_OK;
374 
375 		case PHP_STREAM_OPTION_XPORT_API:
376 			xparam = (php_stream_xport_param *)ptrparam;
377 
378 			switch (xparam->op) {
379 				case STREAM_XPORT_OP_LISTEN:
380 					xparam->outputs.returncode = (listen(sock->socket, xparam->inputs.backlog) == 0) ?  0: -1;
381 					return PHP_STREAM_OPTION_RETURN_OK;
382 
383 				case STREAM_XPORT_OP_GET_NAME:
384 					xparam->outputs.returncode = php_network_get_sock_name(sock->socket,
385 							xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
386 							xparam->want_addr ? &xparam->outputs.addr : NULL,
387 							xparam->want_addr ? &xparam->outputs.addrlen : NULL
388 							);
389 					return PHP_STREAM_OPTION_RETURN_OK;
390 
391 				case STREAM_XPORT_OP_GET_PEER_NAME:
392 					xparam->outputs.returncode = php_network_get_peer_name(sock->socket,
393 							xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
394 							xparam->want_addr ? &xparam->outputs.addr : NULL,
395 							xparam->want_addr ? &xparam->outputs.addrlen : NULL
396 							);
397 					return PHP_STREAM_OPTION_RETURN_OK;
398 
399 				case STREAM_XPORT_OP_SEND:
400 					flags = 0;
401 					if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) {
402 						flags |= MSG_OOB;
403 					}
404 					xparam->outputs.returncode = sock_sendto(sock,
405 							xparam->inputs.buf, xparam->inputs.buflen,
406 							flags,
407 							xparam->inputs.addr,
408 							xparam->inputs.addrlen);
409 					if (xparam->outputs.returncode == -1) {
410 						char *err = php_socket_strerror(php_socket_errno(), NULL, 0);
411 						php_error_docref(NULL, E_WARNING,
412 						   	"%s\n", err);
413 						efree(err);
414 					}
415 					return PHP_STREAM_OPTION_RETURN_OK;
416 
417 				case STREAM_XPORT_OP_RECV:
418 					flags = 0;
419 					if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) {
420 						flags |= MSG_OOB;
421 					}
422 					if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) {
423 						flags |= MSG_PEEK;
424 					}
425 					xparam->outputs.returncode = sock_recvfrom(sock,
426 							xparam->inputs.buf, xparam->inputs.buflen,
427 							flags,
428 							xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
429 							xparam->want_addr ? &xparam->outputs.addr : NULL,
430 							xparam->want_addr ? &xparam->outputs.addrlen : NULL
431 							);
432 					return PHP_STREAM_OPTION_RETURN_OK;
433 
434 
435 #ifdef HAVE_SHUTDOWN
436 # ifndef SHUT_RD
437 #  define SHUT_RD 0
438 # endif
439 # ifndef SHUT_WR
440 #  define SHUT_WR 1
441 # endif
442 # ifndef SHUT_RDWR
443 #  define SHUT_RDWR 2
444 # endif
445 				case STREAM_XPORT_OP_SHUTDOWN: {
446 					static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR};
447 
448 					xparam->outputs.returncode = shutdown(sock->socket, shutdown_how[xparam->how]);
449 					return PHP_STREAM_OPTION_RETURN_OK;
450 				}
451 #endif
452 
453 				default:
454 					return PHP_STREAM_OPTION_RETURN_NOTIMPL;
455 			}
456 
457 		default:
458 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
459 	}
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 
844 		xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+");
845 		if (xparam->outputs.client) {
846 			xparam->outputs.client->ctx = stream->ctx;
847 			if (stream->ctx) {
848 				GC_ADDREF(stream->ctx);
849 			}
850 		}
851 	}
852 
853 	return xparam->outputs.client == NULL ? -1 : 0;
854 }
855 
php_tcp_sockop_set_option(php_stream * stream,int option,int value,void * ptrparam)856 static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
857 {
858 	php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
859 	php_stream_xport_param *xparam;
860 
861 	switch(option) {
862 		case PHP_STREAM_OPTION_XPORT_API:
863 			xparam = (php_stream_xport_param *)ptrparam;
864 
865 			switch(xparam->op) {
866 				case STREAM_XPORT_OP_CONNECT:
867 				case STREAM_XPORT_OP_CONNECT_ASYNC:
868 					xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam);
869 					return PHP_STREAM_OPTION_RETURN_OK;
870 
871 				case STREAM_XPORT_OP_BIND:
872 					xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam);
873 					return PHP_STREAM_OPTION_RETURN_OK;
874 
875 
876 				case STREAM_XPORT_OP_ACCEPT:
877 					xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC);
878 					return PHP_STREAM_OPTION_RETURN_OK;
879 				default:
880 					/* fall through */
881 					;
882 			}
883 	}
884 	return php_sockop_set_option(stream, option, value, ptrparam);
885 }
886 
887 
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)888 PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen,
889 		const char *resourcename, size_t resourcenamelen,
890 		const char *persistent_id, int options, int flags,
891 		struct timeval *timeout,
892 		php_stream_context *context STREAMS_DC)
893 {
894 	php_stream *stream = NULL;
895 	php_netstream_data_t *sock;
896 	const php_stream_ops *ops;
897 
898 	/* which type of socket ? */
899 	if (strncmp(proto, "tcp", protolen) == 0) {
900 		ops = &php_stream_socket_ops;
901 	} else if (strncmp(proto, "udp", protolen) == 0) {
902 		ops = &php_stream_udp_socket_ops;
903 	}
904 #ifdef AF_UNIX
905 	else if (strncmp(proto, "unix", protolen) == 0) {
906 		ops = &php_stream_unix_socket_ops;
907 	} else if (strncmp(proto, "udg", protolen) == 0) {
908 		ops = &php_stream_unixdg_socket_ops;
909 	}
910 #endif
911 	else {
912 		/* should never happen */
913 		return NULL;
914 	}
915 
916 	sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0);
917 	memset(sock, 0, sizeof(php_netstream_data_t));
918 
919 	sock->is_blocked = 1;
920 	sock->timeout.tv_sec = FG(default_socket_timeout);
921 	sock->timeout.tv_usec = 0;
922 
923 	/* we don't know the socket until we have determined if we are binding or
924 	 * connecting */
925 	sock->socket = -1;
926 
927 	stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+");
928 
929 	if (stream == NULL)	{
930 		pefree(sock, persistent_id ? 1 : 0);
931 		return NULL;
932 	}
933 
934 	if (flags == 0) {
935 		return stream;
936 	}
937 
938 	return stream;
939 }
940