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   | http://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 = 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 		if (NULL == (factory = zend_hash_str_find_ptr(&xport_hash, protocol, n))) {
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 			return NULL;
121 		}
122 	}
123 
124 	if (factory == NULL) {
125 		/* should never happen */
126 		php_error_docref(NULL, E_WARNING, "Could not find a factory !?");
127 		return NULL;
128 	}
129 
130 	stream = (factory)(protocol, n,
131 			(char*)name, namelen, persistent_id, options, flags, timeout,
132 			context STREAMS_REL_CC);
133 
134 	if (stream) {
135 		php_stream_context_set(stream, context);
136 
137 		if ((flags & STREAM_XPORT_SERVER) == 0) {
138 			/* client */
139 
140 			if (flags & (STREAM_XPORT_CONNECT|STREAM_XPORT_CONNECT_ASYNC)) {
141 				if (-1 == php_stream_xport_connect(stream, name, namelen,
142 							flags & STREAM_XPORT_CONNECT_ASYNC ? 1 : 0,
143 							timeout, &error_text, error_code)) {
144 
145 					ERR_RETURN(error_string, error_text, "connect() failed: %s");
146 
147 					failed = 1;
148 				}
149 			}
150 
151 		} else {
152 			/* server */
153 			if (flags & STREAM_XPORT_BIND) {
154 				if (0 != php_stream_xport_bind(stream, name, namelen, &error_text)) {
155 					ERR_RETURN(error_string, error_text, "bind() failed: %s");
156 					failed = 1;
157 				} else if (flags & STREAM_XPORT_LISTEN) {
158 					zval *zbacklog = NULL;
159 					int backlog = 32;
160 
161 					if (PHP_STREAM_CONTEXT(stream) && (zbacklog = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "backlog")) != NULL) {
162 						backlog = zval_get_long(zbacklog);
163 					}
164 
165 					if (0 != php_stream_xport_listen(stream, backlog, &error_text)) {
166 						ERR_RETURN(error_string, error_text, "listen() failed: %s");
167 						failed = 1;
168 					}
169 				}
170 			}
171 		}
172 	}
173 
174 	if (failed) {
175 		/* failure means that they don't get a stream to play with */
176 		if (persistent_id) {
177 			php_stream_pclose(stream);
178 		} else {
179 			php_stream_close(stream);
180 		}
181 		stream = NULL;
182 	}
183 
184 	return stream;
185 }
186 
187 /* Bind the stream to a local address */
php_stream_xport_bind(php_stream * stream,const char * name,size_t namelen,zend_string ** error_text)188 PHPAPI int php_stream_xport_bind(php_stream *stream,
189 		const char *name, size_t namelen,
190 		zend_string **error_text
191 		)
192 {
193 	php_stream_xport_param param;
194 	int ret;
195 
196 	memset(&param, 0, sizeof(param));
197 	param.op = STREAM_XPORT_OP_BIND;
198 	param.inputs.name = (char*)name;
199 	param.inputs.namelen = namelen;
200 	param.want_errortext = error_text ? 1 : 0;
201 
202 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
203 
204 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
205 		if (error_text) {
206 			*error_text = param.outputs.error_text;
207 		}
208 
209 		return param.outputs.returncode;
210 	}
211 
212 	return ret;
213 }
214 
215 /* 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)216 PHPAPI int php_stream_xport_connect(php_stream *stream,
217 		const char *name, size_t namelen,
218 		int asynchronous,
219 		struct timeval *timeout,
220 		zend_string **error_text,
221 		int *error_code
222 		)
223 {
224 	php_stream_xport_param param;
225 	int ret;
226 
227 	memset(&param, 0, sizeof(param));
228 	param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT;
229 	param.inputs.name = (char*)name;
230 	param.inputs.namelen = namelen;
231 	param.inputs.timeout = timeout;
232 
233 	param.want_errortext = error_text ? 1 : 0;
234 
235 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
236 
237 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
238 		if (error_text) {
239 			*error_text = param.outputs.error_text;
240 		}
241 		if (error_code) {
242 			*error_code = param.outputs.error_code;
243 		}
244 		return param.outputs.returncode;
245 	}
246 
247 	return ret;
248 
249 }
250 
251 /* Prepare to listen */
php_stream_xport_listen(php_stream * stream,int backlog,zend_string ** error_text)252 PHPAPI int php_stream_xport_listen(php_stream *stream, int backlog, zend_string **error_text)
253 {
254 	php_stream_xport_param param;
255 	int ret;
256 
257 	memset(&param, 0, sizeof(param));
258 	param.op = STREAM_XPORT_OP_LISTEN;
259 	param.inputs.backlog = backlog;
260 	param.want_errortext = error_text ? 1 : 0;
261 
262 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
263 
264 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
265 		if (error_text) {
266 			*error_text = param.outputs.error_text;
267 		}
268 
269 		return param.outputs.returncode;
270 	}
271 
272 	return ret;
273 }
274 
275 /* 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)276 PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client,
277 		zend_string **textaddr,
278 		void **addr, socklen_t *addrlen,
279 		struct timeval *timeout,
280 		zend_string **error_text
281 		)
282 {
283 	php_stream_xport_param param;
284 	int ret;
285 
286 	memset(&param, 0, sizeof(param));
287 
288 	param.op = STREAM_XPORT_OP_ACCEPT;
289 	param.inputs.timeout = timeout;
290 	param.want_addr = addr ? 1 : 0;
291 	param.want_textaddr = textaddr ? 1 : 0;
292 	param.want_errortext = error_text ? 1 : 0;
293 
294 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
295 
296 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
297 		*client = param.outputs.client;
298 		if (addr) {
299 			*addr = param.outputs.addr;
300 			*addrlen = param.outputs.addrlen;
301 		}
302 		if (textaddr) {
303 			*textaddr = param.outputs.textaddr;
304 		}
305 		if (error_text) {
306 			*error_text = param.outputs.error_text;
307 		}
308 
309 		return param.outputs.returncode;
310 	}
311 	return ret;
312 }
313 
php_stream_xport_get_name(php_stream * stream,int want_peer,zend_string ** textaddr,void ** addr,socklen_t * addrlen)314 PHPAPI int php_stream_xport_get_name(php_stream *stream, int want_peer,
315 		zend_string **textaddr,
316 		void **addr, socklen_t *addrlen
317 		)
318 {
319 	php_stream_xport_param param;
320 	int ret;
321 
322 	memset(&param, 0, sizeof(param));
323 
324 	param.op = want_peer ? STREAM_XPORT_OP_GET_PEER_NAME : STREAM_XPORT_OP_GET_NAME;
325 	param.want_addr = addr ? 1 : 0;
326 	param.want_textaddr = textaddr ? 1 : 0;
327 
328 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
329 
330 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
331 		if (addr) {
332 			*addr = param.outputs.addr;
333 			*addrlen = param.outputs.addrlen;
334 		}
335 		if (textaddr) {
336 			*textaddr = param.outputs.textaddr;
337 		}
338 
339 		return param.outputs.returncode;
340 	}
341 	return ret;
342 }
343 
php_stream_xport_crypto_setup(php_stream * stream,php_stream_xport_crypt_method_t crypto_method,php_stream * session_stream)344 PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream)
345 {
346 	php_stream_xport_crypto_param param;
347 	int ret;
348 
349 	memset(&param, 0, sizeof(param));
350 	param.op = STREAM_XPORT_CRYPTO_OP_SETUP;
351 	param.inputs.method = crypto_method;
352 	param.inputs.session = session_stream;
353 
354 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
355 
356 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
357 		return param.outputs.returncode;
358 	}
359 
360 	php_error_docref("streams.crypto", E_WARNING, "This stream does not support SSL/crypto");
361 
362 	return ret;
363 }
364 
php_stream_xport_crypto_enable(php_stream * stream,int activate)365 PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate)
366 {
367 	php_stream_xport_crypto_param param;
368 	int ret;
369 
370 	memset(&param, 0, sizeof(param));
371 	param.op = STREAM_XPORT_CRYPTO_OP_ENABLE;
372 	param.inputs.activate = activate;
373 
374 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
375 
376 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
377 		return param.outputs.returncode;
378 	}
379 
380 	php_error_docref("streams.crypto", E_WARNING, "This stream does not support SSL/crypto");
381 
382 	return ret;
383 }
384 
385 /* Similar to recv() system call; read data from the stream, optionally
386  * 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)387 PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t buflen,
388 		int flags, void **addr, socklen_t *addrlen, zend_string **textaddr
389 		)
390 {
391 	php_stream_xport_param param;
392 	int ret = 0;
393 	int recvd_len = 0;
394 #if 0
395 	int oob;
396 
397 	if (flags == 0 && addr == NULL) {
398 		return php_stream_read(stream, buf, buflen);
399 	}
400 
401 	if (stream->readfilters.head) {
402 		php_error_docref(NULL, E_WARNING, "Cannot peek or fetch OOB data from a filtered stream");
403 		return -1;
404 	}
405 
406 	oob = (flags & STREAM_OOB) == STREAM_OOB;
407 
408 	if (!oob && addr == NULL) {
409 		/* must be peeking at regular data; copy content from the buffer
410 		 * first, then adjust the pointer/len before handing off to the
411 		 * stream */
412 		recvd_len = stream->writepos - stream->readpos;
413 		if (recvd_len > buflen) {
414 			recvd_len = buflen;
415 		}
416 		if (recvd_len) {
417 			memcpy(buf, stream->readbuf, recvd_len);
418 			buf += recvd_len;
419 			buflen -= recvd_len;
420 		}
421 		/* if we filled their buffer, return */
422 		if (buflen == 0) {
423 			return recvd_len;
424 		}
425 	}
426 #endif
427 
428 	/* otherwise, we are going to bypass the buffer */
429 
430 	memset(&param, 0, sizeof(param));
431 
432 	param.op = STREAM_XPORT_OP_RECV;
433 	param.want_addr = addr ? 1 : 0;
434 	param.want_textaddr = textaddr ? 1 : 0;
435 	param.inputs.buf = buf;
436 	param.inputs.buflen = buflen;
437 	param.inputs.flags = flags;
438 
439 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
440 
441 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
442 		if (addr) {
443 			*addr = param.outputs.addr;
444 			*addrlen = param.outputs.addrlen;
445 		}
446 		if (textaddr) {
447 			*textaddr = param.outputs.textaddr;
448 		}
449 		return recvd_len + param.outputs.returncode;
450 	}
451 	return recvd_len ? recvd_len : -1;
452 }
453 
454 /* Similar to send() system call; send data to the stream, optionally
455  * 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)456 PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t buflen,
457 		int flags, void *addr, socklen_t addrlen)
458 {
459 	php_stream_xport_param param;
460 	int ret = 0;
461 	int oob;
462 
463 #if 0
464 	if (flags == 0 && addr == NULL) {
465 		return php_stream_write(stream, buf, buflen);
466 	}
467 #endif
468 
469 	oob = (flags & STREAM_OOB) == STREAM_OOB;
470 
471 	if ((oob || addr) && stream->writefilters.head) {
472 		php_error_docref(NULL, E_WARNING, "Cannot write OOB data, or data to a targeted address on a filtered stream");
473 		return -1;
474 	}
475 
476 	memset(&param, 0, sizeof(param));
477 
478 	param.op = STREAM_XPORT_OP_SEND;
479 	param.want_addr = addr ? 1 : 0;
480 	param.inputs.buf = (char*)buf;
481 	param.inputs.buflen = buflen;
482 	param.inputs.flags = flags;
483 	param.inputs.addr = addr;
484 	param.inputs.addrlen = addrlen;
485 
486 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
487 
488 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
489 		return param.outputs.returncode;
490 	}
491 	return -1;
492 }
493 
494 /* Similar to shutdown() system call; shut down part of a full-duplex
495  * connection */
php_stream_xport_shutdown(php_stream * stream,stream_shutdown_t how)496 PHPAPI int php_stream_xport_shutdown(php_stream *stream, stream_shutdown_t how)
497 {
498 	php_stream_xport_param param;
499 	int ret = 0;
500 
501 	memset(&param, 0, sizeof(param));
502 
503 	param.op = STREAM_XPORT_OP_SHUTDOWN;
504 	param.how = how;
505 
506 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
507 
508 	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
509 		return param.outputs.returncode;
510 	}
511 	return -1;
512 }
513