1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2016 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: Sara Golemon <pollita@php.net>                               |
16    +----------------------------------------------------------------------+
17 */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 #include "php_ssh2.h"
25 
php_ssh2_zval_from_resource_handle(int handle)26 void *php_ssh2_zval_from_resource_handle(int handle) {
27 	zval *val;
28 	zend_resource *zr;
29 	ZEND_HASH_FOREACH_VAL(&EG(regular_list), val) {
30 		zr = Z_RES_P(val);
31 		if (zr->handle == handle) {
32 			return val;
33 		}
34 	} ZEND_HASH_FOREACH_END();
35 	return NULL;
36 }
37 
38 
39 /* **********************
40    * channel_stream_ops *
41    ********************** */
42 
43 #if PHP_VERSION_ID < 70400
php_ssh2_channel_stream_write(php_stream * stream,const char * buf,size_t count)44 static size_t php_ssh2_channel_stream_write(php_stream *stream, const char *buf, size_t count)
45 #else
46 static ssize_t php_ssh2_channel_stream_write(php_stream *stream, const char *buf, size_t count)
47 #endif
48 {
49 	php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract;
50 	ssize_t writestate;
51 	LIBSSH2_SESSION *session;
52 
53 	libssh2_channel_set_blocking(abstract->channel, abstract->is_blocking);
54 	session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session);
55 
56 
57 
58 #ifdef PHP_SSH2_SESSION_TIMEOUT
59 	if (abstract->is_blocking) {
60 		libssh2_session_set_timeout(session, abstract->timeout);
61 	}
62 #endif
63 
64 	writestate = libssh2_channel_write_ex(abstract->channel, abstract->streamid, buf, count);
65 
66 #ifdef PHP_SSH2_SESSION_TIMEOUT
67 	if (abstract->is_blocking) {
68 		libssh2_session_set_timeout(session, 0);
69 	}
70 #endif
71 
72 	if (writestate == LIBSSH2_ERROR_EAGAIN) {
73 #if PHP_VERSION_ID < 70400
74 		writestate = 0;
75 #endif
76 	} else if (writestate < 0) {
77 		char *error_msg = NULL;
78 		if (libssh2_session_last_error(session, &error_msg, NULL, 0) == writestate) {
79 			php_error_docref(NULL, E_WARNING, "Failure '%s' (%ld)", error_msg, writestate);
80 		}
81 
82 		stream->eof = 1;
83 #if PHP_VERSION_ID < 70400
84 		writestate = 0;
85 #endif
86 	}
87 
88 	return writestate;
89 }
90 
91 #if PHP_VERSION_ID < 70400
php_ssh2_channel_stream_read(php_stream * stream,char * buf,size_t count)92 static size_t php_ssh2_channel_stream_read(php_stream *stream, char *buf, size_t count)
93 #else
94 static ssize_t php_ssh2_channel_stream_read(php_stream *stream, char *buf, size_t count)
95 #endif
96 {
97 	php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract;
98 	ssize_t readstate;
99 	LIBSSH2_SESSION *session;
100 
101 	stream->eof = libssh2_channel_eof(abstract->channel);
102 	libssh2_channel_set_blocking(abstract->channel, abstract->is_blocking);
103 	session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session);
104 
105 #ifdef PHP_SSH2_SESSION_TIMEOUT
106 	if (abstract->is_blocking) {
107 		libssh2_session_set_timeout(session, abstract->timeout);
108 	}
109 #endif
110 
111 	readstate = libssh2_channel_read_ex(abstract->channel, abstract->streamid, buf, count);
112 
113 #ifdef PHP_SSH2_SESSION_TIMEOUT
114 	if (abstract->is_blocking) {
115 		libssh2_session_set_timeout(session, 0);
116 	}
117 #endif
118 
119 	if (readstate == LIBSSH2_ERROR_EAGAIN) {
120 #if PHP_VERSION_ID < 70400
121 		readstate = 0;
122 #endif
123 	} else if (readstate < 0) {
124 		char *error_msg = NULL;
125 		if (libssh2_session_last_error(session, &error_msg, NULL, 0) == readstate) {
126 			php_error_docref(NULL, E_WARNING, "Failure '%s' (%ld)", error_msg, readstate);
127 		}
128 
129 		stream->eof = 1;
130 		readstate = 0;
131 	}
132 	return readstate;
133 }
134 
php_ssh2_channel_stream_close(php_stream * stream,int close_handle)135 static int php_ssh2_channel_stream_close(php_stream *stream, int close_handle)
136 {
137 	php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract;
138 
139 	if (!abstract->refcount || (--(*(abstract->refcount)) == 0)) {
140 		/* Last one out, turn off the lights */
141 		if (abstract->refcount) {
142 			efree(abstract->refcount);
143 		}
144 		libssh2_channel_eof(abstract->channel);
145 		libssh2_channel_free(abstract->channel);
146 		zend_list_delete(abstract->session_rsrc);
147 	}
148 	efree(abstract);
149 
150 	return 0;
151 }
152 
php_ssh2_channel_stream_flush(php_stream * stream)153 static int php_ssh2_channel_stream_flush(php_stream *stream)
154 {
155 	php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract;
156 
157 	return libssh2_channel_flush_ex(abstract->channel, abstract->streamid);
158 }
159 
php_ssh2_channel_stream_cast(php_stream * stream,int castas,void ** ret)160 static int php_ssh2_channel_stream_cast(php_stream *stream, int castas, void **ret)
161 {
162 	php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract;
163 	LIBSSH2_SESSION *session;
164 	php_ssh2_session_data **session_data;
165 
166 	session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session);
167 	session_data = (php_ssh2_session_data **)libssh2_session_abstract(session);
168 
169 	switch (castas)	{
170 		case PHP_STREAM_AS_FD:
171 		case PHP_STREAM_AS_FD_FOR_SELECT:
172 		case PHP_STREAM_AS_SOCKETD:
173 			if (ret) {
174 				*(php_socket_t *)ret = (*session_data)->socket;
175 			}
176 			return SUCCESS;
177 		default:
178 			return FAILURE;
179 	}
180 }
181 
php_ssh2_channel_stream_set_option(php_stream * stream,int option,int value,void * ptrparam)182 static int php_ssh2_channel_stream_set_option(php_stream *stream, int option, int value, void *ptrparam)
183 {
184 	php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract;
185 	int ret;
186 
187 	switch (option) {
188 		case PHP_STREAM_OPTION_BLOCKING:
189 			ret = abstract->is_blocking;
190 			abstract->is_blocking = value;
191 			return ret;
192 			break;
193 
194 		case PHP_STREAM_OPTION_META_DATA_API:
195 			add_assoc_long((zval*)ptrparam, "exit_status", libssh2_channel_get_exit_status(abstract->channel));
196 			break;
197 
198 		case PHP_STREAM_OPTION_READ_TIMEOUT:
199 			ret = abstract->timeout;
200 #ifdef PHP_SSH2_SESSION_TIMEOUT
201 			struct timeval tv = *(struct timeval*)ptrparam;
202 			abstract->timeout = tv.tv_sec * 1000 + (tv.tv_usec / 1000);
203 #else
204 			php_error_docref(NULL, E_WARNING, "No support for ssh2 stream timeout. Please recompile with libssh2 >= 1.2.9");
205 #endif
206 			return ret;
207 			break;
208 
209 		case PHP_STREAM_OPTION_CHECK_LIVENESS:
210 			return stream->eof = libssh2_channel_eof(abstract->channel);
211 			break;
212 	}
213 
214 	return -1;
215 }
216 
217 php_stream_ops php_ssh2_channel_stream_ops = {
218 	php_ssh2_channel_stream_write,
219 	php_ssh2_channel_stream_read,
220 	php_ssh2_channel_stream_close,
221 	php_ssh2_channel_stream_flush,
222 	PHP_SSH2_CHANNEL_STREAM_NAME,
223 	NULL, /* seek */
224 	php_ssh2_channel_stream_cast,
225 	NULL, /* stat */
226 	php_ssh2_channel_stream_set_option,
227 };
228 
229 /* *********************
230    * Magic Path Helper *
231    ********************* */
232 
233 /* {{{ php_ssh2_fopen_wraper_parse_path
234  * Parse an ssh2.*:// path
235  */
php_ssh2_fopen_wraper_parse_path(const char * path,char * type,php_stream_context * context,LIBSSH2_SESSION ** psession,zend_resource ** presource,LIBSSH2_SFTP ** psftp,zend_resource ** psftp_rsrc)236 php_url *php_ssh2_fopen_wraper_parse_path(const char *path, char *type, php_stream_context *context,
237 											LIBSSH2_SESSION **psession, zend_resource **presource,
238 											LIBSSH2_SFTP **psftp, zend_resource **psftp_rsrc)
239 {
240 	php_ssh2_sftp_data *sftp_data = NULL;
241 	LIBSSH2_SESSION *session;
242 	php_url *resource;
243 	zval *methods = NULL, *callbacks = NULL, zsession, *tmpzval;
244 	zend_long resource_id;
245 	char *h, *username = NULL, *password = NULL, *pubkey_file = NULL, *privkey_file = NULL;
246 	int username_len = 0, password_len = 0;
247 
248 	h = strstr(path, "Resource id #");
249 	if (h) {
250 		/* Starting with 5.6.28, 7.0.13 need to be clean, else php_url_parse will fail */
251 		char *tmp = estrdup(path);
252 
253 		strncpy(tmp + (h-path), h + sizeof("Resource id #")-1, strlen(tmp)-sizeof("Resource id #"));
254 		resource = php_url_parse(tmp);
255 		efree(tmp);
256 	} else {
257 		resource = php_url_parse(path);
258 	}
259 	if (!resource || !resource->path) {
260 		return NULL;
261 	}
262 
263 	if (strncmp(SSH2_URL_STR(resource->scheme), "ssh2.", sizeof("ssh2.") - 1)) {
264 		/* Not an ssh wrapper */
265 		php_url_free(resource);
266 		return NULL;
267 	}
268 
269 	if (strcmp(SSH2_URL_STR(resource->scheme) + sizeof("ssh2.") - 1, type)) {
270 		/* Wrong ssh2. wrapper type */
271 		php_url_free(resource);
272 		return NULL;
273 	}
274 
275 	if (!resource->host) {
276 		return NULL;
277 	}
278 
279 	/*
280 		Find resource->path in the path string, then copy the entire string from the original path.
281 		This includes ?query#fragment in the path string
282 	*/
283 // TODO copy seems uneeded
284 #if PHP_VERSION_ID < 70300
285 	{
286 	char * s;
287 
288 	s = resource->path;
289 	resource->path = estrdup(strstr(path, resource->path));
290 	efree(s);
291 	}
292 #else
293 	{
294 	zend_string *tmp;
295 
296 	tmp = resource->path;
297 	resource->path = zend_string_init(ZSTR_VAL(resource->path), ZSTR_LEN(resource->path), 0);
298 	zend_string_release(tmp);
299 	}
300 #endif
301 
302 	/* Look for a resource ID to reuse a session */
303 	if (is_numeric_string(SSH2_URL_STR(resource->host), SSH2_URL_LEN(resource->host), &resource_id, NULL, 0) == IS_LONG) {
304 		php_ssh2_sftp_data *sftp_data;
305 		zval *zresource;
306 
307 		if ((zresource = php_ssh2_zval_from_resource_handle(resource_id)) == NULL) {
308 			php_url_free(resource);
309 			return NULL;
310 		}
311 
312 		if (psftp) {
313 			/* suppress potential warning by passing NULL as resource_type_name */
314 			sftp_data = (php_ssh2_sftp_data *)zend_fetch_resource(Z_RES_P(zresource), NULL, le_ssh2_sftp);
315 			if (sftp_data) {
316 				/* Want the sftp layer */
317 				Z_ADDREF_P(zresource);
318 				*psftp_rsrc = Z_RES_P(zresource);
319 				*psftp = sftp_data->sftp;
320 				*presource = sftp_data->session_rsrc;
321 				*psession = sftp_data->session;
322 				return resource;
323 			}
324 		}
325 		session = (LIBSSH2_SESSION *)zend_fetch_resource(Z_RES_P(zresource), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session);
326 		if (session) {
327 			if (psftp) {
328 				/* We need an sftp layer too */
329 				LIBSSH2_SFTP *sftp = libssh2_sftp_init(session);
330 
331 				if (!sftp) {
332 					php_url_free(resource);
333 					return NULL;
334 				}
335 				sftp_data = emalloc(sizeof(php_ssh2_sftp_data));
336 				sftp_data->sftp = sftp;
337 				sftp_data->session = session;
338 				sftp_data->session_rsrc = Z_RES_P(zresource);
339 				Z_ADDREF_P(zresource);
340 				*psftp_rsrc = zend_register_resource(sftp_data, le_ssh2_sftp);
341 				*psftp = sftp;
342 				*presource = Z_RES_P(zresource);
343 				*psession = session;
344 				return resource;
345 			}
346 			Z_ADDREF_P(zresource);
347 			*presource = Z_RES_P(zresource);
348 			*psession = session;
349 			return resource;
350 		}
351 	}
352 
353 	/* Fallback on finding it in the context */
354 	if (SSH2_URL_STR(resource->host)[0] == 0 && context && psftp &&
355 		(tmpzval = php_stream_context_get_option(context, "ssh2", "sftp")) != NULL &&
356 		Z_TYPE_P(tmpzval) == IS_RESOURCE) {
357 		php_ssh2_sftp_data *sftp_data;
358 		sftp_data = (php_ssh2_sftp_data *)zend_fetch_resource(Z_RES_P(tmpzval), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp);
359 		if (sftp_data) {
360 			Z_ADDREF_P(tmpzval);
361 			*psftp_rsrc = Z_RES_P(tmpzval);
362 			*psftp = sftp_data->sftp;
363 			*presource = sftp_data->session_rsrc;
364 			*psession = sftp_data->session;
365 			return resource;
366 		}
367 	}
368 	if (SSH2_URL_STR(resource->host)[0] == 0 && context &&
369 		(tmpzval = php_stream_context_get_option(context, "ssh2", "session")) != NULL &&
370 		Z_TYPE_P(tmpzval) == IS_RESOURCE) {
371 		session = (LIBSSH2_SESSION *)zend_fetch_resource(Z_RES_P(tmpzval), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session);
372 		if (session) {
373 			if (psftp) {
374 				/* We need an SFTP layer too! */
375 				LIBSSH2_SFTP *sftp = libssh2_sftp_init(session);
376 				php_ssh2_sftp_data *sftp_data;
377 
378 				if (!sftp) {
379 					php_url_free(resource);
380 					return NULL;
381 				}
382 				sftp_data = emalloc(sizeof(php_ssh2_sftp_data));
383 				sftp_data->sftp = sftp;
384 				sftp_data->session = session;
385 				sftp_data->session_rsrc = Z_RES_P(tmpzval);
386 				Z_ADDREF_P(tmpzval);
387 				*psftp_rsrc = zend_register_resource(sftp_data, le_ssh2_sftp);
388 				*psftp = sftp;
389 				*presource = Z_RES_P(tmpzval);
390 				*psession = session;
391 				return resource;
392 			}
393 			Z_ADDREF_P(tmpzval);
394 			*psession = session;
395 			*presource = Z_RES_P(tmpzval);
396 			return resource;
397 		}
398 	}
399 
400 	/* Make our own connection then */
401 	if (!resource->port) {
402 		resource->port = 22;
403 	}
404 
405 	if (context &&
406 		(tmpzval = php_stream_context_get_option(context, "ssh2", "methods")) != NULL &&
407 		Z_TYPE_P(tmpzval) == IS_ARRAY) {
408 		methods = tmpzval;
409 	}
410 
411 	if (context &&
412 		(tmpzval = php_stream_context_get_option(context, "ssh2", "callbacks")) != NULL &&
413 		Z_TYPE_P(tmpzval) == IS_ARRAY) {
414 		callbacks = tmpzval;
415 	}
416 
417 	if (context &&
418 		(tmpzval = php_stream_context_get_option(context, "ssh2", "username")) != NULL &&
419 		Z_TYPE_P(tmpzval) == IS_STRING) {
420 		username = Z_STRVAL_P(tmpzval);
421 		username_len = Z_STRLEN_P(tmpzval);
422 	}
423 
424 	if (context &&
425 		(tmpzval = php_stream_context_get_option(context, "ssh2", "password")) != NULL &&
426 		Z_TYPE_P(tmpzval) == IS_STRING) {
427 		password = Z_STRVAL_P(tmpzval);
428 		password_len = Z_STRLEN_P(tmpzval);
429 	}
430 
431 	if (context &&
432 		(tmpzval = php_stream_context_get_option(context, "ssh2", "pubkey_file")) != NULL &&
433 		Z_TYPE_P(tmpzval) == IS_STRING) {
434 		pubkey_file = Z_STRVAL_P(tmpzval);
435 	}
436 
437 	if (context &&
438 		(tmpzval = php_stream_context_get_option(context, "ssh2", "privkey_file")) != NULL &&
439 		Z_TYPE_P(tmpzval) == IS_STRING) {
440 		privkey_file = Z_STRVAL_P(tmpzval);
441 	}
442 
443 	if (resource->user) {
444 		int len = SSH2_URL_LEN(resource->user);
445 
446 		if (len) {
447 			username = SSH2_URL_STR(resource->user);
448 			username_len = len;
449 		}
450 	}
451 
452 	if (resource->pass) {
453 		int len = SSH2_URL_LEN(resource->pass);
454 
455 		if (len) {
456 			password = SSH2_URL_STR(resource->pass);
457 			password_len = len;
458 		}
459 	}
460 
461 	if (!username) {
462 		/* username is a minimum */
463 		php_url_free(resource);
464 		return NULL;
465 	}
466 
467 	session = php_ssh2_session_connect(SSH2_URL_STR(resource->host), resource->port, methods, callbacks);
468 	if (!session) {
469 		/* Unable to connect! */
470 		php_url_free(resource);
471 		return NULL;
472 	}
473 
474 	/* Authenticate */
475 	if (pubkey_file && privkey_file) {
476 		if (php_check_open_basedir(pubkey_file) || php_check_open_basedir(privkey_file)) {
477 			php_url_free(resource);
478 			return NULL;
479 		}
480 
481 		/* Attempt pubkey authentication */
482 		if (!libssh2_userauth_publickey_fromfile(session, username, pubkey_file, privkey_file, password)) {
483 			goto session_authed;
484 		}
485 	}
486 
487 	if (password) {
488 		/* Attempt password authentication */
489 		if (libssh2_userauth_password_ex(session, username, username_len, password, password_len, NULL) == 0) {
490 			goto session_authed;
491 		}
492 	}
493 
494 	/* Auth failure */
495 	php_url_free(resource);
496 	if (Z_RES(zsession)) {
497 		zend_list_delete(Z_RES(zsession));
498 	}
499 	return NULL;
500 
501 session_authed:
502 	ZVAL_RES(&zsession, zend_register_resource(session, le_ssh2_session));
503 
504 	if (psftp) {
505 		LIBSSH2_SFTP *sftp;
506 		zval zsftp;
507 
508 		sftp = libssh2_sftp_init(session);
509 		if (!sftp) {
510 			php_url_free(resource);
511 			zend_list_delete(Z_RES(zsession));
512 			return NULL;
513 		}
514 
515 		sftp_data = emalloc(sizeof(php_ssh2_sftp_data));
516 		sftp_data->session = session;
517 		sftp_data->sftp = sftp;
518 		sftp_data->session_rsrc = Z_RES(zsession);
519 
520 		//TODO Sean-Der
521 		//ZEND_REGISTER_RESOURCE(sftp_data, le_ssh2_sftp);
522 		*psftp_rsrc = Z_RES(zsftp);
523 		*psftp = sftp;
524 	}
525 
526 	*presource = Z_RES(zsession);
527 	*psession = session;
528 
529 	return resource;
530 }
531 /* }}} */
532 
533 /* *****************
534    * Shell Wrapper *
535    ***************** */
536 
537 /* {{{ php_ssh2_shell_open
538  * Make a stream from a session
539  */
php_ssh2_shell_open(LIBSSH2_SESSION * session,zend_resource * resource,char * term,int term_len,zval * environment,long width,long height,long type)540 static php_stream *php_ssh2_shell_open(LIBSSH2_SESSION *session, zend_resource *resource, char *term, int term_len, zval *environment, long width, long height, long type)
541 {
542 	LIBSSH2_CHANNEL *channel;
543 	php_ssh2_channel_data *channel_data;
544 	php_stream *stream;
545 
546 	libssh2_session_set_blocking(session, 1);
547 
548 	channel = libssh2_channel_open_session(session);
549 	if (!channel) {
550 		php_error_docref(NULL, E_WARNING, "Unable to request a channel from remote host");
551 		return NULL;
552 	}
553 
554 	if (environment) {
555 		zend_string *key;
556 		int key_type;
557 		zend_ulong idx;
558 
559 		for(zend_hash_internal_pointer_reset(HASH_OF(environment));
560 			(key_type = zend_hash_get_current_key(HASH_OF(environment), &key, &idx)) != HASH_KEY_NON_EXISTENT;
561 			zend_hash_move_forward(HASH_OF(environment))) {
562 			if (key_type == HASH_KEY_IS_STRING) {
563 				zval *value;
564 
565 				if ((value = zend_hash_get_current_data(HASH_OF(environment))) != NULL) {
566 					zval copyval = *value;
567 
568 					zval_copy_ctor(&copyval);
569 					convert_to_string(&copyval);
570 					if (libssh2_channel_setenv_ex(channel, key->val, key->len, Z_STRVAL(copyval), Z_STRLEN(copyval))) {
571 						php_error_docref(NULL, E_WARNING, "Failed setting %s=%s on remote end", ZSTR_VAL(key), Z_STRVAL(copyval));
572 					}
573 					zval_dtor(&copyval);
574 				}
575 			} else {
576 				php_error_docref(NULL, E_NOTICE, "Skipping numeric index in environment array");
577 			}
578 		}
579 	}
580 
581 	if (type == PHP_SSH2_TERM_UNIT_CHARS) {
582 		if (libssh2_channel_request_pty_ex(channel, term, term_len, NULL, 0, width, height, 0, 0)) {
583 			php_error_docref(NULL, E_WARNING, "Failed allocating %s pty at %ldx%ld characters", term, width, height);
584 			libssh2_channel_free(channel);
585 			return NULL;
586 		}
587 	} else {
588 		if (libssh2_channel_request_pty_ex(channel, term, term_len, NULL, 0, 0, 0, width, height)) {
589 			php_error_docref(NULL, E_WARNING, "Failed allocating %s pty at %ldx%ld pixels", term, width, height);
590 			libssh2_channel_free(channel);
591 			return NULL;
592 		}
593 	}
594 
595 	if (libssh2_channel_shell(channel)) {
596 		php_error_docref(NULL, E_WARNING, "Unable to request shell from remote host");
597 		libssh2_channel_free(channel);
598 		return NULL;
599 	}
600 
601 	/* Turn it into a stream */
602 	channel_data = emalloc(sizeof(php_ssh2_channel_data));
603 	channel_data->channel = channel;
604 	channel_data->streamid = 0;
605 	channel_data->is_blocking = 0;
606 	channel_data->timeout = 0;
607 	channel_data->session_rsrc = resource;
608 	channel_data->refcount = NULL;
609 
610 	stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r+");
611 
612 	return stream;
613 }
614 /* }}} */
615 
616 /* {{{ php_ssh2_fopen_wrapper_shell
617  * ssh2.shell:// fopen wrapper
618  */
php_ssh2_fopen_wrapper_shell(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)619 static php_stream *php_ssh2_fopen_wrapper_shell(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
620 {
621 	LIBSSH2_SESSION *session = NULL;
622 	php_stream *stream;
623 	zval *tmpzval, *environment = NULL;
624 	char *terminal = PHP_SSH2_DEFAULT_TERMINAL;
625 	zend_long width = PHP_SSH2_DEFAULT_TERM_WIDTH;
626 	zend_long height = PHP_SSH2_DEFAULT_TERM_HEIGHT;
627 	zend_long type = PHP_SSH2_DEFAULT_TERM_UNIT;
628 	zend_resource *rsrc = NULL;
629 	int terminal_len = sizeof(PHP_SSH2_DEFAULT_TERMINAL) - 1;
630 	php_url *resource;
631 	char *s;
632 
633 	resource = php_ssh2_fopen_wraper_parse_path(path, "shell", context, &session, &rsrc, NULL, NULL);
634 	if (!resource || !session) {
635 		return NULL;
636 	}
637 
638 	if (context &&
639 		(tmpzval = php_stream_context_get_option(context, "ssh2", "env")) != NULL && Z_TYPE_P(tmpzval) == IS_ARRAY) {
640 		environment = tmpzval;
641 	}
642 
643 	if (context &&
644 		(tmpzval = php_stream_context_get_option(context, "ssh2", "term")) != NULL && Z_TYPE_P(tmpzval) == IS_STRING) {
645 		terminal = Z_STRVAL_P(tmpzval);
646 		terminal_len = Z_STRLEN_P(tmpzval);
647 	}
648 
649 	if (context &&
650 		(tmpzval = php_stream_context_get_option(context, "ssh2", "term_width")) != NULL) {
651 		zval copyval;
652 		copyval = *tmpzval;
653 		convert_to_long(&copyval);
654 		width = Z_LVAL_P(&copyval);
655 		zval_ptr_dtor(&copyval);
656 	}
657 
658 	if (context &&
659 		(tmpzval = php_stream_context_get_option(context, "ssh2", "term_height")) != NULL) {
660 		zval copyval;
661 		copyval = *tmpzval;
662 		convert_to_long(&copyval);
663 		height = Z_LVAL_P(&copyval);
664 		zval_ptr_dtor(&copyval);
665 	}
666 
667 	if (context &&
668 		(tmpzval = php_stream_context_get_option(context, "ssh2", "term_units")) != NULL) {
669 		zval copyval;
670 		copyval = *tmpzval;
671 		convert_to_long(&copyval);
672 		type = Z_LVAL_P(&copyval);
673 		zval_ptr_dtor(&copyval);
674 	}
675 
676 	s = resource->path ? SSH2_URL_STR(resource->path) : NULL;
677 
678 	if (s && s[0] == '/') {
679 		/* Terminal type encoded into URL overrides context terminal type */
680 		char *p;
681 
682 		s++;
683 		p = strchr(s, '/');
684 		if (p) {
685 			if (p - s) {
686 				terminal = s;
687 				terminal_len = p - terminal;
688 				s += terminal_len + 1;
689 			} else {
690 				/* "null" terminal given, skip it */
691 				s++;
692 			}
693 		} else {
694 			int len;
695 
696 			if ((len = strlen(path + 1))) {
697 				terminal = s;
698 				terminal_len = len;
699 				s += len;
700 			}
701 		}
702 	}
703 
704 	/* TODO: Accept resolution and environment vars as URL style parameters
705 	 * ssh2.shell://hostorresource/terminal/99x99c?envvar=envval&envvar=envval....
706 	 */
707 	stream = php_ssh2_shell_open(session, rsrc, terminal, terminal_len, environment, width, height, type);
708 	if (!stream) {
709 		zend_list_delete(rsrc);
710 	}
711 	php_url_free(resource);
712 
713 	return stream;
714 }
715 /* }}} */
716 
717 static php_stream_wrapper_ops php_ssh2_shell_stream_wops = {
718 	php_ssh2_fopen_wrapper_shell,
719 	NULL, /* stream_close */
720 	NULL, /* stat */
721 	NULL, /* stat_url */
722 	NULL, /* opendir */
723 	"ssh2.shell"
724 };
725 
726 php_stream_wrapper php_ssh2_stream_wrapper_shell = {
727 	&php_ssh2_shell_stream_wops,
728 	NULL,
729 	0
730 };
731 
732 /* {{{ proto stream ssh2_shell(resource session[, string term_type[, array env[, int width, int height[, int width_height_type]]]])
733  * Open a shell at the remote end and allocate a channel for it
734  */
PHP_FUNCTION(ssh2_shell)735 PHP_FUNCTION(ssh2_shell)
736 {
737 	LIBSSH2_SESSION *session;
738 	php_stream *stream;
739 	zval *zsession;
740 	zval *environment = NULL;
741 	char *term = PHP_SSH2_DEFAULT_TERMINAL;
742 	size_t term_len = sizeof(PHP_SSH2_DEFAULT_TERMINAL) - 1;
743 	zend_long width = PHP_SSH2_DEFAULT_TERM_WIDTH;
744 	zend_long height = PHP_SSH2_DEFAULT_TERM_HEIGHT;
745 	zend_long type = PHP_SSH2_DEFAULT_TERM_UNIT;
746 	int argc = ZEND_NUM_ARGS();
747 
748 	if (argc == 5) {
749 		php_error_docref(NULL, E_ERROR, "width specified without height parameter");
750 		RETURN_FALSE;
751 	}
752 
753 	if (zend_parse_parameters(argc, "r|sa!lll", &zsession, &term, &term_len, &environment, &width, &height, &type) == FAILURE) {
754 		return;
755 	}
756 
757 	SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession);
758 
759 	stream = php_ssh2_shell_open(session, Z_RES_P(zsession), term, term_len, environment, width, height, type);
760 	if (!stream) {
761 		RETURN_FALSE;
762 	}
763 
764 	/* Ensure that channels are freed BEFORE the sessions they belong to */
765 	Z_ADDREF_P(zsession);
766 
767 	php_stream_to_zval(stream, return_value);
768 }
769 /* }}} */
770 
771 /* ****************
772    * Exec Wrapper *
773    **************** */
774 
775 /* {{{ php_ssh2_exec_command
776  * Make a stream from a session
777  */
php_ssh2_exec_command(LIBSSH2_SESSION * session,zend_resource * rsrc,char * command,char * term,int term_len,zval * environment,long width,long height,long type)778 static php_stream *php_ssh2_exec_command(LIBSSH2_SESSION *session, zend_resource *rsrc, char *command, char *term, int term_len, zval *environment, long width, long height, long type)
779 {
780 	LIBSSH2_CHANNEL *channel;
781 	php_ssh2_channel_data *channel_data;
782 	php_stream *stream;
783 
784 	libssh2_session_set_blocking(session, 1);
785 
786 	channel = libssh2_channel_open_session(session);
787 	if (!channel) {
788 		php_error_docref(NULL, E_WARNING, "Unable to request a channel from remote host");
789 		return NULL;
790 	}
791 
792 	if (environment) {
793 		zend_string *key = NULL;
794 		int key_type;
795 		zend_ulong idx = 0;
796 		HashPosition pos;
797 
798 		for(zend_hash_internal_pointer_reset_ex(HASH_OF(environment), &pos);
799 			(key_type = zend_hash_get_current_key_ex(HASH_OF(environment), &key, &idx, &pos)) != HASH_KEY_NON_EXISTENT;
800 			zend_hash_move_forward_ex(HASH_OF(environment), &pos)) {
801 			if (key_type == HASH_KEY_IS_STRING) {
802 				zval *value;
803 
804 				if ((value = zend_hash_get_current_data(HASH_OF(environment))) != NULL) {
805 					zval copyval = *value;
806 
807 					zval_copy_ctor(&copyval);
808 					convert_to_string(&copyval);
809 					if (libssh2_channel_setenv_ex(channel, key->val, key->len, Z_STRVAL(copyval), Z_STRLEN(copyval))) {
810 						php_error_docref(NULL, E_WARNING, "Failed setting %s=%s on remote end", ZSTR_VAL(key), Z_STRVAL(copyval));
811 					}
812 					zval_dtor(&copyval);
813 				}
814 			} else {
815 				php_error_docref(NULL, E_NOTICE, "Skipping numeric index in environment array");
816 			}
817 		}
818 	}
819 
820 	if (term) {
821 		if (type == PHP_SSH2_TERM_UNIT_CHARS) {
822 			if (libssh2_channel_request_pty_ex(channel, term, term_len, NULL, 0, width, height, 0, 0)) {
823 				php_error_docref(NULL, E_WARNING, "Failed allocating %s pty at %ldx%ld characters", term, width, height);
824 				libssh2_channel_free(channel);
825 				return NULL;
826 			}
827 		} else {
828 			if (libssh2_channel_request_pty_ex(channel, term, term_len, NULL, 0, 0, 0, width, height)) {
829 				php_error_docref(NULL, E_WARNING, "Failed allocating %s pty at %ldx%ld pixels", term, width, height);
830 				libssh2_channel_free(channel);
831 				return NULL;
832 			}
833 		}
834 	}
835 
836 	if (libssh2_channel_exec(channel, command)) {
837 		php_error_docref(NULL, E_WARNING, "Unable to request command execution on remote host");
838 		libssh2_channel_free(channel);
839 		return NULL;
840 	}
841 
842 	/* Turn it into a stream */
843 	channel_data = emalloc(sizeof(php_ssh2_channel_data));
844 	channel_data->channel = channel;
845 	channel_data->streamid = 0;
846 	channel_data->is_blocking = 0;
847 	channel_data->timeout = 0;
848 	channel_data->session_rsrc = rsrc;
849 	channel_data->refcount = NULL;
850 
851 	stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r+");
852 
853 	return stream;
854 }
855 /* }}} */
856 
857 /* {{{ php_ssh2_fopen_wrapper_exec
858  * ssh2.exec:// fopen wrapper
859  */
php_ssh2_fopen_wrapper_exec(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)860 static php_stream *php_ssh2_fopen_wrapper_exec(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
861 {
862 	LIBSSH2_SESSION *session = NULL;
863 	php_stream *stream;
864 	zval *tmpzval, *environment = NULL;
865 	zend_resource *rsrc = NULL;
866 	php_url *resource;
867 	char *terminal = NULL;
868 	int terminal_len = 0;
869 	long width = PHP_SSH2_DEFAULT_TERM_WIDTH;
870 	long height = PHP_SSH2_DEFAULT_TERM_HEIGHT;
871 	long type = PHP_SSH2_DEFAULT_TERM_UNIT;
872 
873 	resource = php_ssh2_fopen_wraper_parse_path(path, "exec", context, &session, &rsrc, NULL, NULL);
874 	if (!resource || !session) {
875 		return NULL;
876 	}
877 	if (!resource->path) {
878 		php_url_free(resource);
879 		zend_list_delete(rsrc);
880 		return NULL;
881 	}
882 
883 	if (context &&
884 		(tmpzval = php_stream_context_get_option(context, "ssh2", "env")) != NULL && Z_TYPE_P(tmpzval) == IS_ARRAY) {
885 		environment = tmpzval;
886 	}
887 
888 	if (context &&
889 		(tmpzval = php_stream_context_get_option(context, "ssh2", "term")) != NULL && Z_TYPE_P(tmpzval) == IS_STRING) {
890 		terminal = Z_STRVAL_P(tmpzval);
891 		terminal_len = Z_STRLEN_P(tmpzval);
892 	}
893 
894 	if (context &&
895 		(tmpzval = php_stream_context_get_option(context, "ssh2", "term_width")) != NULL) {
896 		zval copyval;
897 		copyval = *tmpzval;
898 		convert_to_long(&copyval);
899 		width = Z_LVAL_P(&copyval);
900 		zval_ptr_dtor(&copyval);
901 	}
902 
903 	if (context &&
904 		(tmpzval = php_stream_context_get_option(context, "ssh2", "term_height")) != NULL) {
905 		zval copyval;
906 		copyval = *tmpzval;
907 		convert_to_long(&copyval);
908 		height = Z_LVAL_P(&copyval);
909 		zval_ptr_dtor(&copyval);
910 	}
911 
912 	if (context &&
913 		(tmpzval = php_stream_context_get_option(context, "ssh2", "term_units")) != NULL) {
914 		zval *copyval;
915 		copyval = tmpzval;
916 		convert_to_long(copyval);
917 		type = Z_LVAL_P(copyval);
918 		zval_ptr_dtor(copyval);
919 	}
920 
921 	stream = php_ssh2_exec_command(session, rsrc, SSH2_URL_STR(resource->path) + 1, terminal, terminal_len, environment, width, height, type);
922 	if (!stream) {
923 		zend_list_delete(rsrc);
924 	}
925 	php_url_free(resource);
926 
927 	return stream;
928 }
929 /* }}} */
930 
931 static php_stream_wrapper_ops php_ssh2_exec_stream_wops = {
932 	php_ssh2_fopen_wrapper_exec,
933 	NULL, /* stream_close */
934 	NULL, /* stat */
935 	NULL, /* stat_url */
936 	NULL, /* opendir */
937 	"ssh2.exec"
938 };
939 
940 php_stream_wrapper php_ssh2_stream_wrapper_exec = {
941 	&php_ssh2_exec_stream_wops,
942 	NULL,
943 	0
944 };
945 
946 /* {{{ proto stream ssh2_exec(resource session, string command[, string pty[, array env[, int width[, int height[, int width_height_type]]]]])
947  * Execute a command at the remote end and allocate a channel for it
948  *
949  * This function has a dirty little secret.... pty and env can be in either order.... shhhh... don't tell anyone
950  */
PHP_FUNCTION(ssh2_exec)951 PHP_FUNCTION(ssh2_exec)
952 {
953 	LIBSSH2_SESSION *session;
954 	php_stream *stream;
955 	zval *zsession;
956 	zval *environment = NULL;
957 	zval *zpty = NULL;
958 	char *command;
959 	size_t command_len;
960 	zend_long width = PHP_SSH2_DEFAULT_TERM_WIDTH;
961 	zend_long height = PHP_SSH2_DEFAULT_TERM_HEIGHT;
962 	zend_long type = PHP_SSH2_DEFAULT_TERM_UNIT;
963 	char *term = NULL;
964 	int term_len = 0;
965 
966 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs|z!z!lll", &zsession, &command, &command_len, &zpty, &environment, &width, &height, &type) == FAILURE) {
967 		return;
968 	}
969 
970 	if (zpty && Z_TYPE_P(zpty) == IS_ARRAY) {
971 		/* Swap pty and environment -- old call style */
972 		zval *tmp = zpty;
973 		zpty = environment;
974 		environment = tmp;
975 	}
976 
977 	if (environment && Z_TYPE_P(environment) != IS_ARRAY) {
978 		php_error_docref(NULL, E_WARNING, "ssh2_exec() expects arg 4 to be of type array");
979 		RETURN_FALSE;
980 	}
981 
982 	if (zpty) {
983 		convert_to_string(zpty);
984 		term = Z_STRVAL_P(zpty);
985 		term_len = Z_STRLEN_P(zpty);
986 	}
987 
988 	SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession);
989 
990 	stream = php_ssh2_exec_command(session, Z_RES_P(zsession), command, term, term_len, environment, width, height, type);
991 	if (!stream) {
992 		RETURN_FALSE;
993 	}
994 
995 	/* Ensure that channels are freed BEFORE the sessions they belong to */
996 	Z_ADDREF_P(zsession);
997 
998 	php_stream_to_zval(stream, return_value);
999 }
1000 /* }}} */
1001 
1002 /* ***************
1003    * SCP Wrapper *
1004    *************** */
1005 
1006 /* {{{ php_ssh2_scp_xfer
1007  * Make a stream from a session
1008  */
php_ssh2_scp_xfer(LIBSSH2_SESSION * session,zend_resource * rsrc,char * filename)1009 static php_stream *php_ssh2_scp_xfer(LIBSSH2_SESSION *session, zend_resource *rsrc, char *filename)
1010 {
1011 	LIBSSH2_CHANNEL *channel;
1012 	php_ssh2_channel_data *channel_data;
1013 	php_stream *stream;
1014 
1015 	channel = libssh2_scp_recv(session, filename, NULL);
1016 	if (!channel) {
1017 		char *error = "";
1018 		libssh2_session_last_error(session, &error, NULL, 0);
1019 		php_error_docref(NULL, E_WARNING, "Unable to request a channel from remote host: %s", error);
1020 		return NULL;
1021 	}
1022 
1023 	/* Turn it into a stream */
1024 	channel_data = emalloc(sizeof(php_ssh2_channel_data));
1025 	channel_data->channel = channel;
1026 	channel_data->streamid = 0;
1027 	channel_data->is_blocking = 0;
1028 	channel_data->timeout = 0;
1029 	channel_data->session_rsrc = rsrc;
1030 	channel_data->refcount = NULL;
1031 
1032 	stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r");
1033 
1034 	return stream;
1035 }
1036 /* }}} */
1037 
1038 /* {{{ php_ssh2_fopen_wrapper_scp
1039  * ssh2.scp:// fopen wrapper (Read mode only, if you want to know why write mode isn't supported as a stream, take a look at the SCP protocol)
1040  */
php_ssh2_fopen_wrapper_scp(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)1041 static php_stream *php_ssh2_fopen_wrapper_scp(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1042 {
1043 	LIBSSH2_SESSION *session = NULL;
1044 	php_stream *stream;
1045 	zend_resource *rsrc = NULL;
1046 	php_url *resource;
1047 
1048 	if (strchr(mode, '+') || strchr(mode, 'a') || strchr(mode, 'w')) {
1049 		return NULL;
1050 	}
1051 
1052 	resource = php_ssh2_fopen_wraper_parse_path(path, "scp", context, &session, &rsrc, NULL, NULL);
1053 	if (!resource || !session) {
1054 		return NULL;
1055 	}
1056 	if (!resource->path) {
1057 		php_url_free(resource);
1058 		zend_list_delete(rsrc);
1059 		return NULL;
1060 	}
1061 
1062 	stream = php_ssh2_scp_xfer(session, rsrc, SSH2_URL_STR(resource->path));
1063 	if (!stream) {
1064 		zend_list_delete(rsrc);
1065 	}
1066 	php_url_free(resource);
1067 
1068 	return stream;
1069 }
1070 /* }}} */
1071 
1072 static php_stream_wrapper_ops php_ssh2_scp_stream_wops = {
1073 	php_ssh2_fopen_wrapper_scp,
1074 	NULL, /* stream_close */
1075 	NULL, /* stat */
1076 	NULL, /* stat_url */
1077 	NULL, /* opendir */
1078 	"ssh2.scp"
1079 };
1080 
1081 php_stream_wrapper php_ssh2_stream_wrapper_scp = {
1082 	&php_ssh2_scp_stream_wops,
1083 	NULL,
1084 	0
1085 };
1086 
1087 /* {{{ proto bool ssh2_scp_recv(resource session, string remote_file, string local_file)
1088  * Request a file via SCP
1089  */
PHP_FUNCTION(ssh2_scp_recv)1090 PHP_FUNCTION(ssh2_scp_recv)
1091 {
1092 	LIBSSH2_SESSION *session;
1093 	LIBSSH2_CHANNEL *remote_file;
1094 	struct stat sb;
1095 	php_stream *local_file;
1096 	zval *zsession;
1097 	char *remote_filename, *local_filename;
1098 	size_t remote_filename_len, local_filename_len;
1099 
1100 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rss", &zsession,  &remote_filename, &remote_filename_len,
1101 																			&local_filename, &local_filename_len) == FAILURE) {
1102 		return;
1103 	}
1104 
1105 	SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession);
1106 
1107 	remote_file = libssh2_scp_recv(session, remote_filename, &sb);
1108 	if (!remote_file) {
1109 		php_error_docref(NULL, E_WARNING, "Unable to receive remote file");
1110 		RETURN_FALSE;
1111 	}
1112 	libssh2_channel_set_blocking(remote_file, 1);
1113 
1114 	local_file = php_stream_open_wrapper(local_filename, "wb", REPORT_ERRORS, NULL);
1115 	if (!local_file) {
1116 		php_error_docref(NULL, E_WARNING, "Unable to write to local file");
1117 		libssh2_channel_free(remote_file);
1118 		RETURN_FALSE;
1119 	}
1120 
1121 	while (sb.st_size) {
1122 		char buffer[8192];
1123 		int bytes_read;
1124 
1125 		bytes_read = libssh2_channel_read(remote_file, buffer, sb.st_size > 8192 ? 8192 : sb.st_size);
1126 		if (bytes_read < 0) {
1127 			php_error_docref(NULL, E_WARNING, "Error reading from remote file");
1128 			libssh2_channel_free(remote_file);
1129 			php_stream_close(local_file);
1130 			RETURN_FALSE;
1131 		}
1132 		php_stream_write(local_file, buffer, bytes_read);
1133 		sb.st_size -= bytes_read;
1134 	}
1135 
1136 	libssh2_channel_free(remote_file);
1137 	php_stream_close(local_file);
1138 
1139 	RETURN_TRUE;
1140 }
1141 /* }}} */
1142 
1143 /* {{{ proto stream ssh2_scp_send(resource session, string local_file, string remote_file[, int create_mode = 0644])
1144  * Send a file via SCP
1145  */
PHP_FUNCTION(ssh2_scp_send)1146 PHP_FUNCTION(ssh2_scp_send)
1147 {
1148 	LIBSSH2_SESSION *session;
1149 	LIBSSH2_CHANNEL *remote_file;
1150 	php_stream *local_file;
1151 	zval *zsession;
1152 	char *local_filename, *remote_filename;
1153 	size_t local_filename_len, remote_filename_len;
1154 	zend_long create_mode = 0644;
1155 	php_stream_statbuf ssb;
1156 	int argc = ZEND_NUM_ARGS();
1157 
1158 	if (zend_parse_parameters(argc, "rss|l", &zsession, &local_filename, &local_filename_len,
1159 													   &remote_filename, &remote_filename_len, &create_mode) == FAILURE) {
1160 		return;
1161 	}
1162 
1163 	SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession);
1164 
1165 	local_file = php_stream_open_wrapper(local_filename, "rb", REPORT_ERRORS, NULL);
1166 	if (!local_file) {
1167 		php_error_docref(NULL, E_WARNING, "Unable to read source file");
1168 		RETURN_FALSE;
1169 	}
1170 
1171 	if (php_stream_stat(local_file, &ssb)) {
1172 		php_error_docref(NULL, E_WARNING, "Failed statting local file");
1173 		php_stream_close(local_file);
1174 		RETURN_FALSE;
1175 	}
1176 
1177 	if (argc < 4) {
1178 		create_mode = ssb.sb.st_mode & 0777;
1179 	}
1180 
1181 	remote_file = libssh2_scp_send_ex(session, remote_filename, create_mode, ssb.sb.st_size, ssb.sb.st_atime, ssb.sb.st_mtime);
1182 	if (!remote_file) {
1183 		int last_error = 0;
1184 		char *error_msg = NULL;
1185 
1186 		last_error = libssh2_session_last_error(session, &error_msg, NULL, 0);
1187 		php_error_docref(NULL, E_WARNING, "Failure creating remote file: %s (%d)", error_msg, last_error);
1188 		php_stream_close(local_file);
1189 		RETURN_FALSE;
1190 	}
1191 	libssh2_channel_set_blocking(remote_file, 1);
1192 
1193 	while (ssb.sb.st_size) {
1194 		char buffer[8192];
1195 		size_t toread = MIN(8192, ssb.sb.st_size);
1196 		size_t bytesread = php_stream_read(local_file, buffer, toread);
1197 		size_t sent = 0;
1198 		size_t justsent = 0;
1199 
1200 		if (bytesread <= 0 || bytesread > toread) {
1201 			php_error_docref(NULL, E_WARNING, "Failed copying file 2");
1202 			php_stream_close(local_file);
1203 			libssh2_channel_free(remote_file);
1204 			RETURN_FALSE;
1205 		}
1206 
1207 
1208 		while (bytesread - sent > 0) {
1209 			if ((justsent = libssh2_channel_write(remote_file, (buffer + sent), bytesread - sent)) < 0) {
1210 
1211 				switch (justsent) {
1212 					case LIBSSH2_ERROR_EAGAIN:
1213 						php_error_docref(NULL, E_WARNING, "Operation would block");
1214 						break;
1215 
1216 					case LIBSSH2_ERROR_ALLOC:
1217 						php_error_docref(NULL,E_WARNING, "An internal memory allocation call failed");
1218 						break;
1219 
1220 					case LIBSSH2_ERROR_SOCKET_SEND:
1221 						php_error_docref(NULL,E_WARNING, "Unable to send data on socket");
1222 						break;
1223 
1224 					case LIBSSH2_ERROR_CHANNEL_CLOSED:
1225 						php_error_docref(NULL,E_WARNING, "The channel has been closed");
1226 						break;
1227 
1228 					case LIBSSH2_ERROR_CHANNEL_EOF_SENT:
1229 						php_error_docref(NULL,E_WARNING, "The channel has been requested to be closed");
1230 						break;
1231 				}
1232 
1233 				php_stream_close(local_file);
1234 				libssh2_channel_free(remote_file);
1235 				RETURN_FALSE;
1236 			}
1237 			sent = sent + justsent;
1238 		}
1239 		ssb.sb.st_size -= bytesread;
1240 	}
1241 
1242 	libssh2_channel_flush_ex(remote_file, LIBSSH2_CHANNEL_FLUSH_ALL);
1243 	php_stream_close(local_file);
1244 	libssh2_channel_free(remote_file);
1245 	RETURN_TRUE;
1246 }
1247 /* }}} */
1248 
1249 /* ***************************
1250    * Direct TCP/IP Transport *
1251    *************************** */
1252 
1253 /* {{{ php_ssh2_direct_tcpip
1254  * Make a stream from a session
1255  */
php_ssh2_direct_tcpip(LIBSSH2_SESSION * session,zend_resource * rsrc,char * host,int port)1256 static php_stream *php_ssh2_direct_tcpip(LIBSSH2_SESSION *session, zend_resource *rsrc, char *host, int port)
1257 {
1258 	LIBSSH2_CHANNEL *channel;
1259 	php_ssh2_channel_data *channel_data;
1260 	php_stream *stream;
1261 
1262 	libssh2_session_set_blocking(session, 1);
1263 
1264 	channel = libssh2_channel_direct_tcpip(session, host, port);
1265 	if (!channel) {
1266 		php_error_docref(NULL, E_WARNING, "Unable to request a channel from remote host");
1267 		return NULL;
1268 	}
1269 
1270 	/* Turn it into a stream */
1271 	channel_data = emalloc(sizeof(php_ssh2_channel_data));
1272 	channel_data->channel = channel;
1273 	channel_data->streamid = 0;
1274 	channel_data->is_blocking = 0;
1275 	channel_data->timeout = 0;
1276 	channel_data->session_rsrc = rsrc;
1277 	channel_data->refcount = NULL;
1278 
1279 	stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r+");
1280 
1281 	return stream;
1282 }
1283 /* }}} */
1284 
1285 /* {{{ php_ssh2_fopen_wrapper_tunnel
1286  * ssh2.tunnel:// fopen wrapper
1287  */
php_ssh2_fopen_wrapper_tunnel(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)1288 static php_stream *php_ssh2_fopen_wrapper_tunnel(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1289 {
1290 	LIBSSH2_SESSION *session = NULL;
1291 	php_stream *stream = NULL;
1292 	php_url *resource;
1293 	char *host = NULL;
1294 	int port = 0;
1295 	zend_resource *rsrc;
1296 
1297 	resource = php_ssh2_fopen_wraper_parse_path(path, "tunnel", context, &session, &rsrc, NULL, NULL);
1298 	if (!resource || !session) {
1299 		return NULL;
1300 	}
1301 
1302 	if (resource->path && SSH2_URL_STR(resource->path)[0] == '/') {
1303 		char *colon;
1304 
1305 		host = SSH2_URL_STR(resource->path) + 1;
1306 		if (*host == '[') {
1307 			/* IPv6 Encapsulated Format */
1308 			host++;
1309 			colon = strstr(host, "]:");
1310 			if (colon) {
1311 				*colon = 0;
1312 				colon += 2;
1313 			}
1314 		} else {
1315 			colon = strchr(host, ':');
1316 			if (colon) {
1317 				*(colon++) = 0;
1318 			}
1319 		}
1320 		if (colon) {
1321 			port = atoi(colon);
1322 		}
1323 	}
1324 
1325 	if ((port <= 0) || (port > 65535) || !host || (strlen(host) == 0)) {
1326 		/* Invalid connection criteria */
1327 		php_url_free(resource);
1328 		zend_list_delete(rsrc);
1329 		return NULL;
1330 	}
1331 
1332 	stream = php_ssh2_direct_tcpip(session, rsrc, host, port);
1333 	if (!stream) {
1334 		zend_list_delete(rsrc);
1335 	}
1336 	php_url_free(resource);
1337 
1338 	return stream;
1339 }
1340 /* }}} */
1341 
1342 static php_stream_wrapper_ops php_ssh2_tunnel_stream_wops = {
1343 	php_ssh2_fopen_wrapper_tunnel,
1344 	NULL, /* stream_close */
1345 	NULL, /* stat */
1346 	NULL, /* stat_url */
1347 	NULL, /* opendir */
1348 	"ssh2.tunnel"
1349 };
1350 
1351 php_stream_wrapper php_ssh2_stream_wrapper_tunnel = {
1352 	&php_ssh2_tunnel_stream_wops,
1353 	NULL,
1354 	0
1355 };
1356 
1357 /* {{{ proto stream ssh2_tunnel(resource session, string host, int port)
1358  * Tunnel to remote TCP/IP host/port
1359  */
PHP_FUNCTION(ssh2_tunnel)1360 PHP_FUNCTION(ssh2_tunnel)
1361 {
1362 	LIBSSH2_SESSION *session;
1363 	php_stream *stream;
1364 	zval *zsession;
1365 	char *host;
1366 	size_t host_len;
1367 	zend_long port;
1368 
1369 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rsl", &zsession, &host, &host_len, &port) == FAILURE) {
1370 		return;
1371 	}
1372 
1373 	SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession);
1374 
1375 	stream = php_ssh2_direct_tcpip(session, Z_RES_P(zsession), host, port);
1376 	if (!stream) {
1377 		RETURN_FALSE;
1378 	}
1379 
1380 	/* Ensure that channels are freed BEFORE the sessions they belong to */
1381 	Z_ADDREF_P(zsession);
1382 
1383 	php_stream_to_zval(stream, return_value);
1384 }
1385 /* }}} */
1386 
1387 /* ******************
1388    * Generic Helper *
1389    ****************** */
1390 
1391 /* {{{ proto stream ssh2_fetch_stream(stream channel, int streamid)
1392  * Fetch an extended data stream
1393  */
PHP_FUNCTION(ssh2_fetch_stream)1394 PHP_FUNCTION(ssh2_fetch_stream)
1395 {
1396 	php_ssh2_channel_data *data, *stream_data;
1397 	php_stream *parent, *stream;
1398 	zval *zparent;
1399 	zend_long streamid;
1400 
1401 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &zparent, &streamid) == FAILURE) {
1402 		return;
1403 	}
1404 
1405 	if (streamid < 0) {
1406 		php_error_docref(NULL, E_WARNING, "Invalid stream ID requested");
1407 		RETURN_FALSE;
1408 	}
1409 
1410 	php_stream_from_zval(parent, zparent);
1411 
1412 	if (parent->ops != &php_ssh2_channel_stream_ops) {
1413 		php_error_docref(NULL, E_WARNING, "Provided stream is not of type " PHP_SSH2_CHANNEL_STREAM_NAME);
1414 		RETURN_FALSE;
1415 	}
1416 
1417 	data = (php_ssh2_channel_data*)parent->abstract;
1418 
1419 	if (!data->refcount) {
1420 		data->refcount = emalloc(sizeof(unsigned char));
1421 		*(data->refcount) = 1;
1422 	}
1423 
1424 	if (*(data->refcount) == 255) {
1425 		php_error_docref(NULL, E_WARNING, "Too many streams associated to a single channel");
1426 		RETURN_FALSE;
1427 	}
1428 
1429 	(*(data->refcount))++;
1430 
1431 	stream_data = emalloc(sizeof(php_ssh2_channel_data));
1432 	memcpy(stream_data, data, sizeof(php_ssh2_channel_data));
1433 	stream_data->streamid = streamid;
1434 
1435 	stream = php_stream_alloc(&php_ssh2_channel_stream_ops, stream_data, 0, "r+");
1436 	if (!stream) {
1437 		php_error_docref(NULL, E_WARNING, "Error opening substream");
1438 		efree(stream_data);
1439 		(data->refcount)--;
1440 		RETURN_FALSE;
1441 	}
1442 
1443 	php_stream_to_zval(stream, return_value);
1444 }
1445 /* }}} */
1446 
1447 /* {{{ proto stream ssh2_send_eof(stream channel)
1448  * Sends EOF to a stream. Primary use is to close stdin of an stdio stream.
1449  */
PHP_FUNCTION(ssh2_send_eof)1450 PHP_FUNCTION(ssh2_send_eof)
1451 {
1452 	php_ssh2_channel_data *data;
1453 	php_stream *parent;
1454 	zval *zparent;
1455 	int ssh2_ret;
1456 
1457 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zparent) == FAILURE) {
1458 		return;
1459 	}
1460 
1461 	php_stream_from_zval(parent, zparent);
1462 	if (parent->ops != &php_ssh2_channel_stream_ops) {
1463 		php_error_docref(NULL, E_WARNING, "Provided stream is not of type " PHP_SSH2_CHANNEL_STREAM_NAME);
1464 		RETURN_FALSE;
1465 	}
1466 
1467 	data = (php_ssh2_channel_data*)parent->abstract;
1468 	if (!data) {
1469 		php_error_docref(NULL, E_WARNING, "Abstract in stream is null");
1470 		RETURN_FALSE;
1471 	}
1472 
1473 	ssh2_ret = libssh2_channel_send_eof(data->channel);
1474 	if (ssh2_ret < 0) {
1475 		php_error_docref(NULL, E_WARNING, "Couldn't send EOF to channel (Return code %d)", ssh2_ret);
1476 		RETURN_FALSE;
1477 	}
1478 
1479 	RETURN_TRUE;
1480 }
1481 /* }}} */
1482 
1483 /*
1484  * Local variables:
1485  * tab-width: 4
1486  * c-basic-offset: 4
1487  * End:
1488  * vim600: noet sw=4 ts=4 fdm=marker
1489  * vim<600: noet sw=4 ts=4
1490  */
1491