diff --git a/php_ssh2.h b/php_ssh2.h index c27e002..ad9015d 100644 --- a/php_ssh2.h +++ b/php_ssh2.h @@ -170,6 +170,11 @@ extern php_stream_wrapper php_ssh2_sftp_wrapper; extern int le_ssh2_session; extern int le_ssh2_sftp; +static inline zend_bool php_ssh2_session_is_open(zend_resource *session_rsrc) +{ + return session_rsrc && session_rsrc->ptr != NULL; +} + #if PHP_VERSION_ID < 70300 #define SSH2_URL_STR(a) (a) #define SSH2_URL_LEN(a) strlen(a) diff --git a/ssh2.c b/ssh2.c index 0bb7ce6..96eac7b 100644 --- a/ssh2.c +++ b/ssh2.c @@ -906,6 +906,10 @@ PHP_FUNCTION(ssh2_forward_accept) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + channel = libssh2_channel_forward_accept(data->listener); if (!channel) { @@ -916,6 +920,7 @@ PHP_FUNCTION(ssh2_forward_accept) channel_data->channel = channel; channel_data->streamid = 0; channel_data->is_blocking = 0; + channel_data->timeout = 0; channel_data->session_rsrc = data->session_rsrc; channel_data->refcount = NULL; @@ -1010,12 +1015,28 @@ PHP_FUNCTION(ssh2_poll) res_type = Z_RES_P(tmpzval)->type; res = zend_fetch_resource_ex(tmpzval, "Poll Resource", res_type); if (res_type == le_ssh2_listener) { + php_ssh2_listener_data *listener_data = (php_ssh2_listener_data*)res; + + if (!php_ssh2_session_is_open(listener_data->session_rsrc)) { + php_error_docref(NULL, E_WARNING, "Cannot poll a listener after its session is closed"); + numfds--; + continue; + } + pollfds[i].type = LIBSSH2_POLLFD_LISTENER; - pollfds[i].fd.listener = ((php_ssh2_listener_data*)res)->listener; + pollfds[i].fd.listener = listener_data->listener; } else if ((res_type == le_stream || res_type == le_pstream) && ((php_stream*)res)->ops == &php_ssh2_channel_stream_ops) { + php_ssh2_channel_data *channel_data = (php_ssh2_channel_data*)((php_stream*)res)->abstract; + + if (!php_ssh2_session_is_open(channel_data->session_rsrc)) { + php_error_docref(NULL, E_WARNING, "Cannot poll a channel after its session is closed"); + numfds--; + continue; + } + pollfds[i].type = LIBSSH2_POLLFD_CHANNEL; - pollfds[i].fd.channel = ((php_ssh2_channel_data*)(((php_stream*)res)->abstract))->channel; + pollfds[i].fd.channel = channel_data->channel; /* TODO: Add the ability to select against other stream types */ } else { php_error_docref(NULL, E_WARNING, "Invalid resource type in subarray: %s", zend_rsrc_list_get_rsrc_type(Z_RES_P(tmpzval))); @@ -1114,6 +1135,10 @@ PHP_FUNCTION(ssh2_publickey_add) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + if (zattrs) { HashPosition pos; zval *attr_val; @@ -1209,6 +1234,10 @@ PHP_FUNCTION(ssh2_publickey_remove) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + if (libssh2_publickey_remove_ex(data->pkey, (unsigned char *) algo, algo_len, (unsigned char *) blob, blob_len)) { php_error_docref(NULL, E_WARNING, "Unable to remove %s key", algo); RETURN_FALSE; @@ -1236,6 +1265,10 @@ PHP_FUNCTION(ssh2_publickey_list) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + if (libssh2_publickey_list_fetch(data->pkey, &num_keys, &keys)) { php_error_docref(NULL, E_WARNING, "Unable to list keys on remote server"); RETURN_FALSE; @@ -1389,7 +1422,10 @@ static void php_ssh2_listener_dtor(zend_resource *rsrc) php_ssh2_listener_data *data = (php_ssh2_listener_data*)rsrc->ptr; LIBSSH2_LISTENER *listener = data->listener; - libssh2_channel_forward_cancel(listener); + if (php_ssh2_session_is_open(data->session_rsrc)) { + libssh2_channel_forward_cancel(listener); + } + zend_list_delete(data->session_rsrc); efree(data); } @@ -1399,7 +1435,10 @@ static void php_ssh2_pkey_subsys_dtor(zend_resource *rsrc) php_ssh2_pkey_subsys_data *data = (php_ssh2_pkey_subsys_data*)rsrc->ptr; LIBSSH2_PUBLICKEY *pkey = data->pkey; - libssh2_publickey_shutdown(pkey); + if (php_ssh2_session_is_open(data->session_rsrc)) { + libssh2_publickey_shutdown(pkey); + } + zend_list_delete(data->session_rsrc); efree(data); } diff --git a/ssh2_fopen_wrappers.c b/ssh2_fopen_wrappers.c index f1b3ce1..33cf3be 100644 --- a/ssh2_fopen_wrappers.c +++ b/ssh2_fopen_wrappers.c @@ -48,14 +48,20 @@ static ssize_t php_ssh2_channel_stream_write(php_stream *stream, const char *buf { php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract; ssize_t writestate; - LIBSSH2_SESSION *session; - libssh2_channel_set_blocking(abstract->channel, abstract->is_blocking); - session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); +#ifdef PHP_SSH2_SESSION_TIMEOUT + LIBSSH2_SESSION *session; +#endif + if (!php_ssh2_session_is_open(abstract->session_rsrc)) { + return -1; + } + libssh2_channel_set_blocking(abstract->channel, abstract->is_blocking); #ifdef PHP_SSH2_SESSION_TIMEOUT + session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); + if (abstract->is_blocking) { libssh2_session_set_timeout(session, abstract->timeout); } @@ -70,16 +76,8 @@ static ssize_t php_ssh2_channel_stream_write(php_stream *stream, const char *buf #endif if (writestate == LIBSSH2_ERROR_EAGAIN) { -#if PHP_VERSION_ID < 70400 writestate = 0; -#endif } else if (writestate < 0) { - char *error_msg = NULL; - if (libssh2_session_last_error(session, &error_msg, NULL, 0) == writestate) { - php_error_docref(NULL, E_WARNING, "Failure '%s' (%ld)", error_msg, writestate); - } - - stream->eof = 1; #if PHP_VERSION_ID < 70400 writestate = 0; #endif @@ -96,13 +94,21 @@ static ssize_t php_ssh2_channel_stream_read(php_stream *stream, char *buf, size_ { php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract; ssize_t readstate; + +#ifdef PHP_SSH2_SESSION_TIMEOUT LIBSSH2_SESSION *session; +#endif + + if (!php_ssh2_session_is_open(abstract->session_rsrc)) { + stream->eof = 1; + return -1; + } - stream->eof = libssh2_channel_eof(abstract->channel); libssh2_channel_set_blocking(abstract->channel, abstract->is_blocking); - session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); #ifdef PHP_SSH2_SESSION_TIMEOUT + session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); + if (abstract->is_blocking) { libssh2_session_set_timeout(session, abstract->timeout); } @@ -117,17 +123,15 @@ static ssize_t php_ssh2_channel_stream_read(php_stream *stream, char *buf, size_ #endif if (readstate == LIBSSH2_ERROR_EAGAIN) { -#if PHP_VERSION_ID < 70400 + stream->eof = 0; readstate = 0; -#endif } else if (readstate < 0) { - char *error_msg = NULL; - if (libssh2_session_last_error(session, &error_msg, NULL, 0) == readstate) { - php_error_docref(NULL, E_WARNING, "Failure '%s' (%ld)", error_msg, readstate); - } - - stream->eof = 1; + stream->eof = 0; +#if PHP_VERSION_ID < 70400 readstate = 0; +#endif + } else { + stream->eof = (readstate == 0 && libssh2_channel_eof(abstract->channel)); } return readstate; } @@ -141,8 +145,10 @@ static int php_ssh2_channel_stream_close(php_stream *stream, int close_handle) if (abstract->refcount) { efree(abstract->refcount); } - libssh2_channel_eof(abstract->channel); - libssh2_channel_free(abstract->channel); + if (php_ssh2_session_is_open(abstract->session_rsrc)) { + libssh2_channel_free(abstract->channel); + } + zend_list_delete(abstract->session_rsrc); } efree(abstract); @@ -154,6 +160,10 @@ static int php_ssh2_channel_stream_flush(php_stream *stream) { php_ssh2_channel_data *abstract = (php_ssh2_channel_data*)stream->abstract; + if (!php_ssh2_session_is_open(abstract->session_rsrc)) { + return -1; + } + return libssh2_channel_flush_ex(abstract->channel, abstract->streamid); } @@ -163,6 +173,10 @@ static int php_ssh2_channel_stream_cast(php_stream *stream, int castas, void **r LIBSSH2_SESSION *session; php_ssh2_session_data **session_data; + if (!php_ssh2_session_is_open(abstract->session_rsrc)) { + return FAILURE; + } + session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); session_data = (php_ssh2_session_data **)libssh2_session_abstract(session); @@ -192,8 +206,10 @@ static int php_ssh2_channel_stream_set_option(php_stream *stream, int option, in break; case PHP_STREAM_OPTION_META_DATA_API: - add_assoc_long((zval*)ptrparam, "exit_status", libssh2_channel_get_exit_status(abstract->channel)); - break; + if (php_ssh2_session_is_open(abstract->session_rsrc)) { + add_assoc_long((zval*)ptrparam, "exit_status", libssh2_channel_get_exit_status(abstract->channel)); + } + return PHP_STREAM_OPTION_RETURN_OK; case PHP_STREAM_OPTION_READ_TIMEOUT: ret = abstract->timeout; @@ -207,6 +223,10 @@ static int php_ssh2_channel_stream_set_option(php_stream *stream, int option, in break; case PHP_STREAM_OPTION_CHECK_LIVENESS: + if (!php_ssh2_session_is_open(abstract->session_rsrc)) { + return stream->eof = 1; + } + return stream->eof = libssh2_channel_eof(abstract->channel); break; } @@ -303,6 +323,11 @@ php_url *php_ssh2_fopen_wrapper_parse_path(const char *path, char *type, php_str /* suppress potential warning by passing NULL as resource_type_name */ sftp_data = (php_ssh2_sftp_data *)zend_fetch_resource(Z_RES_P(zresource), NULL, le_ssh2_sftp); if (sftp_data) { + if (!php_ssh2_session_is_open(sftp_data->session_rsrc)) { + php_url_free(resource); + return NULL; + } + /* Want the sftp layer */ Z_ADDREF_P(zresource); *psftp_rsrc = Z_RES_P(zresource); @@ -347,6 +372,11 @@ php_url *php_ssh2_fopen_wrapper_parse_path(const char *path, char *type, php_str php_ssh2_sftp_data *sftp_data; sftp_data = (php_ssh2_sftp_data *)zend_fetch_resource(Z_RES_P(tmpzval), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp); if (sftp_data) { + if (!php_ssh2_session_is_open(sftp_data->session_rsrc)) { + php_url_free(resource); + return NULL; + } + Z_ADDREF_P(tmpzval); *psftp_rsrc = Z_RES_P(tmpzval); *psftp = sftp_data->sftp; @@ -454,6 +484,12 @@ php_url *php_ssh2_fopen_wrapper_parse_path(const char *path, char *type, php_str return NULL; } + if (pubkey_file && privkey_file && + (php_check_open_basedir(pubkey_file) || php_check_open_basedir(privkey_file))) { + php_url_free(resource); + return NULL; + } + session = php_ssh2_session_connect(SSH2_URL_STR(resource->host), resource->port, methods, callbacks); if (!session) { /* Unable to connect! */ @@ -461,13 +497,10 @@ php_url *php_ssh2_fopen_wrapper_parse_path(const char *path, char *type, php_str return NULL; } + ZVAL_RES(&zsession, zend_register_resource(session, le_ssh2_session)); + /* Authenticate */ if (pubkey_file && privkey_file) { - if (php_check_open_basedir(pubkey_file) || php_check_open_basedir(privkey_file)) { - php_url_free(resource); - return NULL; - } - /* Attempt pubkey authentication */ if (!libssh2_userauth_publickey_fromfile(session, username, pubkey_file, privkey_file, password)) { goto session_authed; @@ -483,14 +516,10 @@ php_url *php_ssh2_fopen_wrapper_parse_path(const char *path, char *type, php_str /* Auth failure */ php_url_free(resource); - if (Z_RES(zsession)) { - zend_list_delete(Z_RES(zsession)); - } + zend_list_delete(Z_RES(zsession)); return NULL; session_authed: - ZVAL_RES(&zsession, zend_register_resource(session, le_ssh2_session)); - if (psftp && psftp_rsrc) { LIBSSH2_SFTP *sftp; @@ -505,7 +534,6 @@ php_url *php_ssh2_fopen_wrapper_parse_path(const char *path, char *type, php_str sftp_data->session = session; sftp_data->sftp = sftp; sftp_data->session_rsrc = Z_RES(zsession); - Z_ADDREF(zsession); *psftp_rsrc = zend_register_resource(sftp_data, le_ssh2_sftp); *psftp = sftp; } @@ -785,6 +813,10 @@ PHP_FUNCTION(ssh2_shell_resize) data = (php_ssh2_channel_data*)parent->abstract; + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + libssh2_channel_request_pty_size_ex(data->channel, width, height, width_px, height_px); RETURN_TRUE; @@ -1465,6 +1497,10 @@ PHP_FUNCTION(ssh2_fetch_stream) data = (php_ssh2_channel_data*)parent->abstract; + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + if (!data->refcount) { data->refcount = emalloc(sizeof(unsigned char)); *(data->refcount) = 1; @@ -1485,7 +1521,7 @@ PHP_FUNCTION(ssh2_fetch_stream) if (!stream) { php_error_docref(NULL, E_WARNING, "Error opening substream"); efree(stream_data); - (data->refcount)--; + (*(data->refcount))--; RETURN_FALSE; } @@ -1521,6 +1557,10 @@ PHP_FUNCTION(ssh2_send_signal) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + ssh2_ret = libssh2_channel_signal_ex(data->channel, signal->val, signal->len); if (ssh2_ret < 0) { php_error_docref(NULL, E_WARNING, "Couldn't send signal %s to channel (Return code %d)", signal->val, ssh2_ret); @@ -1558,6 +1598,10 @@ PHP_FUNCTION(ssh2_send_eof) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + ssh2_ret = libssh2_channel_send_eof(data->channel); if (ssh2_ret < 0) { php_error_docref(NULL, E_WARNING, "Couldn't send EOF to channel (Return code %d)", ssh2_ret); diff --git a/ssh2_sftp.c b/ssh2_sftp.c index da4e6bf..14ec86f 100644 --- a/ssh2_sftp.c +++ b/ssh2_sftp.c @@ -36,7 +36,7 @@ void php_ssh2_sftp_dtor(zend_resource *rsrc) return; } - if (data->session_rsrc->ptr != NULL) { + if (php_ssh2_session_is_open(data->session_rsrc)) { libssh2_sftp_shutdown(data->sftp); } @@ -103,6 +103,19 @@ typedef struct _php_ssh2_sftp_handle_data { zend_resource *sftp_rsrc; } php_ssh2_sftp_handle_data; +static inline zend_bool php_ssh2_sftp_handle_session_is_open(php_ssh2_sftp_handle_data *data) +{ + php_ssh2_sftp_data *sftp_data; + + if (!data->sftp_rsrc || !data->sftp_rsrc->ptr) { + return 0; + } + + sftp_data = (php_ssh2_sftp_data*)data->sftp_rsrc->ptr; + + return php_ssh2_session_is_open(sftp_data->session_rsrc); +} + /* {{{ php_ssh2_sftp_stream_write */ #if PHP_VERSION_ID < 70400 @@ -114,6 +127,10 @@ static ssize_t php_ssh2_sftp_stream_write(php_stream *stream, const char *buf, s php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data*)stream->abstract; ssize_t bytes_written; + if (!php_ssh2_sftp_handle_session_is_open(data)) { + return -1; + } + bytes_written = libssh2_sftp_write(data->handle, buf, count); #if PHP_VERSION_ID < 70400 @@ -135,9 +152,13 @@ static ssize_t php_ssh2_sftp_stream_read(php_stream *stream, char *buf, size_t c php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data*)stream->abstract; ssize_t bytes_read; + if (!php_ssh2_sftp_handle_session_is_open(data)) { + return -1; + } + bytes_read = libssh2_sftp_read(data->handle, buf, count); - stream->eof = (bytes_read <= 0 && bytes_read != LIBSSH2_ERROR_EAGAIN); + stream->eof = (bytes_read == 0); #if PHP_VERSION_ID < 70400 return (size_t)(bytes_read<0 ? 0 : bytes_read); @@ -152,9 +173,7 @@ static ssize_t php_ssh2_sftp_stream_read(php_stream *stream, char *buf, size_t c static int php_ssh2_sftp_stream_close(php_stream *stream, int close_handle) { php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data*)stream->abstract; - php_ssh2_sftp_data *sftp_data = (php_ssh2_sftp_data*)data->sftp_rsrc->ptr; - - if (sftp_data && sftp_data->session_rsrc->ptr != NULL) { + if (php_ssh2_sftp_handle_session_is_open(data)) { libssh2_sftp_close(data->handle); } zend_list_delete(data->sftp_rsrc); @@ -170,6 +189,10 @@ static int php_ssh2_sftp_stream_seek(php_stream *stream, zend_off_t offset, int { php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data*)stream->abstract; + if (!php_ssh2_sftp_handle_session_is_open(data)) { + return -1; + } + switch (whence) { case SEEK_END: { @@ -214,6 +237,10 @@ static int php_ssh2_sftp_stream_fstat(php_stream *stream, php_stream_statbuf *ss php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data*)stream->abstract; LIBSSH2_SFTP_ATTRIBUTES attrs; + if (!php_ssh2_sftp_handle_session_is_open(data)) { + return -1; + } + if (libssh2_sftp_fstat(data->handle, &attrs)) { return -1; } @@ -295,12 +322,27 @@ static ssize_t php_ssh2_sftp_dirstream_read(php_stream *stream, char *buf, size_ { php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data*)stream->abstract; php_stream_dirent *ent = (php_stream_dirent*)buf; - int bytesread = libssh2_sftp_readdir(data->handle, ent->d_name, sizeof(ent->d_name) - 1, NULL); + int bytesread; zend_string *basename; - if (bytesread <= 0) { + if (!php_ssh2_sftp_handle_session_is_open(data)) { + stream->eof = 0; + return -1; + } + + bytesread = libssh2_sftp_readdir(data->handle, ent->d_name, sizeof(ent->d_name) - 1, NULL); + + if (bytesread < 0) { + stream->eof = 0; + return bytesread; + } + + if (bytesread == 0) { + stream->eof = 1; return 0; } + + stream->eof = 0; ent->d_name[bytesread] = 0; basename = php_basename(ent->d_name, bytesread, NULL, 0); @@ -322,9 +364,7 @@ static ssize_t php_ssh2_sftp_dirstream_read(php_stream *stream, char *buf, size_ static int php_ssh2_sftp_dirstream_close(php_stream *stream, int close_handle) { php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data*)stream->abstract; - php_ssh2_sftp_data *sftp_data = (php_ssh2_sftp_data*)data->sftp_rsrc->ptr; - - if (sftp_data && sftp_data->session_rsrc->ptr != NULL) { + if (php_ssh2_sftp_handle_session_is_open(data)) { libssh2_sftp_close(data->handle); } zend_list_delete(data->sftp_rsrc); @@ -360,7 +400,7 @@ static php_stream *php_ssh2_sftp_dirstream_opener(php_stream_wrapper *wrapper, c php_url *resource; resource = php_ssh2_fopen_wrapper_parse_path(filename, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); - if (!resource || !session || !sftp) { + if (!resource || !session || !sftp || !sftp_rsrc) { return NULL; } @@ -401,23 +441,22 @@ static int php_ssh2_sftp_urlstat(php_stream_wrapper *wrapper, const char *url, i LIBSSH2_SFTP *sftp = NULL; zend_resource *rsrc = NULL, *sftp_rsrc = NULL; php_url *resource; + int result; resource = php_ssh2_fopen_wrapper_parse_path(url, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); if (!resource || !session || !sftp || !resource->path) { return -1; } - if (libssh2_sftp_stat_ex(sftp, SSH2_URL_STR(resource->path), SSH2_URL_LEN(resource->path), - (flags & PHP_STREAM_URL_STAT_LINK) ? LIBSSH2_SFTP_LSTAT : LIBSSH2_SFTP_STAT, &attrs)) { - php_url_free(resource); - //zend_list_delete(sftp_rsrcid); - return -1; - } + result = libssh2_sftp_stat_ex(sftp, SSH2_URL_STR(resource->path), SSH2_URL_LEN(resource->path), + (flags & PHP_STREAM_URL_STAT_LINK) ? LIBSSH2_SFTP_LSTAT : LIBSSH2_SFTP_STAT, &attrs); php_url_free(resource); + zend_list_delete(sftp_rsrc); - /* parse_path addrefs the resource, but we're not holding on to it so we have to delref it before we leave */ - //zend_list_delete(sftp_rsrcid); + if (result) { + return -1; + } return php_ssh2_sftp_attr2ssb(ssb, &attrs); } @@ -443,8 +482,7 @@ static int php_ssh2_sftp_unlink(php_stream_wrapper *wrapper, const char *url, in result = libssh2_sftp_unlink(sftp, SSH2_URL_STR(resource->path)); php_url_free(resource); - - //zend_list_delete(sftp_rsrcid); + zend_list_delete(sftp_rsrc); /* libssh2 uses 0 for success and the streams API uses 0 for failure, so invert */ return (result == 0) ? -1 : 0; @@ -486,8 +524,7 @@ static int php_ssh2_sftp_rename(php_stream_wrapper *wrapper, const char *url_fro result = libssh2_sftp_rename(sftp, SSH2_URL_STR(resource->path), SSH2_URL_STR(resource_to->path)); php_url_free(resource); php_url_free(resource_to); - - //zend_list_delete(sftp_rsrcid); + zend_list_delete(sftp_rsrc); /* libssh2 uses 0 for success and the streams API uses 0 for failure, so invert */ return (result == 0) ? -1 : 0; @@ -522,8 +559,7 @@ static int php_ssh2_sftp_mkdir(php_stream_wrapper *wrapper, const char *url, int result = libssh2_sftp_mkdir(sftp, SSH2_URL_STR(resource->path), mode); php_url_free(resource); - - //zend_list_delete(sftp_rsrcid); + zend_list_delete(sftp_rsrc); /* libssh2 uses 0 for success and the streams API uses 0 for failure, so invert */ return (result == 0) ? -1 : 0; @@ -550,8 +586,7 @@ static int php_ssh2_sftp_rmdir(php_stream_wrapper *wrapper, const char *url, int result = libssh2_sftp_rmdir(sftp, SSH2_URL_STR(resource->path)); php_url_free(resource); - - //zend_list_delete(sftp_rsrcid); + zend_list_delete(sftp_rsrc); /* libssh2 uses 0 for success and the streams API uses 0 for failure, so invert */ return (result == 0) ? -1 : 0; @@ -638,6 +673,10 @@ PHP_FUNCTION(ssh2_sftp_rename) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + RETURN_BOOL(!libssh2_sftp_rename_ex(data->sftp, src->val, src->len, dst->val, dst->len, LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE)); } @@ -659,6 +698,10 @@ PHP_FUNCTION(ssh2_sftp_unlink) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + RETURN_BOOL(!libssh2_sftp_unlink_ex(data->sftp, filename->val, filename->len)); } /* }}} */ @@ -686,6 +729,10 @@ PHP_FUNCTION(ssh2_sftp_mkdir) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + if (recursive) { /* Just attempt to make every directory, some will fail, but we only care about the last success/failure */ p = dirname->val; @@ -718,6 +765,10 @@ PHP_FUNCTION(ssh2_sftp_rmdir) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + RETURN_BOOL(!libssh2_sftp_rmdir_ex(data->sftp, dirname->val, dirname->len)); } /* }}} */ @@ -744,6 +795,10 @@ PHP_FUNCTION(ssh2_sftp_chmod) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + attrs.permissions = mode; attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; @@ -771,6 +826,10 @@ static void php_ssh2_sftp_stat_func(INTERNAL_FUNCTION_PARAMETERS, int stat_type) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + if (libssh2_sftp_stat_ex(data->sftp, path->val, path->len, stat_type, &attrs)) { php_error_docref(NULL, E_WARNING, "Failed to stat remote file"); RETURN_FALSE; @@ -835,6 +894,10 @@ PHP_FUNCTION(ssh2_sftp_symlink) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + RETURN_BOOL(!libssh2_sftp_symlink_ex(data->sftp, targ->val, targ->len, link->val, link->len, LIBSSH2_SFTP_SYMLINK)); } /* }}} */ @@ -857,6 +920,10 @@ PHP_FUNCTION(ssh2_sftp_readlink) RETURN_FALSE; } + if (!php_ssh2_session_is_open(data->session_rsrc)) { + RETURN_FALSE; + } + if ((targ_len = libssh2_sftp_symlink_ex(data->sftp, link->val, link->len, targ, 8192, LIBSSH2_SFTP_READLINK)) < 0) { php_error_docref(NULL, E_WARNING, "Unable to read link '%s'", ZSTR_VAL(link)); RETURN_FALSE; @@ -884,7 +951,7 @@ PHP_FUNCTION(ssh2_sftp_realpath) RETURN_FALSE; } - if (data->session_rsrc->ptr == NULL) { + if (!php_ssh2_session_is_open(data->session_rsrc)) { RETURN_FALSE; } @@ -905,4 +972,3 @@ PHP_FUNCTION(ssh2_sftp_realpath) * indent-tabs-mode: t * End: */ - diff --git a/tests/ssh2_channel_stream_semantics.phpt b/tests/ssh2_channel_stream_semantics.phpt new file mode 100644 index 0000000..1080ee9 --- /dev/null +++ b/tests/ssh2_channel_stream_semantics.phpt @@ -0,0 +1,66 @@ +--TEST-- +SSH2 channel streams distinguish backpressure, errors, and clean EOF +--SKIPIF-- + +--FILE-- +/dev/null'); + +if ($writeChannel === false) { + throw new RuntimeException('Failed to open the SSH write channel.'); +} + +stream_set_blocking($writeChannel, true); + +var_dump(ssh2_send_eof($writeChannel)); +var_dump(@fwrite($writeChannel, 'x') === false); +var_dump(!feof($writeChannel)); + +fclose($writeChannel); + +$readChannel = ssh2_exec($session, 'true'); + +if ($readChannel === false) { + throw new RuntimeException('Failed to open the SSH read channel.'); +} + +stream_set_blocking($readChannel, true); + +var_dump(fread($readChannel, 1) === ''); +var_dump(feof($readChannel)); + +fclose($readChannel); +ssh2_disconnect($session); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/ssh2_resource_lifetime.phpt b/tests/ssh2_resource_lifetime.phpt new file mode 100644 index 0000000..0a87085 --- /dev/null +++ b/tests/ssh2_resource_lifetime.phpt @@ -0,0 +1,167 @@ +--TEST-- +SSH2 child resources fail safely after their parent session is closed +--SKIPIF-- + +--FILE-- + $channel, 'events' => SSH2_POLLIN]]; +var_dump(@ssh2_poll($poll, 0) === 0); +var_dump(fclose($stderr)); +var_dump(fclose($channel)); + +unset($stderr, $channel, $channelSession); + +$sftpSession = $connect(); +$sftp = ssh2_sftp($sftpSession); + +if ($sftp === false) { + throw new RuntimeException('Failed to open the SFTP subsystem.'); +} + +$root = ssh2t_tempnam(); +$filePath = $root . '/file.txt'; +$otherPath = $root . '/other.txt'; +$linkPath = $root . '/link.txt'; + +if (!ssh2_sftp_mkdir($sftp, $root, 0700)) { + throw new RuntimeException('Failed to create the SFTP fixture directory.'); +} + +$file = fopen('ssh2.sftp://' . (int) $sftp . $filePath, 'w+'); +$directory = opendir('ssh2.sftp://' . (int) $sftp . $root); + +if ($file === false || $directory === false) { + throw new RuntimeException('Failed to open the SFTP fixture streams.'); +} + +var_dump(ssh2_disconnect($sftpSession)); +var_dump(@fwrite($file, 'test') === false); +var_dump(@fread($file, 1) === false); +var_dump(fseek($file, 0, SEEK_END) === -1); +var_dump(fstat($file) === false); +var_dump(readdir($directory) === false); +var_dump(feof($directory) === false); +var_dump(!ssh2_sftp_rename($sftp, $filePath, $otherPath)); +var_dump(!ssh2_sftp_unlink($sftp, $filePath)); +var_dump(!ssh2_sftp_mkdir($sftp, $otherPath)); +var_dump(!ssh2_sftp_rmdir($sftp, $root)); +var_dump(!ssh2_sftp_chmod($sftp, $filePath, 0600)); +var_dump(ssh2_sftp_stat($sftp, $filePath) === false); +var_dump(ssh2_sftp_lstat($sftp, $filePath) === false); +var_dump(!ssh2_sftp_symlink($sftp, $filePath, $linkPath)); +var_dump(ssh2_sftp_readlink($sftp, $linkPath) === false); +var_dump(ssh2_sftp_realpath($sftp, $filePath) === false); +var_dump(fclose($file)); +closedir($directory); + +unset($directory, $file, $sftp, $sftpSession); + +$cleanupSession = $connect(); +$cleanupSftp = ssh2_sftp($cleanupSession); + +if ($cleanupSftp === false + || !ssh2_sftp_unlink($cleanupSftp, $filePath) + || !ssh2_sftp_rmdir($cleanupSftp, $root)) { + throw new RuntimeException('Failed to clean up the SFTP fixture.'); +} + +unset($cleanupSftp); +ssh2_disconnect($cleanupSession); +unset($cleanupSession); + +$listenerSession = $connect(); +$listener = ssh2_forward_listen($listenerSession, 0); + +if ($listener === false) { + throw new RuntimeException('Failed to open an SSH remote-forward listener.'); +} + +var_dump(ssh2_disconnect($listenerSession)); +var_dump(ssh2_forward_accept($listener) === false); + +$poll = [['resource' => $listener, 'events' => SSH2_POLLIN]]; +var_dump(@ssh2_poll($poll, 0) === 0); + +unset($listener, $listenerSession); +gc_collect_cycles(); + +var_dump(count(get_resources('SSH2 SFTP')) === $sftpBaseline); +var_dump(count(get_resources('SSH2 Session')) === $sessionBaseline); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/ssh2_sftp_stream_semantics.phpt b/tests/ssh2_sftp_stream_semantics.phpt new file mode 100644 index 0000000..3b74ba2 --- /dev/null +++ b/tests/ssh2_sftp_stream_semantics.phpt @@ -0,0 +1,96 @@ +--TEST-- +SFTP streams distinguish errors from clean completion +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/ssh2_sftp_wrapper_auth_failure.phpt b/tests/ssh2_sftp_wrapper_auth_failure.phpt new file mode 100644 index 0000000..e70585a --- /dev/null +++ b/tests/ssh2_sftp_wrapper_auth_failure.phpt @@ -0,0 +1,40 @@ +--TEST-- +Direct SFTP wrapper authentication failures release their sessions +--SKIPIF-- + +--FILE-- + [ + 'username' => TEST_SSH2_USER, + 'password' => 'invalid-ssh2-test-password', + ], +]); +$url = sprintf( + 'ssh2.sftp://%s:%d%s/missing.txt', + TEST_SSH2_HOSTNAME, + TEST_SSH2_PORT, + rtrim(TEST_SSH2_TEMPDIR, '/'), +); +$fileDescriptorCount = static fn (): int => count(scandir('/proc/self/fd')) - 2; + +$baseline = $fileDescriptorCount(); +$authenticationFailed = @fopen($url, 'rb', false, $context) === false; + +gc_collect_cycles(); + +var_dump($authenticationFailed); +var_dump($fileDescriptorCount() === $baseline); +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/tests/ssh2_sftp_wrapper_release.phpt b/tests/ssh2_sftp_wrapper_release.phpt new file mode 100644 index 0000000..e8a1268 --- /dev/null +++ b/tests/ssh2_sftp_wrapper_release.phpt @@ -0,0 +1,61 @@ +--TEST-- +One-shot SFTP wrapper operations release their resource references +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/ssh2_sftp_wrapper_session_ownership.phpt b/tests/ssh2_sftp_wrapper_session_ownership.phpt new file mode 100644 index 0000000..2c2952e --- /dev/null +++ b/tests/ssh2_sftp_wrapper_session_ownership.phpt @@ -0,0 +1,52 @@ +--TEST-- +Direct SFTP wrapper sessions are released with their streams +--SKIPIF-- + +--FILE-- + [ + 'username' => TEST_SSH2_USER, + 'password' => TEST_SSH2_PASS, + ], +]); + +$stream = fopen($url, 'wb', false, $context); + +var_dump(is_resource($stream)); +var_dump(fwrite($stream, 'test') === 4); +var_dump(fclose($stream)); + +gc_collect_cycles(); + +var_dump(count(get_resources('SSH2 SFTP')) === $sftpBaseline); +var_dump(count(get_resources('SSH2 Session')) === $sessionBaseline); + +$session = ssh2_connect(TEST_SSH2_HOSTNAME, TEST_SSH2_PORT); + +if ($session === false || !ssh2t_auth($session)) { + throw new RuntimeException('Failed to connect to the SSH fixture.'); +} + +$sftp = ssh2_sftp($session); + +if ($sftp === false || !ssh2_sftp_unlink($sftp, $path)) { + throw new RuntimeException('Failed to clean up the SFTP fixture.'); +} +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true)