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