Tuesday, July 10, 2012

Using Phpseclib Read/Write to Manage the Terminal


About Phpseclib


Managing terminal commands with php can be a tricky business.  You have several built-in php commands, such as exec(), system() and passthru().  The issue with these commands is that you will be running all of these commands as the www-data user (or equivalent).  No sudo commands can be run, and you cannot log on as another user.  As well, commands are "one-offs", no interaction with the shell is possible.

You could install the PHP SSH2 library, and run shell commands that way... however, if anyone else would like to use the software, they will have to add that php library as well, since it is not a default library.  The other option you have is phpseclib. Phpseclib is a pure php implementation of SSH2 (and tons others).

In this tutorial we will be covering the SSH2 features of phpseclib, specifically read()/write(), since they can be slightly complicated.

Setting up phpseclib


Extremely simple to do:  download either the stable version (currently 0.3.0) or (as I recommend) pulling the latest version from git: https://github.com/phpseclib/phpseclib.git.  The latest version seems to work much better for me.

Place the folder in your php project folder.  Next, in your php page, include the following code.

set_include_path(get_include_path() . PATH_SEPARATOR . 'phpseclib');

include('Net/SSH2.php');


Next, you need to actually set up an ssh connection to your server in your php script.  Usually this would be localhost.  This is done like so:

$ssh = new Net_SSH2('localhost'); //starting the ssh connection to localhost
if (!$ssh->login($username, $password)) { //if you can't log on...
    exit('Login Failed');
}

If you don't receive an error message, congratulations, you are logged in, that simple.  The $username and $password are actual PHP variables that can be collected from user input (Be careful!  That is sensitive information...)

Phpseclib exec() vs read()/write()


Phpseclib has two different ways to execute shell commands.  The first is exec(), which is simple and effective, and looks like so:

$ssh->exec('killall -v apt-get');

Just make sure that whatever variable you named your connection (in this case $ssh) you use when you execute the command. If you want, you can also store the output of the command like so:

$output = $ssh->exec('killall -v apt-get');

Then you can echo or search the output, which is very useful. Essentially, the exec() command logs on, executes command, then exits. For example, the following command DOES NOT output the contents of "/etc/test":

$ssh->exec('cd /etc/test');
$ssh->exec('ls');

This is because the second command does not follow the first. The connection at the end of the exec command is closed, then reopened for the next command. The ls command will list the contents of the default logon folder (Usually the home folder).


However, the read command could accomplish this... and much more.

Using read() and write() together


IMPORTANT NOTE:  The write() command must simulate the enter key, so end all of your command with "\n".
The read() and write() commands must be used together... the best way to show you is with an example....

$ssh->read('/.*@.*[$|#]/', NET_SSH2_READ_REGEX); //start by reading for the command prompt using regex.. we COULD use our username variable in here to make it even better..
$ssh->write("sudo sed -i 's/KEY_PROVINCE=.*/KEY_PROVINCE=\"$key_province\"/g' $var_file\n");
$ssh->setTimeout(10); //right before the read set timeout so php don't crash/timeout on unexpected output
$output = $ssh->read('/.*@.*[$|#]|.*[P|p]assword.*/', NET_SSH2_READ_REGEX); //reading for either the password or the command prompt
echo "$output
";
if (preg_match('/.*[P|p]assword.*/', $output)) { //if we read a prompt asking for the sudo password
 $ssh->write($password."\n"); //write our password (Stored in $_SESSION) to the prompt and "hit" enter (\n)
 $sed_output = $ssh->read('/.*@.*[$|#]/', NET_SSH2_READ_REGEX); //make sure our password worked, read for the command prompt.....
 echo "
Entering SUDO Password... Check for errors....: $sed_output
"; //echo the output of the command... this usually will print errors/output of command... } $ssh->read('/.*@.*[$|#]/', NET_SSH2_READ_REGEX);//Both reads' appear to be required! $ssh->read('/.*@.*[$|#]/', NET_SSH2_READ_REGEX);//Both reads' appear to be required! echo str_repeat(' ',1024*64);//purging output to the browser


Lets go through the steps involved:
Line 1.  The first $ssh->read is using the REGEX flag, which means it accepts regex commands.  What we are doing here is "reading" what the SSH terminal says currently.  Since we have just logged on, it should be at a command prompt.  So I am using a regex that says "Any number of characters, followed by an "@" symbol, followed by any number of characters, follow by either a "$ or #".  For those of you familiar with the command prompt, you will realize that the regex should work for recognizing almost all command prompts. Once we have read the command prompt, we know it is ready to accept commands.
Line 2.  Now we will write our command, a simple sudo sed command.  You can put php variables in the command as needed.
Line 3.  Set the timeout, so we don't have to wait for php to time out if something goes wrong.
Line 4.  Now we are going to "read" the output from the command, so we save the next ssh->read command to "$output".
Line 5. Just echoing the result of the last write command (this has to be done after the "read" because phpseclib needs to read the result of the last command)
Line 7.  Now we are going to search/read the resulting output to see what we find.  We will once again use a regex, except this time we will test for two outputs.  A) The regular command promt OR B) Password.  As you know, since this is a sudo command, we might get prompted for our sudo password.  If that happens, we need to handle that.
Line 8.  We will reach this command IF either A) or B) happened.  Then we will do a preg_match, to see if the output contains the word "Password". If so, we will write the password to the command line, then read for the command prompt again.

We finally reach the end of the password loop, and next we do a final $ssh->read to get the final results.

IMPORTANT NOTE:  Before launching directly into another $ssh->write command, make sure you add ONE MORE $ssh->read command before doing so, both appear to be required.

Phpseclib Logging


You can view the complete phpseclib log by adding the following lines:

define('NET_SSH2_LOGGING', NET_SSH2_LOG_COMPLEX); //add near include lines

echo $ssh->getLog();//add after the command you want to see the log for....


This will output the log to your browser.

Good luck!




5 comments:

  1. Thanks a lot for the detailed example. Saved me a few hours.

    ReplyDelete
  2. very nice manual thank you
    www.mailmaster.in.th

    ReplyDelete
  3. thanks a lot finally got working app after 3 days

    ReplyDelete
  4. Even though i'm a novice i created a working SSH application with php.
    Thank you very much, Mr Jared Swets

    ReplyDelete
  5. You can also use PHPseclib to upload and download multiple files in PHP SFTP. For this there is a component named Finder in Symfony.

    ReplyDelete