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 = 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 for (p = name; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
98 n++;
99 }
100
101 if ((*p == ':') && (n > 1) && !strncmp("://", p, 3)) {
102 protocol = name;
103 name = p + 3;
104 namelen -= n + 3;
105 } else {
106 protocol = "tcp";
107 n = 3;
108 }
109
110 if (protocol) {
111 if (NULL == (factory = zend_hash_str_find_ptr(&xport_hash, protocol, 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 return NULL;
122 }
123 }
124
125 if (factory == NULL) {
126 /* should never happen */
127 php_error_docref(NULL, E_WARNING, "Could not find a factory !?");
128 return NULL;
129 }
130
131 stream = (factory)(protocol, n,
132 (char*)name, namelen, persistent_id, options, flags, timeout,
133 context STREAMS_REL_CC);
134
135 if (stream) {
136 zend_try {
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 = true;
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 = true;
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 = true;
170 }
171 }
172 if (!failed) {
173 stream->flags |= PHP_STREAM_FLAG_NO_IO;
174 }
175 }
176 }
177 } zend_catch {
178 bailout = true;
179 } zend_end_try();
180 }
181
182 if (failed || bailout) {
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 if (bailout) {
191 zend_bailout();
192 }
193 }
194
195 return stream;
196 }
197
198 /* Bind the stream to a local address */
php_stream_xport_bind(php_stream * stream,const char * name,size_t namelen,zend_string ** error_text)199 PHPAPI int php_stream_xport_bind(php_stream *stream,
200 const char *name, size_t namelen,
201 zend_string **error_text
202 )
203 {
204 php_stream_xport_param param;
205 int ret;
206
207 memset(¶m, 0, sizeof(param));
208 param.op = STREAM_XPORT_OP_BIND;
209 param.inputs.name = (char*)name;
210 param.inputs.namelen = namelen;
211 param.want_errortext = error_text ? 1 : 0;
212
213 ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m);
214
215 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
216 if (error_text) {
217 *error_text = param.outputs.error_text;
218 }
219
220 return param.outputs.returncode;
221 }
222
223 return ret;
224 }
225
226 /* 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)227 PHPAPI int php_stream_xport_connect(php_stream *stream,
228 const char *name, size_t namelen,
229 int asynchronous,
230 struct timeval *timeout,
231 zend_string **error_text,
232 int *error_code
233 )
234 {
235 php_stream_xport_param param;
236 int ret;
237
238 memset(¶m, 0, sizeof(param));
239 param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT;
240 param.inputs.name = (char*)name;
241 param.inputs.namelen = namelen;
242 param.inputs.timeout = timeout;
243
244 param.want_errortext = error_text ? 1 : 0;
245
246 ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m);
247
248 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
249 if (error_text) {
250 *error_text = param.outputs.error_text;
251 }
252 if (error_code) {
253 *error_code = param.outputs.error_code;
254 }
255 return param.outputs.returncode;
256 }
257
258 return ret;
259
260 }
261
262 /* Prepare to listen */
php_stream_xport_listen(php_stream * stream,int backlog,zend_string ** error_text)263 PHPAPI int php_stream_xport_listen(php_stream *stream, int backlog, zend_string **error_text)
264 {
265 php_stream_xport_param param;
266 int ret;
267
268 memset(¶m, 0, sizeof(param));
269 param.op = STREAM_XPORT_OP_LISTEN;
270 param.inputs.backlog = backlog;
271 param.want_errortext = error_text ? 1 : 0;
272
273 ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m);
274
275 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
276 if (error_text) {
277 *error_text = param.outputs.error_text;
278 }
279
280 return param.outputs.returncode;
281 }
282
283 return ret;
284 }
285
286 /* 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)287 PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client,
288 zend_string **textaddr,
289 void **addr, socklen_t *addrlen,
290 struct timeval *timeout,
291 zend_string **error_text
292 )
293 {
294 php_stream_xport_param param;
295 int ret;
296
297 memset(¶m, 0, sizeof(param));
298
299 param.op = STREAM_XPORT_OP_ACCEPT;
300 param.inputs.timeout = timeout;
301 param.want_addr = addr ? 1 : 0;
302 param.want_textaddr = textaddr ? 1 : 0;
303 param.want_errortext = error_text ? 1 : 0;
304
305 ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m);
306
307 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
308 *client = param.outputs.client;
309 if (addr) {
310 *addr = param.outputs.addr;
311 *addrlen = param.outputs.addrlen;
312 }
313 if (textaddr) {
314 *textaddr = param.outputs.textaddr;
315 }
316 if (error_text) {
317 *error_text = param.outputs.error_text;
318 }
319
320 return param.outputs.returncode;
321 }
322 return ret;
323 }
324
php_stream_xport_get_name(php_stream * stream,int want_peer,zend_string ** textaddr,void ** addr,socklen_t * addrlen)325 PHPAPI int php_stream_xport_get_name(php_stream *stream, int want_peer,
326 zend_string **textaddr,
327 void **addr, socklen_t *addrlen
328 )
329 {
330 php_stream_xport_param param;
331 int ret;
332
333 memset(¶m, 0, sizeof(param));
334
335 param.op = want_peer ? STREAM_XPORT_OP_GET_PEER_NAME : STREAM_XPORT_OP_GET_NAME;
336 param.want_addr = addr ? 1 : 0;
337 param.want_textaddr = textaddr ? 1 : 0;
338
339 ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m);
340
341 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
342 if (addr) {
343 *addr = param.outputs.addr;
344 *addrlen = param.outputs.addrlen;
345 }
346 if (textaddr) {
347 *textaddr = param.outputs.textaddr;
348 }
349
350 return param.outputs.returncode;
351 }
352 return ret;
353 }
354
php_stream_xport_crypto_setup(php_stream * stream,php_stream_xport_crypt_method_t crypto_method,php_stream * session_stream)355 PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream)
356 {
357 php_stream_xport_crypto_param param;
358 int ret;
359
360 memset(¶m, 0, sizeof(param));
361 param.op = STREAM_XPORT_CRYPTO_OP_SETUP;
362 param.inputs.method = crypto_method;
363 param.inputs.session = session_stream;
364
365 ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m);
366
367 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
368 return param.outputs.returncode;
369 }
370
371 php_error_docref("streams.crypto", E_WARNING, "This stream does not support SSL/crypto");
372
373 return ret;
374 }
375
php_stream_xport_crypto_enable(php_stream * stream,int activate)376 PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate)
377 {
378 php_stream_xport_crypto_param param;
379 int ret;
380
381 memset(¶m, 0, sizeof(param));
382 param.op = STREAM_XPORT_CRYPTO_OP_ENABLE;
383 param.inputs.activate = activate;
384
385 ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m);
386
387 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
388 return param.outputs.returncode;
389 }
390
391 php_error_docref("streams.crypto", E_WARNING, "This stream does not support SSL/crypto");
392
393 return ret;
394 }
395
396 /* Similar to recv() system call; read data from the stream, optionally
397 * 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)398 PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t buflen,
399 int flags, void **addr, socklen_t *addrlen, zend_string **textaddr
400 )
401 {
402 php_stream_xport_param param;
403 int ret = 0;
404 int recvd_len = 0;
405 #if 0
406 int oob;
407
408 if (flags == 0 && addr == NULL) {
409 return php_stream_read(stream, buf, buflen);
410 }
411
412 if (stream->readfilters.head) {
413 php_error_docref(NULL, E_WARNING, "Cannot peek or fetch OOB data from a filtered stream");
414 return -1;
415 }
416
417 oob = (flags & STREAM_OOB) == STREAM_OOB;
418
419 if (!oob && addr == NULL) {
420 /* must be peeking at regular data; copy content from the buffer
421 * first, then adjust the pointer/len before handing off to the
422 * stream */
423 recvd_len = stream->writepos - stream->readpos;
424 if (recvd_len > buflen) {
425 recvd_len = buflen;
426 }
427 if (recvd_len) {
428 memcpy(buf, stream->readbuf, recvd_len);
429 buf += recvd_len;
430 buflen -= recvd_len;
431 }
432 /* if we filled their buffer, return */
433 if (buflen == 0) {
434 return recvd_len;
435 }
436 }
437 #endif
438
439 /* otherwise, we are going to bypass the buffer */
440
441 memset(¶m, 0, sizeof(param));
442
443 param.op = STREAM_XPORT_OP_RECV;
444 param.want_addr = addr ? 1 : 0;
445 param.want_textaddr = textaddr ? 1 : 0;
446 param.inputs.buf = buf;
447 param.inputs.buflen = buflen;
448 param.inputs.flags = flags;
449
450 ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m);
451
452 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
453 if (addr) {
454 *addr = param.outputs.addr;
455 *addrlen = param.outputs.addrlen;
456 }
457 if (textaddr) {
458 *textaddr = param.outputs.textaddr;
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,int flags,void * addr,socklen_t addrlen)467 PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t buflen,
468 int flags, void *addr, socklen_t addrlen)
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, E_WARNING, "Cannot write OOB data, or data to a targeted address on a filtered stream");
484 return -1;
485 }
486
487 memset(¶m, 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, ¶m);
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)507 PHPAPI int php_stream_xport_shutdown(php_stream *stream, stream_shutdown_t how)
508 {
509 php_stream_xport_param param;
510 int ret = 0;
511
512 memset(¶m, 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, ¶m);
518
519 if (ret == PHP_STREAM_OPTION_RETURN_OK) {
520 return param.outputs.returncode;
521 }
522 return -1;
523 }
524