planetteamspeak / ts3phpframework

Modern use-at-will framework that provides individual components to manage TeamSpeak 3 Server instances
https://www.planetteamspeak.com
GNU General Public License v3.0
211 stars 59 forks source link

Option SSH: Error handling and documentation #114

Closed Sebbo94BY closed 5 years ago

Sebbo94BY commented 6 years ago

As described here, this framework can connect to the TeamSpeak 3 server via SSH instead of the unsecure telnet client.

First of all: The option is missing in the documentation: TeamSpeak3 > factory() Someone should update it. :)

I've just started implementing this option to be able to secure the connection, but while implementing and testing, I was a bit confused about the behaviour.

This is my used code:

$TS3PHPFramework = new TeamSpeak3();
if(filter_var($request->serverquery_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
    $uri = "serverquery://".$request->serverquery_user.":".$request->serverquery_password."@".$request->serverquery_ip.":".$request->serverquery_port."/";
} elseif(filter_var($request->serverquery_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
    $uri = "serverquery://".$request->serverquery_user.":".$request->serverquery_password."@[".$request->serverquery_ip."]:".$request->serverquery_port."/";
} else {
    echo "ERROR: Invalid IP address provided!";
}

if($request->ssh) {
    $uri = $uri."?ssh=1&server_port=".$request->virtualserver_port;
} else {
    $uri = $uri."?server_port=".$request->virtualserver_port;
}

try {
    $ts3_virtualserver = $TS3PHPFramework->factory("$uri");
    $ts3_virtualserver->clientList();
} catch(TeamSpeak3_Exception $ex) {
    echo "Failed to connect to your server due to this error: " . $ex->getMessage();
}

In my test case I've provided the following information:

My expected behaviour was, that it's throwing the exception $ex as the test server is not reachable via SSH on the port 10011 - this port is only available without SSH, so for telnet:

$ cat ts3server.ini
[...]
query_protocols=raw,ssh
query_port=10011
query_ssh_port=10022

Testing it via a command line reflects my expected behaviour: It's not possible.

The current behaviour is, that no exception is thrown and it tells me, that it just worked fine. How can this be, when it's not working on a command line at all?

Is there any fallback in the factory() function, which tries to establish the connection via telnet, if SSH didn't work?

ronindesign commented 6 years ago
  1. Since doxygen generates docs from code, you can actually add this in the code via PR! [ref] 👍

  2. Yeah, this is a really good point. Definitely needs clarification. Let me double check what fallback behavior is before I speak without knowing!

EDIT: Also, please note the README does include mention of factory ssh URI option [ref], definitely not in the docs though! $uri = "serverquery://username:password@[fe80::250:56ff:fe16:1447]:10022/?ssh=1";

Sebbo94BY commented 6 years ago

Ah, right. Just created the PR. Didn't worked with documentation tools like doxygen since a long time. :D

Sure, let me know the results. :)

ronindesign commented 6 years ago

Looks like maybe you need to specify / switch on serverquery_port?

Suggest something like:

$request->serverquery_port = $request->ssh ? 10022 :  10011;

Relevant code from `` [ref]:


      $this->session = @ssh2_connect($host, $port);
      if($this->session === FALSE)
      {
        throw new TeamSpeak3_Transport_Exception("failed to establish secure shell connection to server '" . $this->config["host"] . ":" . $this->config["port"] . "'");
      }
      if(!@ssh2_auth_password($this->session, $this->config["username"], $this->config["password"]))
      {
        throw new TeamSpeak3_Adapter_ServerQuery_Exception("invalid loginname or password", 0x208);
      }
      $this->stream = @ssh2_shell($this->session, "raw");
      if($this->stream === FALSE)
      {
        throw new TeamSpeak3_Transport_Exception("failed to open a secure shell on server '" . $this->config["host"] . ":" . $this->config["port"] . "'");
      }
Sebbo94BY commented 6 years ago

This is how the URI looked like for testing purposes: serverquery://user:password@ipv4address:10011/?ssh=1&server_port=9987

As my test instance is not reachable via SSH on ServerQuery port 10011, I would expect an exception, but I don't get any exception. Instead I receive the $ts3_virtualserver->clientList(), which I should NOT get at all:

array:1 [▼
  25 => TeamSpeak3_Node_Client {#434 ▼
    #parent: TeamSpeak3_Node_Server {#436 ▶}
    #server: null
    #nodeId: 25
    #nodeList: null
    #nodeInfo: array:30 [▶]
    #storage: []
  }
]

Using the correct values, it should work: serverquery://user:password@ipv4address:10022/?ssh=1&server_port=9987 ...but doesn't: connection to server 'ipv4address:10022' lost

Seems like as there is a worm (bug) somewhere. :D

ronindesign commented 6 years ago

Ohhh, wow gotcha so your issue is that you SHOULD be getting an exception and you're not. Sorry, on the same page now, my bad!

Sebbo94BY commented 6 years ago

Yes, correct.

No problem!

Sebbo94BY commented 6 years ago

Ah, damn! My fault, sorry for wasting your time. :(

I just searched for "ssh" recursive in the library and didn't found it. Checked my version and the releases here and yeah... My installed version is 1.1.28, while the latest is 1.1.33.

SSH support was added in 1.1.33: + added support for secure shell connections

Updated my version now:

$ composer update planetteamspeak/ts3-php-framework
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 0 installs, 1 update, 0 removals
  - Updating planetteamspeak/ts3-php-framework (1.1.28 => 1.1.33): Downloading (100%)
Writing lock file
Generating optimized autoload files

I also had to install the PHP extension ssh2: php-ssh2

Now it's loading that long with the incorrect values, that I receive the error message 504 Gateway Time-out from the web server, what I don't understand as my default_socket_timeout is 60 seconds, so it should stop after 60 seconds, but it doesn't.

You may should use the TS3PHPFrameworks library timeout option for ssh2_connect() too: https://stackoverflow.com/questions/10730556/php-ssh2-connect-implement-a-timeout

Sebbo94BY commented 6 years ago

The interesting thing is, that it reports me the error, when I reload the page, where it should show me the error after it loaded too long: failed to establish secure shell connection to server 'ipv4address:10011'

Mhmm, thought it's maybe due to this different type of exception, but even after I changed it, it didn't returned it faster. :(

catch(TeamSpeak3_Exception $ex):

try {
    $ts3_virtualserver = $TS3PHPFramework->factory("$uri");
    $ts3_virtualserver->clientList();
} catch(TeamSpeak3_Exception $ex) {
    echo "Error: " . $ex->getMessage();
}

catch(TeamSpeak3_Transport_Exception $ex):

try {
    $ts3_virtualserver = $TS3PHPFramework->factory("$uri");
    $ts3_virtualserver->clientList();
} catch(TeamSpeak3_Transport_Exception $ex) {
    echo "Error: " . $ex->getMessage();
}

Both are loading until the web server throws the gateway time-out.

ronindesign commented 6 years ago

Hmm, yeah the failure logic probably needs to be updated so it's a bit more graceful / intuitive.

ronindesign commented 6 years ago

Re: timeouts - https://stackoverflow.com/questions/16002268/prevent-nginx-504-gateway-timeout-using-php-set-time-limit There's numerous ways to correctly handle timeouts in the various connection states.

Re: Catching Exception Timeout - With lower level work such as connections / sockets, sometimes the framework needs a little hand holding.

It may be related to the connection logic blocking on your script and it never resumes after connection fails. Then when you reload page, it's able to resume last error message from thread or something. Honestly have no clue, would depend on your specific environment (nginx, php-fpm, etc).

~In any case, you should definitely be able to make the request / connection, wait for some amount of time, and then either manually check for exception or kill waiting connection logic to continue script and see if exception is returned.~ ~In these cases, you may need to build in "parent" script that's handling connection via framework as a "child" process or some similar logic so that lower level errors can be monitored and/or blocking, non-terminating connection attempts can be killed / continued.~

~How you specifically might go about doing this will depend on your implementation and how much complexity you want to add.~ At the very least though, you should have no problem getting your script / web page to be able to handle full life cycle (load web page, it tried to connect, waits, timeout (or connect!), finish with exception (or do your logic / print, etc).

I have no issue loading web page and connecting successfully. I can try to troubleshoot / reproduce on my end with your specific case: connect to non-ssh port (i.e. 10011) when having ssh option enabled

It's very possible the framework is not catching the system level timeout from ssh library as it should.

Sebbo94BY commented 6 years ago

Thanks - I'll give it a try and report the feedback here.

My environment: nginx 1.10.3-1 php-fpm 7.1.20-1

My /etc/php/7.1/fpm/pool.d/www.conf:

[www]
user = www-data
group = www-data

listen = /run/php/php-fpm.sock
listen.owner = www-data
listen.group = www-data

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

And the PHP section of my /etc/php/7.1/fpm/php.ini contains these settings:

[PHP]
engine = On
short_open_tag = Off
precision = 14
output_buffering = 4096
zlib.output_compression = Off

implicit_flush = Off
unserialize_callback_func =
serialize_precision = -1

disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,

disable_classes =
zend.enable_gc = On
expose_php = Off
max_execution_time = 30
max_input_time = 60
memory_limit = 128M
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = On

variables_order = "GPCS"
request_order = "GP"
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 10M
auto_prepend_file =
auto_append_file =
default_mimetype = "text/html"
default_charset = "UTF-8"

doc_root =
user_dir =

enable_dl = Off
cgi.force_redirect = 0

file_uploads = On
upload_max_filesize = 10M
max_file_uploads = 20

allow_url_fopen = On
allow_url_include = Off

default_socket_timeout = 60

extension=geoip.so
extension=php_openssl.dll