I want to build a php based site that (automate) some commands on my Ubuntu Server
first thing I did was going to the file (sudoers) and add the user www-data so I can execute php commands with root privileges!
# running the web apps with root power!!!
www-data ALL=(ALL) NOPASSWD: ALL
then my PHP code was
<?php
$command = "cat /etc/passwd | cut -d\":\" -f1";
echo 'running the command: <b>'.$command."</b><br />";
echo exec($command);
?>
it returns only one user (the last user) !!! how to make it return all users?
thank you
From the PHP manual on exec:
Return Values
The last line from the result of the
command. If you need to execute a
command and have all the data from the
command passed directly back without
any interference, use the passthru()
function.
To get the output of the executed command, be sure to set and use the
output parameter.
So you have to do something similar to this:
<?php
$output = array();
$command = "cat /etc/passwd | cut -d\":\" -f1";
echo 'running the command: <b>'.$command."</b><br />";
exec($command, &$output);
echo implode("<br />\n", $output);
?>
As #benjamin explains, no need to be root or sudo, no need for SUID.
Just pure PHP. I used the field names from posix_getpwnam.
function getUsers() {
$result = [];
/** #see http://php.net/manual/en/function.posix-getpwnam.php */
$keys = ['name', 'passwd', 'uid', 'gid', 'gecos', 'dir', 'shell'];
$handle = fopen('/etc/passwd', 'r');
if(!$handle){
throw new \RuntimeException("failed to open /etc/passwd for reading! ".print_r(error_get_last(),true));
}
while ( ($values = fgetcsv($handle, 1000, ':')) !== false ) {
$result[] = array_combine($keys, $values);
}
fclose($handle);
return $result;
}
It returns an array containing all users, formatted like this:
[
[
'name' => 'root',
'passwd' => 'x',
'uid' => '0',
'gid' => '0',
'gecos' => 'root',
'dir' => '/root',
'shell' => '/bin/bash',
],
[
'name' => 'daemon',
'passwd' => 'x',
'uid' => '1',
'gid' => '1',
'gecos' => 'daemon',
'dir' => '/usr/sbin',
'shell' => '/usr/sbin/nologin',
],
...
]
Like Matt S said, that's an incredibly bad idea to allow www-data root access on your server. The slightest compromise through your web applications could allow anyone full control of your system.
A better idea would be to make separate scripts for specific accessions then use SUID permissions. This means, a specific user (in this case, www-data) can make small changes to the system through the execution of scripts. Still not a good idea, though. You may be able to work around it with suPHP but security is still a major concern.
/etc/passwd is readable by anyone, so you should be able to execute your command without having any special rights (unless PHP prevents it?).
Related
I have an array with object server like this:
Array
(
[0](
(
[id] => 1
[version] => 1
[server_addr] => 192.168.5.210
[server_name] => server1
)
)
[1](
(
[id] => 2
[server_addr] => 192.168.5.211
[server_name] => server2
)
)
)
By running the code below, I'm able to get the desired output
foreach ($model as $server) {
$cpu_usage = shell_exec('sudo path/to/total_cpu_usage.sh '.$server->server_addr);
$memory_usage = shell_exec('sudo path/to/total_memory_usage.sh '.$server->server_addr);
$disk_space = shell_exec('sudo path/to/disk_space.sh '.$server->server_addr);
$inode_space = shell_exec('sudo path/to/inode_space.sh '.$server->server_addr);
$network = shell_exec('sudo path/to/network.sh '.$server->server_addr);
exec('sudo path/to/process.sh '.$server->server_addr, $processString);
$processArray = array();
foreach ($processString as $i) {
$row = explode(" ", preg_replace('/\s+/', ' ', $i));
array_push($processArray,$row);
}
$datetime = shell_exec('sudo path/to/datetime.sh '.$server->server_addr);
echo $cpu_usage;
echo $mem_usage;
echo $disk_space;
......
}
My scripts are similar like:
#!/bin/bash
if [ "$1" == "" ]
then
echo "To start monitor, please provide the server ip:"
read IP
else
IP=$1
fi
ssh root#$IP "date"
But the whole process took like 10 sec for 5 servers compared to 1 server for less than 2 sec. Why is that? Is there anyway to lessen the time? My guess is that the exec command was waiting for the output to be assign to the variable before going to next loop? I tried to google a little bit but most of the answer are for without returning any output at all... I need the output though
You can run your scripts simultaneously with popen() and grab the output later with fread().
//execute
foreach ($model as $server) {
$server->handles = [
popen('sudo path/to/total_cpu_usage.sh '.$server->server_addr, 'r'),
popen('sudo path/to/total_memory_usage.sh '.$server->server_addr, 'r'),
popen('sudo path/to/disk_space.sh '.$server->server_addr, 'r'),
popen('sudo path/to/inode_space.sh '.$server->server_addr, 'r'),
popen('sudo path/to/network.sh '.$server->server_addr, 'r'),
];
}
//grab and store the output, then close the handles
foreach ($model as $server) {
$server->cpu_usage = fread($server->handles[0], 4096);
$server->mem_usage = fread($server->handles[1], 4096);
$server->disk_space = fread($server->handles[2], 4096);
$server->inode_space = fread($server->handles[3], 4096);
$server->network = fread($server->handles[4], 4096);
foreach($server->handles as $h) pclose($h);
}
//print everything
print_r($model);
I tested a similar code to execute 5 scripts that sleep for 2 seconds and the whole thing took only 2.12 seconds
instead of 10.49 seconds with shell_exec().
Update 1: Big thanks to Markus AO for pointing out an optimization potential.
Update 2: Modified the code to remove the possibility of overwrite.
The results are now inside $model.
This can also show which server refused the connection, in case that issue about sshd is affecting you.
All you need to do is add an > /dev/null & at the end on Linux, you wont get the output though, but it will run as a background ( async ) process.
shell_exec('sudo path/to/datetime.sh '.$server->server_addr.' > /dev/null &');
see also this Background process script from my GitHub, ( it has windows compatible background processes )
https://github.com/ArtisticPhoenix/MISC/blob/master/BgProcess.php
Cheers!
I don't know how to make your logic faster but I can tell you how I use to track time of running when I have scripts. At the begin of the script put some var $start = date('c'); and at the end just simple echo ' start='.$start; echo ' end='.date(c);
Yes you're correct: your PHP script is waiting for each response before moving onward.
I presume you're hoping to run the requests to all servers simultaneously, instead of waiting for each server to respond. In that case, assuming you're running a thread-safe version of PHP, look into pthreads. One option is to use cURL multi-exec for making asynchronous requests. Then there's also pcntl_fork that may help you out. Also see this & this thread for possible thread/async approaches.
Aside that, do test and benchmark the shell scripts individually to see where the bottlenecks are, and whether you can speed them up. That may be easier than thread/async setups in PHP. If you have issues with network latency, then write an aggregator shell script that executes the other scripts and returns the results in one request, and only call that in your PHP script.
I am writing my first BASH script to automate configuration of my (Laravel) web projects.
I have some config files (app/config/local/database.php,app/config/app.php) with PHP arrays that I need to access and modify. For example ...
'providers' => array(
/** Append new service provider value, if it does not exist already */
'Illuminate\Foundation\Providers\ArtisanServiceProvider',
'Illuminate\Auth\AuthServiceProvider',
// ...
)
... or ...
'mysql' => array(
/** Replace value under key "database" to "test_db" */
'database' => 'homestead',
'username' => 'homestead',
)
So far I was using sed expressions like this:
$LV_DB_NAME="test_db"
$LV_DB_FILE="app/config/local/database.php"
gsed -i "s/'database' .*/'database' => '$LV_DB_NAME',/g" $LV_FILE_DB_CONFIG
This feels a little messy to me, especially in the case of example 1.
What would be awesome
Is there any way to get PHP arrays to BASH arrays and work with like you would in PHP?
Example 1
if (!in_array($new_provider, $providers)) {
$providers[] = $new_provider;
}
Example 2
$config['mysql']['database'] = $database_name
Update: What would also be awesome
If there is any other common way how to modify PHP arrays using terminal, I would be glad if you point me to it! I'm sure I'm not the only one who needs to modify PHP configuration arrays using terminal.
mTorres was actually right. If your machine has php cli installed (which is probable), You can easily jump into PHP from your bash scripts. There are multiple ways to do so, I finally settled with this:
print_s "Putting data to file \"$PATH\" ... "
export PATH=$PATH
export DATA_JSON=$DATA_JSON
/usr/bin/php << 'EOF'
<?php
$path = getenv("PATH");
$data = getenv("DATA_JSON");
$data = json_decode($data);
$config = (file_exists($path) && is_array($config_data = require($path))) ? $config_data : array();
foreach ($data as $k => $v) {
$config[$k] = $v;
}
file_put_contents($path, "<?php \n \n return ".var_export($config,true).";");
?>
EOF
}
There are some gotchas with passing associative arrays in BASH, check my other question: Pass BASH associative arrays to PHP script
I'm using PHPStorm and PyCharm as my IDE.
Is it possible to debug what's going on in the python script when called from PHP?
Here's my code:
$data = array('token' => $this->user_token, 'user_id' => User::getCurrentUser(),
'archive_id' => $archive_id, 'path' => $path );
$result = shell_exec('python /mypi/Main.py ' . escapeshellarg(json_encode($data)));
Ideally I would like to set a breakpoint in PyCharm and whenever the PHP script is executed, for it to allow me to debug the code.
I am primarily a PHP developer and have limited experience with Perl.
I was tasked with writing a queue script in Perl which checks against a database, and that is all working out great.
The problem I have is that in the Perl script I need to include a database hostname and password.
Right now I have them hard coded, which works fine, but my PHP application uses a global PHP array which holds the database hostname and password.I'd like to be able to use this PHP array in my Perl script.
Here is my PHP array
<?php
return array(
'database' => array(
'master' => array(
'hostname' => 'fd35:4776:6804:2:a::1',
'password' => 'password'
),
'slave' => array(
'hostname' => 'fd35:4776:6804:2:2::2',
'password' => 'password',
'profile' => true
)
)
);
I've tried searching with Google and have read many random posts on line, but I have yet been able to come up with a solution.
Does anyone have any ideas which I could try? If I'm missing any additional input, let me know and I can provide it.
Edit
Hopefully I worded this properly. How would I go about including this PHP array file so that I can manipulate it with Perl?
Alternative solutions are welcome too!
You've discovered one of the many reasons why code makes for bad config files. You should move the information to an actual config file, and access that file from both that .php file and from Perl.
JSON would make a decent file format here.
{
"database": {
"master": {
"hostname": "fd35:4776:6804:2:a::1",
"password": "password"
},
"slave": {
"hostname": "fd35:4776:6804:2:2::2",
"password": "password",
"profile": true
}
}
}
The Perl code would be
use JSON::XS qw( decode_json );
open (my $fh, '<:raw', $config_path)
or die("Can't open config file $config_path: $!\n");
my $file; { local $/; $file = <$fh>; }
my $config = decode_json($file);
On the PHP side, just replace the contents of the file you showed in your post with code to read the config file. I don't know PHP, but it should be quite simple. A quick search shows it might be
return json_decode(file_get_contents($config_path));
It would be simple to provide a short PHP program that dumps the array to a file in JSON format. That file can then be read from Perl using the JSON module.
This is all that is necessary.
<?php
$array = include 'array.php';
$fh = fopen('array.json', 'w');
fwrite($fh, json_encode($array));
fclose($fh);
?>
The resultant JSON file can then be read in a Perl program, like so:
use strict;
use warnings;
use JSON 'from_json';
my $data = do {
open my $fh, '<', 'array.json' or die $!;
local $/;
from_json(<$fh>);
};
use Data::Dump;
dd $data;
output
{
database => {
master => { hostname => "fd35:4776:6804:2:a::1", password => "password" },
slave => {
hostname => "fd35:4776:6804:2:2::2",
password => "password",
profile => bless(do{\(my $o = 1)}, "JSON::XS::Boolean"),
},
},
}
There is PHP::Include, which uses a source filter to let you have PHP blocks in your Perl code to declare variables. It also has a read_file() function that applies such a filter to a single PHP file.
But it seems to expect that your PHP has assignments (e.g. $config = array('database' => array(...) and changes those to Perl variable declarations.
In a few minutes of playing with it, I couldn't get it to do anything useful with your PHP code that uses return.
If you want a more "native Perl" solution, you can pretty much* just search and replace all your "array(" and their matching ")" to "{" and "}". That'll give you a perl datastructure called a "hash of hashes" (note: Unlike PHP, Perl refers to arrays with integer indicies as arrays (and uses the # sigil to denote variables containing them), but refers to array-like things with string indicies as "hashes" (and uses the % sigil to denote variables containing them)). The Perl keywords/concepts you probably want to read up on are:
Perl Data Structures: http://perldoc.perl.org/perldsc.html
and specifically the Hash Of Hashes section: http://perldoc.perl.org/perldsc.html#HASHES-OF-HASHES
and if you dont understand what $hashref = \%hash and %hash{key} and $hashref->{key} mean in Perl, you'd want to read http://perldoc.perl.org/perlref.html
Example code (note how similar the getConfig subroutine is to your PHP code):
#!/usr/bin/perl
use strict;
use warnings;
my $config=getConfig();
print "Database master host = " . $config->{database}{master}{hostname};
print "\n";
print "Database master password = " . $config->{database}{master}{password};
print "\n";
print "Database slave profile = " . $config->{database}{slave}{profile};
print "\n";
sub getConfig{
return {
'database' => {
'master' => {
'hostname' => 'fd35:4776:6804:2:a::1',
'password' => 'password'
},
'slave' => {
'hostname' => 'fd35:4776:6804:2:2::2',
'password' => 'password',
'profile' => 'true'
}
}
};
}
I said "pretty much", because your sample data used the bare word 'true' for the slave->profile value - that's a syntax error in Perl - you can change it to a bare 1, or quote the value as "true" to make it work. In Perl, the digit zero, the string "0" or the empty/nul string "" all evaluate to "false" in a boolean context, anything else evaluates to "true". Take care if you choose to automate PHP to Perl translation, there may be other PHP-isms which could catch you out like that.
So much good information here and it helped me out quite a bit to come up with a working solution.
Here is the perl script I've got working:
#!/usr/bin/perl
use PHP::Include;
include_php_vars( 'config.local.php' );
my $test = \%config;
print $test->{'database'}->{'master'}->{'hostname'};
I also took the PHP array and changed it so that it no longer return array() but $config = array() and then return $config;
This did the trick for me. Thank you!
I have several config files for routers. Variables are between < >
configure
port aps-<x>
description "<VAR1>;<port_APS>"
aps
neighbor <#IP_loopback3_NM_PAIRE>
no revert-time
working-circuit <x/y/z>
exit
sonet-sdh
path
mtu 4494
atm
exit
no shutdown
exit
exit
no shutdown
exit
exit
I wonder how i can store raw text into my database and then use php for building the final config with some values instead of my variables....
Just like a:
sprintf('port aps-%d',$aps_port);
but for the entirely text...
Any ideas?
Thks all!
The easiest way is to use str_replace:
$vars = array(
'x' => 'foo',
'VAR1' => 'bar',
'port_APS' => 'baz',
'#IP_loopback3_NM_PAIRE' => 'hello',
'x/y/z' => 'world',
);
$invars = array();
foreach (array_keys($vars) as $var) {
$invars[] = "<$var>";
}
$text = str_replace($invars, $vars, $text);
Yields:
configure
port aps-foo
description "bar;baz"
aps
neighbor hello
no revert-time
working-circuit world
exit
sonet-sdh
path
mtu 4494
atm
exit
no shutdown
exit
exit
no shutdown
exit
exit