PHP SSH multiple commands - php

I am trying to build a ssh script to run multiple commands on a host. My goal is get the output of each command. My target host is a cisco router and for the following script to execute more that one command i need to run it for each command i want to execute which is not a very elegant solution.
$cmd = array ('sh run int te 1/1', 'sh run int te 1/2');
for ($i = 0; $i <= 1; $i++) {
$connection = ssh2_connect('10.1.1.1', 22);
ssh2_auth_password($connection, 'user', 'pass');
$stream = ssh2_exec($connection, $cmd[$i]);
stream_set_blocking($stream, true);
$stream_out = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO);
echo stream_get_contents($stream_out); }
I have created a loop because i was unable to get the output for each command in the same stream. Since i guess that the php is terminating the ssh connections at the end of each stream.
What i would like to achieve is to execute several commands and get the output in the same stream (if posible).
I am editing the post to answer the sugestions made by #Melvin Koopmans and #LSerni.
if i change the code as sugested (this was someting i also tried before) the second command returns an error. here is the cli output:
the script changed:
$cmds = array ('sh run int te 1/1', 'sh run int te 1/2');
$connection = ssh2_connect('10.1.1.1', 22);
ssh2_auth_password($connection, 'user', 'pass');
foreach ($cmds as $cmd) {
$stream = ssh2_exec( $connection, $cmd );
stream_set_blocking( $stream, true );
$stream_out = ssh2_fetch_stream( $stream, SSH2_STREAM_STDIO );
echo stream_get_contents($stream_out);}
the output from cli
interface TenGigabitEthernet1/1
description trunk
switchport trunk allowed vlan 1,2,3,4,5,6,10
switchport mode trunk
auto qos trust
storm-control broadcast include multicast
storm-control broadcast level 1.00
spanning-tree guard loop
service-policy input AutoQos-4.0-Input-Policy
service-policy output AutoQos-4.0-Output-Policy
ip dhcp snooping trust
end
PHP Warning: ssh2_exec(): Unable to request a channel from remote host in C:\Users\SMS\Downloads\php_scripts\ssh.php on line 13
PHP Warning: stream_set_blocking() expects parameter 1 to be resource, boolean given in C:\Users\SMS\Downloads\php_scripts\ssh.php on line 14
PHP Warning: ssh2_fetch_stream() expects parameter 1 to be resource, boolean given in C:\Users\SMS\Downloads\php_scripts\ssh.php on line 15
PHP Warning: stream_get_contents() expects parameter 1 to be resource, null given in C:\Users\SMS\Downloads\php_scripts\ssh.php on line 16
I am only getting the output from the first command "sh run int te 1/1".

You are repeating the connection stage. Try this instead:
$cmds = array ('sh run int te 1/1', 'sh run int te 1/2');
$connection = ssh2_connect('10.1.1.1', 22);
ssh2_auth_password($connection, 'user', 'pass');
foreach ($cmds as $cmd) {
$stream = ssh2_exec($connection, $cmd);
stream_set_blocking($stream, true);
$stream_out = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO);
echo stream_get_contents($stream_out);
}
I tested now on a Ubuntu machine (14.04-LTS), and it works:
I say 'who', answer was: lserni :0 Jan 21 11:25 (console)
I say 'date', answer was: Thu Feb 16 09:55:52 CET 2017
...well, apart from the fact that I forgot an open console login on that machine :-(

I wouldn't recommend initiating a new connection on every single loop, instead set the connection first then loop over an array of commands and push the output to an array. Like so:
$cmds = [ 'ls', 'ps ux' ];
$connection = ssh2_connect( '127.0.0.1', 22 );
ssh2_auth_password( $connection, 'username', 'password' );
$output = [];
foreach ($cmds as $cmd) {
$stream = ssh2_exec( $connection, $cmd );
stream_set_blocking( $stream, true );
$stream_out = ssh2_fetch_stream( $stream, SSH2_STREAM_STDIO );
$output[] = stream_get_contents($stream_out);
}
This will push all of the output to the array $output.
Now you can loop over $output, or you can choose to give output the key of yoru command so you can access it:
$output[$cmd] = stream_get_contents($stream_out);
And then, for example, call: $output['ls']

Related

Resize pseudo tty allocated by php

I am trying to develop a web console using php and xterm.js,
I managed to get the pseudo tty allocated and attach it to xterm.js via websocket but I am not able to tell the process what is the size of terminal to make it work correctly with the size, and I couldn't find any documentation for this.
// using react/child-process
$process = new Process('/usr/bin/env bash -l', null, null, [
0 => ['pty', 'r'],
1 => ['pty', 'w'],
2 => ['pty', 'w'],
]);
$process->start($this->loop);
I found out run stty on the allocated devpts will do the tricks
if (isset($this->processes[spl_object_id($conn)])) {
$process = $this->processes[spl_object_id($conn)];
$data = unpack('i3', $raw);
$col = $data[2];
$row = $data[3];
$getstream = (function () {
return $this->stream;
});
$stty = new Process("stty cols $col rows $row", null, null, [
$getstream->call($process->stdin), // ex: /dev/pts/0
$getstream->call($process->stdout),
$getstream->call($process->stderr),
]);
$stty->start($this->loop);
}
alternatively can do this
use React\ChildProcess\Process;
$gettty = escapeshellcmd(PHP_BINARY).' -r "echo posix_ttyname(STDIN).PHP_EOL;"';
$bash = "setsid bash -l"; // setsid is important here
$process = new Process(
"$gettty && $bash",
null, null,
[
['pty', 'r'],
['pty', 'w'],
['pty', 'w'],
]
);
// TODO: read the first line and get the tty path so can run stty on it later.
Here is a simple example:
https://gist.github.com/eslym/d3bd7809681aa9c1eb34913043df9bb6

get screen id via ssh in php

I am trying to run a lengthy command from a remote server on php using screen, but i want to make sure that the screen is killed at the end of the command. So i am trying to use "echo $STY" to get the screen id and kill it that way. for some reason, this is not working. Any ideas:
ssh2_exec($conn, 'screen');
$getscreen =ssh2_exec($conn, 'echo $STY');
stream_set_blocking($getscreen, true);
$stream_outA = ssh2_fetch_stream($getscreen, SSH2_STREAM_STDIO);
$valA =fgets($stream_outA,160);
$scrnids =explode('.',$valA);
var_dump($scrnids);
$killcommand = 'screen -X -S '.$scrnids[0].' kill';
$stream = ssh2_exec($conn, $command);
.....
ssh2_exec($conn, $killcommand);
ssh2_exec($conn, 'exit'); //just in case
unset($conn);
the var dump just outputs:
array (size=1)
0 => string '
' (length=1)

PHP: Deadlock through using proc_open("ssh hostname 'tail -F ~somefile' ")

Happens that i need to track file status through ssh by php(using phpunit). But when i trying to launch this code:
$descriptorspec = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$cmd = "ssh hostname 'tail -F ~/test.file'";
$proc = proc_open($cmd, $descriptorspec, $pipes, null);
$str = fgets($pipes[1]);
echo $str;
if (!fclose($pipes[0])) {
throw new Exception("Can't close pipe 0");
}
if (!fclose($pipes[1])) {
throw new Exception("pipe 1");
}
if (!fclose($pipes[2])) {
throw new Exception("pipe 2");
}
$res = proc_close($proc);
nothing happens - no output, and i guess deadlock exucuted: script doesn't exit.
Have any ideas? or suggestions?
tail -F doesn't actually "end" - it just keeps dumping output as it becomes available. That's probably the problem. It's blocking on the fgets().
My recommendation: use phpseclib, a pure PHP SSH2 implementation. eg.
<?php
include('Net/SSH2.php');
$ssh = new Net_SSH2('www.domain.tld');
if (!$ssh->login('username', 'password')) {
exit('Login Failed');
}
function packet_handler($str)
{
echo $str;
}
$ssh->exec('tail -F ~/test.file', 'packet_handler');
?>
Although looking at the implementation, now... it doesn't look like it provides you with any mechanism to prematurely quit either. It'd be nice if it was like "if packet_handler returns false then exec() stops running" or something.
I guess in lieu of that you can use ->setTimeout().

How to gpg encrypt via PHP on a windows platform, under a webserver?

I'm trying to do GPG encryption on a Windows platform, in PHP, running XAMPP.
The webserver is Apache and is running PHP 5.2.9.
I'm using GPG4Win 2.0.4.
I've had success running the encrypt command from the command line. I've changed the recipient and host names.
C:\>C:\PROGRA~1\GNU\GnuPG\pub\gpg.exe --encrypt --homedir C:\DOCUME~1\reubenh.AD\APPLIC~1\gnupg --recipient name#host.com --armor < test.txt > test.enc.txt
In PHP, I'm using proc_open() so I can pipe the content to be encrypted directly to the process, and use the stdout pipe to grab the output.
Following is a snippet of the code:
$cmd = Configure::read('Legacy.GPG.gpg_bin').' --encrypt '.
'--homedir '.Configure::read('Legacy.GPG.gpg_home').' '.
'--recipient '.Configure::read('Legacy.MO.gnugp_keyname').' '.
'--local-user '.'me#host.com'.' '.
'--armor --no-tty --batch --debug-all';
error_log('Encrypting Command line is '.$cmd);
$descriptors = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('file', LOGS.'gpg.log', 'a')
);
$process = proc_open($cmd, $descriptors, $pipes);
if (is_resource($process)) {
error_log(print_r($pipes, true));
list($stdin, $stdout) = $pipes;
error_log('Have pipes');
error_log('body length is '.strlen($this->request['body']));
$ret = fwrite($stdin, $this->request['body'], strlen($this->request['body']));
error_log($ret.' written');
error_log('before fclose()');
fclose($stdin);
error_log('Done with input');
$encryptedData = '';
while(!feof($stdout)) {
$line = fgets($stdout);
error_log('Line read:'.error_log(print_r($line, true)));
$encryptedData .= $line;
}
fclose($stdout);
$return = proc_close($process);
error_log('Encryption process returned '.print_r($return, true));
if ($return == '0') { // ... next step is to sign
The generated command from the first error_log() statement is:
C:\PROGRA~1\GNU\GnuPG\pub\gpg.exe --encrypt --homedir C:\DOCUME~1\reubenh.AD\APPLIC~1\gnupg --recipient name#host.com --local-user me#host.com --armor --no-tty --batch --debug-all
The actual running seems to get as far as "Have pipes". After that, it just stops.
I can also see in the Process Explorer, that the gpg.exe also spawns a gpg2.exe. I suspect that it is this gpg2.exe that I do not have a handle to, is waiting for the input, not the original gpg.exe that I invoked.
I've tried invoking gpg2.exe directly, but a child gpg2.exe is still spawned.
I'd rather use proc_open(), and avoid using disk I/O to provide the content and grab the output, since this will be run on a webserver, and proc_open() will allow me to not bother generating unique files, and then having to clean them up.
I've ended up compromising, to get the solution initially "work", although I'm not very happy about the way it's been done.
The problem seemed to be in two parts.
The first part was the process would hang when trying to sign, and use the --passwd-fd option. If I left this option out, I would get a prompt through the interactive nature of the webserver, enter it manually, and everything would be ok. The workaround, for an unattended application, is to simply have no passphrase. I've seen recommendations in various GnuPG forums to the effect that if your passphrase is going to stored as plain text on the same machine as the private key, then you may as well dispense with the pretence and don't have one. No passphrase is working for the moment.
The second part was that the input was too large. The magic number seemed to be 72kb. Any payload to be encrypted larger than that, using proc_open and a standard pipe just didn't seem to work. As a result, I've opted for temporarily writing the payload to a file, to be read by the proc_open. See as follows:
$tmpfile = tmpfile();
fwrite($tmpfile, $this->request['body']);
fseek($tmpfile, 0);
$cmd = '...'; // similar to question command, but with --sign --encrypt and no --passphrase-fd
$descriptors = array(
0 => $tmpfile,
1 => array('pipe', 'w'),
2 => array('file', LOGS.'gpg.log', 'a')
);
$options = array('bypass_shell' => true);
$process = proc_open($cmd, $descriptors, $pipes, null, null, $options);
if (is_resource($process)) {
stream_set_blocking($pipes[1], 0);
fclose($tmpfile);
$encryptedData = '';
$line = fgets($pipes[1]);
while (!feof($pipes[1])) {
$encryptedData .= $line;
$line =fgets($pipes[1]);
}
fclose($pipes[1]);
$return = proc_close($process);
if ($return = '0') {
// success processing
}
}
I elected not to use list() = $pipes, because only the stdout pipe would actually return in the array.
If anyone had has experience with GPG and PHP in a Windows environment, I'd be more than welcome to hear something, even if it's years down the track.

Using linux 'dialog' command from PHP

I'm trying to write a helper script for doing various admin tasks on a server which can only be done from the command line and trying to use the 'dialog' command to display message boxes, inputs, password prompts etc, however, the needs of this task call for me to process the data in PHP.
I'm having problems getting the dialog command to work in this way and can't figure out what i'm doing wrong.
There's an example here
Unfortunately it doesn't work.
When you run PHP and exec/backtick/system to an external application, the IO doesn't appear to work how you'd expect.
The nearest I can get is using the passthru() command:
<?php
$CMD = "dialog --menu \"Please select\" 10 40 3 backup \"Backup Files\" restore \"Restore Files\"";
passthru($CMD);
?>
This is the only way that PHP will let dialog use the STDOUT properly, anything else results in no display but you can press return to select an option.
I've tried backticks, exec() and system() but nothing seems to work.
What I wondered was how to read STDERR properly from within PHP to get the return value into a variable called $result.
I'm sure some other sysadmins have had to do this before.
My reasons for not using bash for this are that one command I have to execute as a result of a selection produces XML output only and I can't parse that effectively in bash.
Just in case someone else is searching for this:
function dialog ($args) {
$pipes = array (NULL, NULL, NULL);
// Allow user to interact with dialog
$in = fopen ('php://stdin', 'r');
$out = fopen ('php://stdout', 'w');
// But tell PHP to redirect stderr so we can read it
$p = proc_open ('dialog '.$args, array (
0 => $in,
1 => $out,
2 => array ('pipe', 'w')
), $pipes);
// Wait for and read result
$result = stream_get_contents ($pipes[2]);
// Close all handles
fclose ($pipes[2]);
fclose ($out);
fclose ($in);
proc_close ($p);
// Return result
return $result;
}
It requires dialog (apt-get install dialog) and proc_xxx (PHP 4.3.0, PHP 5)
It works, at least for me. :)
You can use proc_open() but not as show above... All dialog boxes do not work in same way. I provides a concrete sample below:
#!/usr/bin/env php
<?php
$pipes = array();
$process = null;
$output = '';
$ret = -1;
/**
* Start process
*
* #param string $cmd Command to execute
* #param bool $wantinputfd Whether or not input fd (pipe) is required
* #retun void
*/
function processStart($cmd, $wantinputfd = false)
{
global $process, $pipes;
$process = proc_open(
$cmd,
array(
0 => ($wantinputfd) ? array('pipe', 'r') : STDIN, // pipe/fd from which child will read
1 => STDOUT,
2 => array('pipe', 'w'), // pipe to which child will write any errors
3 => array('pipe', 'w') // pipe to which child will write any output
),
$pipes
);
}
/**
* Stop process
*
* #return void
*/
function processStop()
{
global $output, $pipes, $process, $ret;
if (isset($pipes[0]) {
fclose($pipes[0]);
usleep(2000);
}
$output = '';
while ($_ = fgets($pipes[3])) {
$output .= $_;
}
$errors = '';
while ($_ = fgets($pipes[2])) {
fwrite(STDERR, $_);
$errors++;
}
if ($errors) {
fwrite(STDERR, "dialog output the above errors, giving up!\n");
exit(1);
}
fclose($pipes[2]);
fclose($pipes[3]);
do {
usleep(2000);
$status = proc_get_status($process);
} while ($status['running']);
proc_close($process);
$ret = $status['exitcode'];
}
// Test for yesno dialog box
processStart("dialog --backtitle 'dialog test' --title 'Little test' --output-fd 3 --yesno 'yesno dialog box' 0 70");
processStop();
echo "Exit code is $ret\n";
// Test for gauge dialog box
processStart("dialog --backtitle 'dialog test' --title 'Little test' --output-fd 3 --gauge 'Gauge dialog box' 0 70 0", true);
sleep(1);
fwrite($pipes[0], "XXX\n0\nFirst step\nXXX\n20\n");
sleep(1);
fwrite($pipes[0], "XXX\n20\nSecond step\nXXX\n50\n");
sleep(1);
fwrite($pipes[0], "XXX\n50\nThird step\nXXX\n80\n");
sleep(1);
fwrite($pipes[0], "XXX\n80\nFourth step\nXXX\n100\n");
sleep(1);
processStop();
echo "Exit code is $ret\n";
// Test for input dialog box
processStart("dialog --backtitle 'dialog test' --title 'Little test' --output-fd 3 --inputbox 'input dialog box' 0 70");
processStop();
echo "Output is $output\n";
echo "Exit code is $ret\n";
// Test for errors output
processStart("dialog --backtitle 'dialog test' --title 'Little test' --output-fd 3 --dummy 'my input box' 0 70");
processStop();
exit(0);
You can use proc_open() to run a command and interact on all pipes, STDIN, STDOUT and STDERR:
$pipes = array(NULL, NULL, NULL);
$proc = proc_open(
"dialog --gauge ..",
array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
),
$pipes
);
print fgets($pipes[2]);
See the manual for more examples.
PHP-GTK looks like a solution for this problem
http://gtk.php.net/
I think you can't run ncurses application through PHP like this - maybe you should take a look at this: http://php.net/manual/de/book.ncurses.php

Categories