xref: /PHP-7.4/main/streams/transports.c (revision 92ac598a)
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 "php_streams_int.h"
21 #include "ext/standard/file.h"
22 
23 static HashTable xport_hash;
24 
php_stream_xport_get_hash(void)25 PHPAPI HashTable *php_stream_xport_get_hash(void)
26 {
27 	return &xport_hash;
28 }
29 
php_stream_xport_register(const char * protocol,php_stream_transport_factory factory)30 PHPAPI int php_stream_xport_register(const char *protocol, php_stream_transport_factory factory)
31 {
32 	zend_string *str = zend_string_init_interned(protocol, strlen(protocol), 1);
33 
34 	zend_hash_update_ptr(&xport_hash, str, factory);
35 	zend_string_release_ex(str, 1);
36 	return SUCCESS;
37 }
38 
php_stream_xport_unregister(const char * protocol)39 PHPAPI int php_stream_xport_unregister(const char *protocol)
40 {
41 	return zend_hash_str_del(&xport_hash, protocol, strlen(protocol));
42 }
43 
44 #define ERR_REPORT(out_err, fmt, arg) \
45 	if (out_err) { *out_err = strpprintf(0, fmt, arg); } \
46 	else { php_error_docref(NULL, E_WARNING, fmt, arg); }
47 
48 #define ERR_RETURN(out_err, local_err, fmt) \
49 	if (out_err) { *out_err = local_err; } \
50 	else { php_error_docref(NULL, E_WARNING, fmt, local_err ? ZSTR_VAL(local_err) : "Unspecified error"); \
51 		if (local_err) { zend_string_release_ex(local_err, 0); local_err = NULL; } \
52 	}
53 
_php_stream_xport_create(const char * name,size_t namelen,int options,int flags,const char * persistent_id,struct timeval * timeout,php_stream_context * context,zend_string ** error_string,int * error_code STREAMS_DC)54 PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, int options,
55 		int flags, const char *persistent_id,
56 		struct timeval *timeout,
57 		php_stream_context *context,
58 		zend_string **error_string,
59 		int *error_code
60 		STREAMS_DC)
61 {
62 	php_stream *stream = NULL;
63 	php_stream_transport_factory factory = NULL;
64 	const char *p, *protocol = NULL;
65 	size_t n = 0;
66 	int failed = 0;
67 	zend_string *error_text = NULL;
68 	struct timeval default_timeout = { 0, 0 };
69 
70 	default_timeout.tv_sec = FG(default_socket_timeout);
71 
72 	if (timeout == NULL) {
73 		timeout = &default_timeout;
74 	}
75 
76 	/* check for a cached persistent socket */
77 	if (persistent_id) {
78 		switch(php_stream_from_persistent_id(persistent_id, &stream)) {
79 			case PHP_STREAM_PERSISTENT_SUCCESS:
80 				/* use a 0 second timeout when checking if the socket
81 				 * has already died */
82 				if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS, 0, NULL)) {
83 					return stream;
84 				}
85 				/* dead - kill it */
86 				php_stream_pclose(stream);
87 				stream = NULL;
88 
89 				/* fall through */
90 
91 			case PHP_STREAM_PERSISTENT_FAILURE:
92 			default:
93 				/* failed; get a new one */
94 				;
95 		}
96 	}
97 
98 	for (p = name; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
99 		n++;
100 	}
101 
102 	if ((*p == ':') && (n > 1) && !strncmp("://", p, 3)) {
103 		protocol = name;
104 		name = p + 3;
105 		namelen -= n + 3;
106 	} else {
107 		protocol = "tcp";
108 		n = 3;
109 	}
110 
111 	if (protocol) {
112 		if (NULL == (factory = zend_hash_str_find_ptr(&xport_hash, protocol, n))) {
113 			char wrapper_name[32];
114 
115 			if (n >= sizeof(wrapper_name))
116 				n = sizeof(wrapper_name) - 1;
117 			PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
118 
119 			ERR_REPORT(error_string, "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?",
120 					wrapper_name);
121 
122 			return NULL;
123 		}
124 	}
125 
126 	if (factory == NULL) {
127 		/* should never happen */
128 		php_error_docref(NULL, E_WARNING, "Could not find a factory !?");
129 		return NULL;
130 	}
131 
132 	stream = (factory)(protocol, n,
133 			(char*)name, namelen, persistent_id, options, flags, timeout,
134 			context STREAMS_REL_CC);
135 
136 	if (stream) {
137 		php_stream_context_set(stream, context);
138 
139 		if ((flags & STREAM_XPORT_SERVER) == 0) {
140 			/* client */
141 
142 			if (flags & (STREAM_XPORT_CONNECT|STREAM_XPORT_CONNECT_ASYNC)) {
143 				if (-1 == php_stream_xport_connect(stream, name, namelen,
144 							flags & STREAM_XPORT_CONNECT_ASYNC ? 1 : 0,
145 							timeout, &error_text, error_code)) {
146 
147 					ERR_RETURN(error_string, error_text, "connect() failed: %s");
148 
149 					failed = 1;
150 				}
151 			}
152 
153 		} else {
154 			/* server */
155 			if (flags & STREAM_XPORT_BIND) {
156 				if (0 != php_stream_xport_bind(stream, name, namelen, &error_text)) {
157 					ERR_RETURN(error_string, error_text, "bind() failed: %s");
158 					failed = 1;
159 				} else if (flags & STREAM_XPORT_LISTEN) {
160 					zval *zbacklog = NULL;
161 					int backlog = 32;
162 
163 					if (PHP_STREAM_CONTEXT(stream) && (zbacklog = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "backlog")) != NULL) {
164 						backlog = zval_get_long(zbacklog);
165 					}
166 
167 					if (0 != php_stream_xport_listen(stream, backlog, &error_text)) {
168 						ERR_RETURN(error_string, error_text, "listen() failed: %s");
169 						failed = 1;
170 					}
171 				}
172 			}
173 		}
174 	}
175 
176 	if (failed) {
177 		/* failure means that they don't get a stream to play with */
178 		if (persistent_id) {
179 			php_stream_pclose(stream);
180 		} else {
181 			php_stream_close(stream);
182 		}
183 		stream = NULL;
184 	}
185 
186 	return stream;
187 }
188 
189 /* Bind the stream to a local address */
php_stream_xport_bind(php_stream * stream,const char * name,size_t namelen,zend_string ** error_text)190 PHPAPI int php_stream_xport_bind(php_stream *stream,
191 		const char *name, size_t namelen,
192 		zend_string **error_text
193 		)
194 {
195 	php_stream_xport_param param;
196 	int ret;
197 
198 	memset(&param, 0, sizeof(param));
199 	param.op = STREAM_XPORT_OP_BIND;
200 	param.inputs.name = (char*)name;
201 	param.inputs.namelen = namelen;
202 	param.want_errortext = error_text ? 1 : 0;
203 
204 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
205 
206 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
207 		if (error_text) {
208 			*error_text = param.outputs.error_text;
209 		}
210 
211 		return param.outputs.returncode;
212 	}
213 
214 	return ret;
215 }
216 
217 /* Connect to a remote address */
php_stream_xport_connect(php_stream * stream,const char * name,size_t namelen,int asynchronous,struct timeval * timeout,zend_string ** error_text,int * error_code)218 PHPAPI int php_stream_xport_connect(php_stream *stream,
219 		const char *name, size_t namelen,
220 		int asynchronous,
221 		struct timeval *timeout,
222 		zend_string **error_text,
223 		int *error_code
224 		)
225 {
226 	php_stream_xport_param param;
227 	int ret;
228 
229 	memset(&param, 0, sizeof(param));
230 	param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT;
231 	param.inputs.name = (char*)name;
232 	param.inputs.namelen = namelen;
233 	param.inputs.timeout = timeout;
234 
235 	param.want_errortext = error_text ? 1 : 0;
236 
237 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
238 
239 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
240 		if (error_text) {
241 			*error_text = param.outputs.error_text;
242 		}
243 		if (error_code) {
244 			*error_code = param.outputs.error_code;
245 		}
246 		return param.outputs.returncode;
247 	}
248 
249 	return ret;
250 
251 }
252 
253 /* Prepare to listen */
php_stream_xport_listen(php_stream * stream,int backlog,zend_string ** error_text)254 PHPAPI int php_stream_xport_listen(php_stream *stream, int backlog, zend_string **error_text)
255 {
256 	php_stream_xport_param param;
257 	int ret;
258 
259 	memset(&param, 0, sizeof(param));
260 	param.op = STREAM_XPORT_OP_LISTEN;
261 	param.inputs.backlog = backlog;
262 	param.want_errortext = error_text ? 1 : 0;
263 
264 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
265 
266 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
267 		if (error_text) {
268 			*error_text = param.outputs.error_text;
269 		}
270 
271 		return param.outputs.returncode;
272 	}
273 
274 	return ret;
275 }
276 
277 /* Get the next client and their address (as a string) */
php_stream_xport_accept(php_stream * stream,php_stream ** client,zend_string ** textaddr,void ** addr,socklen_t * addrlen,struct timeval * timeout,zend_string ** error_text)278 PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client,
279 		zend_string **textaddr,
280 		void **addr, socklen_t *addrlen,
281 		struct timeval *timeout,
282 		zend_string **error_text
283 		)
284 {
285 	php_stream_xport_param param;
286 	int ret;
287 
288 	memset(&param, 0, sizeof(param));
289 
290 	param.op = STREAM_XPORT_OP_ACCEPT;
291 	param.inputs.timeout = timeout;
292 	param.want_addr = addr ? 1 : 0;
293 	param.want_textaddr = textaddr ? 1 : 0;
294 	param.want_errortext = error_text ? 1 : 0;
295 
296 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
297 
298 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
299 		*client = param.outputs.client;
300 		if (addr) {
301 			*addr = param.outputs.addr;
302 			*addrlen = param.outputs.addrlen;
303 		}
304 		if (textaddr) {
305 			*textaddr = param.outputs.textaddr;
306 		}
307 		if (error_text) {
308 			*error_text = param.outputs.error_text;
309 		}
310 
311 		return param.outputs.returncode;
312 	}
313 	return ret;
314 }
315 
php_stream_xport_get_name(php_stream * stream,int want_peer,zend_string ** textaddr,void ** addr,socklen_t * addrlen)316 PHPAPI int php_stream_xport_get_name(php_stream *stream, int want_peer,
317 		zend_string **textaddr,
318 		void **addr, socklen_t *addrlen
319 		)
320 {
321 	php_stream_xport_param param;
322 	int ret;
323 
324 	memset(&param, 0, sizeof(param));
325 
326 	param.op = want_peer ? STREAM_XPORT_OP_GET_PEER_NAME : STREAM_XPORT_OP_GET_NAME;
327 	param.want_addr = addr ? 1 : 0;
328 	param.want_textaddr = textaddr ? 1 : 0;
329 
330 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
331 
332 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
333 		if (addr) {
334 			*addr = param.outputs.addr;
335 			*addrlen = param.outputs.addrlen;
336 		}
337 		if (textaddr) {
338 			*textaddr = param.outputs.textaddr;
339 		}
340 
341 		return param.outputs.returncode;
342 	}
343 	return ret;
344 }
345 
php_stream_xport_crypto_setup(php_stream * stream,php_stream_xport_crypt_method_t crypto_method,php_stream * session_stream)346 PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream)
347 {
348 	php_stream_xport_crypto_param param;
349 	int ret;
350 
351 	memset(&param, 0, sizeof(param));
352 	param.op = STREAM_XPORT_CRYPTO_OP_SETUP;
353 	param.inputs.method = crypto_method;
354 	param.inputs.session = session_stream;
355 
356 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
357 
358 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
359 		return param.outputs.returncode;
360 	}
361 
362 	php_error_docref("streams.crypto", E_WARNING, "this stream does not support SSL/crypto");
363 
364 	return ret;
365 }
366 
php_stream_xport_crypto_enable(php_stream * stream,int activate)367 PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate)
368 {
369 	php_stream_xport_crypto_param param;
370 	int ret;
371 
372 	memset(&param, 0, sizeof(param));
373 	param.op = STREAM_XPORT_CRYPTO_OP_ENABLE;
374 	param.inputs.activate = activate;
375 
376 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
377 
378 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
379 		return param.outputs.returncode;
380 	}
381 
382 	php_error_docref("streams.crypto", E_WARNING, "this stream does not support SSL/crypto");
383 
384 	return ret;
385 }
386 
387 /* Similar to recv() system call; read data from the stream, optionally
388  * peeking, optionally retrieving OOB data */
php_stream_xport_recvfrom(php_stream * stream,char * buf,size_t buflen,int flags,void ** addr,socklen_t * addrlen,zend_string ** textaddr)389 PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t buflen,
390 		int flags, void **addr, socklen_t *addrlen, zend_string **textaddr
391 		)
392 {
393 	php_stream_xport_param param;
394 	int ret = 0;
395 	int recvd_len = 0;
396 #if 0
397 	int oob;
398 
399 	if (flags == 0 && addr == NULL) {
400 		return php_stream_read(stream, buf, buflen);
401 	}
402 
403 	if (stream->readfilters.head) {
404 		php_error_docref(NULL, E_WARNING, "cannot peek or fetch OOB data from a filtered stream");
405 		return -1;
406 	}
407 
408 	oob = (flags & STREAM_OOB) == STREAM_OOB;
409 
410 	if (!oob && addr == NULL) {
411 		/* must be peeking at regular data; copy content from the buffer
412 		 * first, then adjust the pointer/len before handing off to the
413 		 * stream */
414 		recvd_len = stream->writepos - stream->readpos;
415 		if (recvd_len > buflen) {
416 			recvd_len = buflen;
417 		}
418 		if (recvd_len) {
419 			memcpy(buf, stream->readbuf, recvd_len);
420 			buf += recvd_len;
421 			buflen -= recvd_len;
422 		}
423 		/* if we filled their buffer, return */
424 		if (buflen == 0) {
425 			return recvd_len;
426 		}
427 	}
428 #endif
429 
430 	/* otherwise, we are going to bypass the buffer */
431 
432 	memset(&param, 0, sizeof(param));
433 
434 	param.op = STREAM_XPORT_OP_RECV;
435 	param.want_addr = addr ? 1 : 0;
436 	param.want_textaddr = textaddr ? 1 : 0;
437 	param.inputs.buf = buf;
438 	param.inputs.buflen = buflen;
439 	param.inputs.flags = flags;
440 
441 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
442 
443 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
444 		if (addr) {
445 			*addr = param.outputs.addr;
446 			*addrlen = param.outputs.addrlen;
447 		}
448 		if (textaddr) {
449 			*textaddr = param.outputs.textaddr;
450 		}
451 		return recvd_len + param.outputs.returncode;
452 	}
453 	return recvd_len ? recvd_len : -1;
454 }
455 
456 /* Similar to send() system call; send data to the stream, optionally
457  * sending it as OOB data */
php_stream_xport_sendto(php_stream * stream,const char * buf,size_t buflen,int flags,void * addr,socklen_t addrlen)458 PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t buflen,
459 		int flags, void *addr, socklen_t addrlen)
460 {
461 	php_stream_xport_param param;
462 	int ret = 0;
463 	int oob;
464 
465 #if 0
466 	if (flags == 0 && addr == NULL) {
467 		return php_stream_write(stream, buf, buflen);
468 	}
469 #endif
470 
471 	oob = (flags & STREAM_OOB) == STREAM_OOB;
472 
473 	if ((oob || addr) && stream->writefilters.head) {
474 		php_error_docref(NULL, E_WARNING, "cannot write OOB data, or data to a targeted address on a filtered stream");
475 		return -1;
476 	}
477 
478 	memset(&param, 0, sizeof(param));
479 
480 	param.op = STREAM_XPORT_OP_SEND;
481 	param.want_addr = addr ? 1 : 0;
482 	param.inputs.buf = (char*)buf;
483 	param.inputs.buflen = buflen;
484 	param.inputs.flags = flags;
485 	param.inputs.addr = addr;
486 	param.inputs.addrlen = addrlen;
487 
488 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
489 
490 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
491 		return param.outputs.returncode;
492 	}
493 	return -1;
494 }
495 
496 /* Similar to shutdown() system call; shut down part of a full-duplex
497  * connection */
php_stream_xport_shutdown(php_stream * stream,stream_shutdown_t how)498 PHPAPI int php_stream_xport_shutdown(php_stream *stream, stream_shutdown_t how)
499 {
500 	php_stream_xport_param param;
501 	int ret = 0;
502 
503 	memset(&param, 0, sizeof(param));
504 
505 	param.op = STREAM_XPORT_OP_SHUTDOWN;
506 	param.how = how;
507 
508 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
509 
510 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
511 		return param.outputs.returncode;
512 	}
513 	return -1;
514 }
515