Escaping a lengthy Bash command to use with PHP exec() - php

I have a semi-lengthy shell command that I need to execute from within php. I feel like I have everything properly escaped, however it is not working properly when executed from php's exec() function. When I run the command with exec() it exits without error, but does not run properly.
I am comparing a list file that has email addresses with additional pipe delimited information against a text file containing the md5 version of email addresses to suppress from the original list.
Here is the bash command:
while read line ; do email=`echo "$line" | cut -d '|' -f1` ; if [ $(E=`echo -en "$email" | md5sum | awk '{print $1}'`; grep $E /path/to/suppressions | head -1 ;) ] ; then continue ; else echo $line ; fi done < /path/to/emails
/path/to/suppressions is a text file, containing the md5 version of an email address to suppress (it contains the md5 for "emailtosuppress#domain.com":
edb5feb3be7d0a4e1e250ccdf0c04ace
/path/to/emails is a text file containing a list of email addresses with some other delimeted information:
emailtokeep#domain.com|1000|1
emailtosuppress#domain.com|1000|1
When the command is executed in bash, the output is simply the email address that does NOT exist in the suppression list, and it works flawless:
emailtokeep#domain.com|1000|1
The trouble I am having is when I execute this same command with exec() in php, it outputs all the lines from the "emails" file, without suppressing any matches from the suppression file.
$supCommand = 'while read line ; do email=`echo "$line" | cut -d \'|\' -f1` ; if [ $(E=`echo -en "$email" | md5sum | awk \'{print $1}\'`; grep $E /path/to/suppressions | head -1 ;) ] ; then continue ; else echo $line ; fi done < /path/to/emails' ;
exec($supCommand, $supStatus) ;
print_r($supStatus) ;
I enclosed the command in single quotes, and escaped the four instances of single quotes within the command. If anyone has any ideas I would be extremely GRATEFUL.
Thanks in advance :)

This seems to work:
<?php
$bash = '
mapfile -t md5sums < suppressions
function md5 { printf "%s" "$1" | md5sum | cut -f1 -d" "; }
while IFS="|" read -r email b c; do
this_md5=$(md5 "$email")
for suppress in "${md5sums[#]}"; do
[[ $this_md5 == $suppress ]] && continue 2
done
echo "$email|$b|$c"
done < emails
';
$cmd = "bash -c '$bash'";
$keep = passthru($cmd);
print($keep);
?>

Related

How can I shell_exec this powershell command to get the pid of a process?

I need to run this command from php within a shell exec but it doesn't work
Im runing a node rtsp monitoring aplication from php so i need to get the pid of the node process every time it runs so when i close the window i can close the process so the cpu doesn't exceed it's limits.
I tried
Get-Process | Where-Object ProcessName -eq "node" | ForEach-Object Id
and
Get-Process | Where-Object { $_.ProcessName -eq "node" } | ForEach-Object { $_.Id }
both works in powershell but not in php
this is what i need to run
$cmd = "Get-Process | Where-Object ProcessName -eq 'node' | ForEach-Object Id";
var_dump(shell_exec("powershell.exe -Command " . $cmd));
debugging returns null
var_dump(shell_exec('powershell.exe ' . $cmd));
PHP's shell_exec() function, as the name suggests, uses the host platform's native shell, which on Windows means that the given command line is passed to cmd.exe.
Therefore, if you want to pass metacharacters such as | through to PowerShell, you must quote them, so that cmd.exe doesn't interpret them: You typically do that by enclosing arguments in double quotes ("...") or, less commonly, by quoting (escaping) individual characters with ^.
The best approach in your case is to double-quote the entire PowerShell command using embedded double quotes in the command-line string passed (adapting the technique from your own comment on the question):
$cmd = "Get-Process | Where-Object ProcessName -eq 'node' | ForEach-Object Id";
var_dump(
shell_exec("powershell.exe -Command " . '"' . str_replace('"', '\"', $cmd) . '"')
);
Note the str_replace() call, which ensures that any " chars. embedded in $cmd are properly escaped as \", even though that's not strictly necessary with the specific command in question.
Note that you can greatly streamline your code, in which case you could even get away without double-quoting.
$cmd = "(Get-Process -ErrorAction Ignore -Name node).Id";
var_dump(shell_exec("powershell.exe -Command " . $cmd));

How to grab inout from user and modify file using echo or sed

I am using the below code to get input from user and modify my backup.php file:
#!/bin/bash
read -p "Enter hostname: " hostname
read -p "Enter cPanel username: " user
read -p "Enter password: " pass
echo "\$source_server_ip = \"$hostname\";" >> "backup.php"
echo "\$cpanel_account = \"$user\"; " >> "backup.php"
echo "\$cpanel_password = \"$pass\"; " >> "backup.php"
This works perfectly; however, I want to insert the user-provided details in backup.php at line numbers 4, 5 and 6, respectively.
backup.php contents:
Line no. 1: php
Line no. 2: include xmlapi.php
Line no. 3: (blank)
Line no. 4: $source_server_ip = " ";
Line no. 5: $cpanel_account = " ";
Line no. 6: $cpanel_password = " ";
Line no 7 L code continue..
I want to keep reset of the code as it is and want to make changes in line no 4,5.6 online.
How can I do this? Do I need to use sed?
If I understand correctly, you need to replace/update your 4th, 5th and 6th line with some new lines. You can use sed's substitution command for this:
host_line="\$source_server_ip = \"$hostname\""
user_line="\$cpanel_account = \"$user\""
pass_line="\$cpanel_password = \"$pass\""
sed -i "4s/.*/$host_line/; 5s/.*/$user_line/; 6s/.*/$pass_line/" backup.php
#different notation:
sed -i -e "4s/.*/$host_line/" -e "5s/.*/$user_line/" -e "6s/.*/$pass_line/" backup.php
4s determines on which line the substitution command should take place (in this case 4th line)
.* matches the entire line and substitutes it with your user input
-i is in-place editing. It will edit your file instead of sending the result to stdin
Warning: using sed's s command is really straightforward but it can have some surprising and dangerous results if your variables contain unescaped special characters to sed, for example: &, /, newline, ...
Or make use of the sed's c command:
sed -i "4c \
$host_line
5c \
$user_line
6c \
$pass_line
" backup.php
#different notatiton:
sed -i -e "4c $host_line" -e "5c $user_line" -e "6c $pass_line" backup.php
Warning: you can manage to break this as well, with unescaped newline for example:
pass='mypass
2c oops'
You will either have to escape those special characters to sed or use what I consider the safest solution, awk:
awk -i inplace -v hl="$host_line" -v ul="$user_line" -v pl="$pass_line" '
NR==4 { $0=hl }
NR==5 { $0=ul }
NR==6 { $0=pl }
{ print }' backup.php
Do not forget to use -r option with read or it will treat backslashes specially.
Also, note that even backslashes in your variables will later get interpreted! You can add extra backslashes with parameter expansion if you want to prevent this:
host_line="\$source_server_ip = \"${hostname//\\/\\\\}\""
user_line="\$cpanel_account = \"${user//\\/\\\\}\""
pass_line="\$cpanel_password = \"${pass//\\/\\\\}\""
EDIT: If you just want to insert the 3 lines after the 3th line, use this simple sed:
sed -i "4i $host_line\n$user_line\n$pass_line" backup.php

AWK pipe output from TTY to PHP

I have a tty device (/dev/ttyUSB0), which occasionally outputs a string in the form of Cycle 1: 30662 ms, 117.41 W. I'm using a simple bash script to process it:
#!/bin/sh
stty -F /dev/ttyUSB0 57600
cd /home/pi
while true; do
cat /dev/ttyUSB0 | awk '{ print $0 > "/dev/stderr"; if (/^Cycle/) { print "update kWh.rrd N:" $5 } }' | php5 test.php
sleep 1
done
The test.php script looks like this:
<?php
stream_set_blocking(STDIN, 0);
$line = trim(fgets(STDIN));
$file = 'kwhoutput.txt';
$current = file_get_contents($file);
$current .= $line;
file_put_contents($file, $current);
?>
however, the kwhoutput.txt remains empty. Why is this not working?
awk is buffering your data. Use fflush() to flush the buffers after each output line:
awk '{
print $0 > "/dev/stderr";
if (/^Cycle/) {
print "update kWh.rrd N:" $5;
fflush();
}
}' < /dev/ttyUSB0 | php5 test.php
Also make sure that /dev/ttyUSB0 actually outputs a line (terminated by \n), and not just a string of data.
You should also fix up your php script to:
Read multiple lines and append them one by one (otherwise, the script will skip every other line).
Find out how to append to a file in php. Reading the whole file, concatenating a string in memory, then writing the whole file is not the way to go.

Execute a shell command through php and display it in browser?

I would like to execute a shell command through php and display it in a browser. Is there anyway to do so?
here is my php code : [test.php]
<?php
$number=$_GET["num"];
$date=$_GET["date"];
$output = shell_exec('egrep -w '2012-09-01|974' /home/myquery_test/log/push.log');
echo "<pre>$output</pre>";
?>
When I run this(test.php) file from browser nothing shows up. But when i change the
$output = shell_exec('ls')
its working fine!! Why isn't the egrep/grep command not working??
The egrep command isn't working, because you're using single quotes as a string constant delimiter: 'egreep -w' <==> 2012-09-01|974' <==> /home/myquery_test/log/push.log' <==Just use double quotes in the string, or as string delimiters OR escape the quotes.
shell_exec('egrep -w \'2012-09-01|974\' /home/myquery_test/log/push.log');
shell_exec('egrep -w "2012-09-01|974" /home/myquery_test/log/push.log');
shell_exec("egrep -w '2012-09-01|974' /home/myquery_test/log/push.log");
And, to avoid not getting the warnings and errors that would have brought this issue to light when testing, set your ini to E_STRICT|E_ALL, and fix the warnings, rather then ignoring them. [teasingly: after you're done with that, you might want to consider accepting some answers]I see you've accepted a lot while I was typing this post up :)
Using variables in your command:
$output = shell_exec("tail -f | egrep '$number.*$date' /var/www/myquery_test/log/push.log");
$output = shell_exec('tail -f | egrep "'.$number.'.*'.$date.'" /var/www/myquery_test/log/push.log');
$output = shell_exec("tail -f | egrep \"$number.*$date\" /var/www/myquery_test/log/push.log");

Check to determine if userA has access to fileB (PHP)

PHP has a is_readable function which checks to see if the file is readable by the owner of the script. Is there a corresponding script to see if a file is readable by a specified user, for example
is_readable('Gavrilo Princip', 'black_hand.srj')
Not built in. I don't even think there is a command line utility to check if a certain user has read permissions to a file.
You can write your own function to do the checking though. Look into the fileperms(), fileowner(), filegroup(), and posix_getpwuid() functions.
Check this question
Check file permissions
PHP fileperms http://php.net/manual/en/function.fileperms.php
PHP stat http://www.php.net/manual/en/function.stat.php
The examples in there are for *nix systems. I don't know if it will operate the same on Windows hosts. With these you could get the GID and UID of the file.
I don't know if there is a PHP equivalent that would let you get the UID and/or GID of the particular system user. You may need to get that manually and search against those values. You can find the value typically in the /etc/passwd file
Thanks to the help of Chris and AndrewR I have come up with a, as of yet untested, solution. This solution is implemented in shell, and waits for input from standard in (designed to work with Apache RewriteMap). However, it can easily be modified to be called from either the command line or from a PHP script. It is a little bit more complicated than it has to be because we are piping the input of a function (getfacl) to a while loop. When we do this, it starts a new suprocess, so any variables declared or updated inside this loop (ie. result) will not be available to the outside world. Furthermore, I used getfacl as I can later expand it to also work with ACL permissions as well. Finally, for implementation reasons, I already know the owner of the file (user) before calling this script, however, if this is not the case, one can easily find this from the getfacl command.
#!/bin/bash
#USAGE: STDIN viewer:user:file
while read line
do
viewer=`echo $4 | cut -d ':' -f 1`
user=`echo $4 | cut -d ':' -f 2`
file=`echo $4 | cut -d ':' -f 3`
result=$(
getfacl $file 2>/dev/null | while read line
do
if [[ $user == $viewer ]] && [[ $line =~ ^user: ]]
then
permissions=`echo $line | cut -d ':' -f 3`
if [[ $permissions =~ r ]]
then
echo true
break
fi
elif [[ $user == $viewer ]] && [ $line =~ ^group: ]]
then
#NOTE: I take advantage of the fact that each user has one single group and that group has the same name as the user's name
permissions=`echo $line | cut -d ':' -f 3`
if [[ $permissions =~ r ]]
then
echo true
break
fi
elif [[ $line =~ ^other: ]]
then
permissions=`echo $line | cut -d ':' -f 3`
if [[ $permissions =~ r ]]
then
echo true
break
fi
fi
done
)
if [[ $result == "true" ]]
then
echo true
else
echo false
fi
done

Categories