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