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