How does php's proc_open perform interactive operations - php

I try to operate "php xxx.php" with proc_open, it works fine, but I have a problem when I try to interact with mysql, I can't get the response data of mysql, it is output directly to the shell
code:
<?php
$handle = proc_open('mysql -uroot -p', [
["pipe", "r"],
["pipe", "w"],
["pipe", "w"]
], $pipes);
$world = stream_get_contents($pipes[1]);
var_dump($world);
Even if I just execute "mysql", I can't get its output, it seems that its output pipe is redirected somewhere else, how can I make it work as I expected.
I tried "bash -c 'mysql -uroot -p'" but still didn't get the expected behavior
For ease of understanding, I have appended the output:
[root#myrokcy code]# php myfile.php
Enter password: (Handle blocking here manually, otherwise the script won't continue)
string(0) ""
[root#myrokcy code]#
If I adjust the output to stream_get_contents($pipes[2]);
[root#myrokcy code]# php myfile.php
Enter password: (Handle blocking here manually, otherwise the script won't continue)
string(83) "ERROR 1045 (28000): Access denied for user 'root'#'localhost' (using password: NO)
"
[root#myrokcy code]#
I still can't catch "Enter password: ", it is output first and blocks my reading and writing. I want to try to read this content and automate the interaction. Obviously, I fail.
It shouldn't be a permission issue, my test account is "root", system: rocky8.6, it's just that I haven't modified the default "php.ini".
I think I still need to emphasize that the current problem is that the output from "$pipes[1]" cannot be obtained correctly, ""Enter password: "" is forced to output to the shell, and it will block until you operate, "$ world = stream_get_contents($pipes[1])" will not be executed unless you do something to stop the blocking process
If I'm doing it right, is this a bug? if it is i will report it.

Related

Capture exec() error output of background process

I'm using the following command to open a temporary ssh tunnel for making a mysql connection:
exec('ssh -f -L 3400:127.0.0.1:3306 user#example.com sleep 1 > /dev/null');
$connection = #new \mysqli(127.0.0.1, $username, $password, $database, 3400);
This works splendidly. However, once in a while there may be another process using that port in which case it fails.
bind [127.0.0.1]:3400: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 3401
Could not request local forwarding.
What I'd like to do is capture the error output of exec() so that I can retry using a different port. If I add 2>&1 to my command the error output just goes nowhere since stdout is already being piped to /dev/null.
One solution I've come up with is to pipe output to a file instead of /dev/null:
exec('ssh -f -L 3400:127.0.0.1:3306 user#example.com sleep 1 >temp.log 2>&1');
$output = file_get_contents('temp.log');
This works, but it feels messy. I'd prefer not to use the filesystem just to get the error response. Is there a way to capture the error output of this command without piping it to a file?
UPDATE: For the sake of clarity:
(a) Capturing result code using the second argument of exec() does not work in this case. Don't ask me why - but it will always return 0 (success)
(b) stdout must be redirected somewhere or php will not treat it as a background process and script execution will stop until it completes. (https://www.php.net/manual/en/function.exec.php#refsect1-function.exec-notes)
If a program is started with this function, in order for it to continue running in the background, the output of the program must be redirected to a file or another output stream. Failing to do so will cause PHP to hang until the execution of the program ends.
As far as i can tell, exec is not the right tool. For a more controlled approach, you may use proc_open. This may look something like this:
$process = proc_open(
'ssh -f -L 3400:127.0.0.1:3306 user#example.com sleep 1',
[/*stdin*/ 0 => ["pipe", "r"], /*stdout*/ 1 => ["pipe", "w"], /*stderr*/2 => ["pipe", "w"]],
$pipes
);
// Set the streams to non-blocking
// This is required since any unread output on the pipes may result in the process still marked as running
// Note that this does not work on windows due to restrictions in the windows api (https://bugs.php.net/bug.php?id=47918)
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
// Wait a litte bit - you would probably have to loop here and check regulary
// Also note that you may need to read from stdout and stderr from time to time to allow the process to finish
sleep(2);
// The process should now be running as background task
// You can check if the process has finished like this
if (
!proc_get_status($process)["running"] ||
proc_get_status($process)["signaled"] ||
proc_get_status($process)["stopped"] ||
) {
// Process should have stopped - read the output
$stdout = stream_get_contents($pipes[1]) ?: "";
$stderr = stream_get_contents($pipes[2]) ?: "";
// Close everything
#fclose($pipes[1]);
#fclose($pipes[2]);
proc_close($process);
}
You can find more details on that the manual on proc_open
If I add 2>&1 to my command the error output just goes nowhere since stdout is already being piped to /dev/null.
You can redirect stdout to null and stderr to stdout. That would seem to me as the simpler way of doing what you want (minimal modification).
So instead of
>temp.log 2>&1
do:
2>&1 1>/dev/null
Note that the order of the redirects is important.
Test
First we exec without redirection, then we redirect as above to capture stderr.
<?php
$me = $argv[0];
$out = exec("ls -la no-such-file {$me}");
print("The output is '{$out}'\n");
print("\n----\n");
$out = exec("ls -la no-such-file {$me} 2>&1 1>/dev/null");
print("The output is '{$out}'\n");
print("\n");
~
$ php -q tmp.php
ls: cannot access 'no-such-file': No such file or directory
The output is 'The output is '-rw-r--r-- 1 lserni users 265 Oct 25 22:48 tmp.php'
----
The output is 'ls: cannot access 'no-such-file': No such file or directory'
Update
This requirement was not clear initially: "process must detach" (as if it went into the background). Now, the fact is, whatever redirection you do to the original stream via exec() will prevent the process from detaching, because at the time the detachment would happen, the process has not completed, its output is not delivered.
That is also why exec() reports a zero error code - there was no error in spawning. If you want the final result, someone must wait for the process to finalize. So, you have to redirect locally (that way it will be the local file that will wait), then reconnect with whoever it was that waited for the process to finalize and then read the results.
For what you want, exec will never work. You ought to use the proc_* functions.
You might however force detach even so using nohup (you have no control over the spawned pid, so this is less than optimal)
if (file_exists('nohup.out')) { unlink('nohup.out'); }
$out = shell_exec('nohup ssh ... 2>&1 1>/dev/null &');
...still have to wait for connection to be established...
...read nohup.out to verify...
...
...do your thing...
As I said, this is less than optimal. Using proc_*, while undoubtedly more complicated, would allow you to start the ssh connection in tunnel mode without a terminal, and terminate it as soon as you don't need it anymore.
Actually, however, no offense intended, but this is a "X-Y problem". What you want to do is open a SSH tunnel for MySQL. So I'd look into doing just that.

Execution of shell script from PHP fails

I have a shell script that connects to mysql database and exports the data to a csv file. I use .my.cnf file to set the credentials for the database so I don't have to use the -u and -p option for mysql to connect.
The shell script works perfectly when I run from the linux command line. When I try to run the same script from PHP code, it fails with error message saying that user#localhost doesn't has permission to connect to the database.
The shell script is owned by the same user that apache is running under and has all the permissions to the script. What am I missing that is causing the error?
The exact error message being returned to the PHP code is "ERROR 1045 (28000): Access denied for user 'mysqldbuser'#'localhost' (using password: NO)"
Thanks for your help.

php Why can't I run a batch file?

I have tried to run a batch file using PHP:
$script = "\\\MAFINFWWWPV02\D$\WebContent\\engsys.corp.ftr.com\BatchFiles\CopyFiles.bat";
exec($script,$ReturnArray,$ReturnValue);
//shell_exec($script);
//system('cmd /c $script');
//system($script,$ReturnValue);
None of these work! I've tried
var_dump($ReturnValue); echo "<br>";
var_dump($ReturnArray); echo "<br>";
to try and see what is going on, but I get what appears to be normal output like this:
int(1)
array(0) { }
But the files that I'm trying to copy with my bat file, which works fine when run manually, don't get copied!
Edit additional question
Do the \ need to be escaped in the file address?
EDIT 2
Here's what I get from running icacls:
CopyFiles.bat NT AUTHORITY\IUSR:(I)(RX)
NT AUTHORITY\SYSTEM:(I)(F)
NT AUTHORITY\NETWORK:(I)(RX)
CORP\ibb601:(I)(F)
CORP\taw330:(I)(F)
CORP\mmm976:(I)(F)
BUILTIN\Administrators:(I)(F)
BUILTIN\Users:(I)(RX,W)
I have full control and everybody else has at least read/execute.
EDIT 3
I have narrowed it down. It's not that the commands are not working it's that permission is denied to the files. Which I don't understand since everyone has write and read/execute access to the entire folder.
EDIT 4
I am running the commands from above trying to get it to work. I am using a function (var_export(my_exec("shell_exec($script)"));) to print what the errors are to my screen. I keep getting something like this:
'\'shell_exec\' is not recognized as an internal or external command, operable program or batch file.
I get a different one for each shell_exec, system, and exec. It just keeps saying that the command is not recognized. This is being executed on a Windows Server 2012 R2 Standard 64-bit. Is there something that I'm doing wrong with the commands?
Function that I'm using to print errors (I found it on another post):
function my_exec($cmd, $input='')
{
$proc=proc_open($cmd, array(0=>array('pipe', 'r'), 1=>array('pipe', 'w'), 2=>array('pipe', 'w')), $pipes);
fwrite($pipes[0], $input);fclose($pipes[0]);
$stdout=stream_get_contents($pipes[1]);fclose($pipes[1]);
$stderr=stream_get_contents($pipes[2]);fclose($pipes[2]);
$rtn=proc_close($proc);
return array('stdout'=>$stdout,'stderr'=>$stderr,'return'=>$rtn);
}
I finally got it to work. Here's what I ended up with:
$script = chr(92) . chr(92) . 'MAFINFWWWPV02\D$\WebContent\engsys.corp.ftr.com\BatchFiles\CopyFiles.bat';
if (!file_exists($script))
{
var_dump($script); echo " Script<br>";
echo "Script doesn't exist!<br>";
var_dump(!file_exists($script));
}
else
{
system('cmd /c ' . $script); echo " <br>";
}
Sometimes I still get the Script doesn't exist! message, but that is usually when I'm connected to the #1 server and not the #2 server. The .nat looks like this:
echo off
SET source2="\\MAFINFWWWPV02\engsys.corp.ftr.com"
START /wait NET USE L: "\\MAFINFWWWPV01\engsys.corp.ftr.com" Password /user:UserName
xcopy %source2% L: /E /Y /Q
NET USE L: /DELETE
You don't need to escape \ when inside single quotes.
If you want to run a script, make sure is exists and is executable:
$script = '\\MAFINFWWWPV02\engsys.corp.ftr.com\CopyFiles.bat';
if (!file_exists($script) || !is_executable($script)) {
// Im' sorry dave
}
// carry on...
If the script is run through a webserver, make sure the user running the service has execute rights, not your own user! is_executable only checks if the file is an executable, not if the user running the script has executable rights
Since your return code is 1, you probably have some permission issue. A successful execution has return code 0
Also consider the following note from the docs:
Note: When safe mode is enabled, you can only execute files within the safe_mode_exec_dir. For practical reasons, it is currently not allowed to have .. components in the path to the executable.
To have a OS agnostic method of running exernal processes, have a look at the Symfony Process component
after many hours. you should try
c:\\a\\b\\c
instead of
c:\a\b\c

php to call a shell script

purpose: use php to input commands directly into the minecraft server console
Im trying to use a php script (run from browser) to exec() a shell script. when i run the php from a terminal it works! But in the browser, nothing happens.
exec('sudo -u root sh /home/minecraft/whitelist-reload.sh', $out, $ret_val);
When running from terminal, i get a "array 0" but the browser gives me a "array 1"
what is the issue?
and once i run the shell, shouldn't everything after that work as if you were on a terminal?(does it matter what is inside of shell script?)
the shell has all rx permissions and is in the sudoers file as
www-data ALL = NOPASSWD: /home/minecraft/whitelist-reload.sh
The problem is, that you run the script on a terminal as a user that probably has the sudo rights, whereas the apache/webserver user doesn't, so the $ret_val (which is actually just a status code) is set to 1 (means error).
try var_dump($out); in both cases to see the results of your exec call. To do this kind of thing from the browser, you might want to look into proc_open and family, or have a script that is chmod'ed to 777, so the apache user can run it, too. Let that script then call the actual shell script and return it's output back. This is, however very dangerous, and should only be used for testing environments on your own machine. Never do this in production environments!
I have posted a couple of questions here, too that might prove informative:
interaction over ssh
opening a second shell, and load profile variables AND call another script
Turns out... after inputting www-data into the sudoers file, all i needed to do was take of the "-u root" after it

php exec does not work as expected

My system: Ubuntu 11.10, LAMP Stack.
Issue:
I run the following in terminal and it does the back up correctly.
mysqldump -u root dbBugTracker > BAK/dbw.sql
But I include it in my php code like the following and it does NOT work.
exec('/usr/bin/mysqldump -u root dbTracker > BAK/dbT.sql');
Tips:
I tried putting a second parameter in exec but nothing is shown except the word Array. I print it out but nothing in it.
The file dbw.sql is actually created as a result of the exec function but it is 0 bytes.
I tried with the full path and without for mysql and the same result is seen. i.e., 0 bytes.
The folder BAK is within my project folder and I even gave it 777 permissions.
Even tried different file names and databases but the result is the same.
I appreciate any inputs on this. Thank!
MORE INFO:
I added 2>&1 to the exec line and NOW the file contains some text but NOT the DB dump. This is an error and I have no idea how to deal with this :(
Here's the error
mysqldump: Got error: 1045: Access denied for user 'root'#'localhost' (using password: NO) when trying to connect
So this is what the output file (dbw.sql) now contains.
Once again, it works fine when I run the dump from terminal.
You're running that dump command as a different user while on the command line. You are running it as Apache (I assume) when using exec(). Try adding a password parameter to the exec command, or creating an php-specific user in your db with appropriate privileges.
UPDATE:: As I guessed, you are not able to use the root user while executing this dump using PHP. So, create a new user.
First, login to your database from the command line. If you are the root user, don't bother with using -u root:
mysql
Now that you're logged in, go ahead and create a new user for Apache to use:
GRANT ALL ON database_name.* TO yourapacheuser#localhost IDENTIFIED BY 'yourpassword';
Go ahead and logout of mysql:
exit
Next, let's re-work your original code a bit...
$db_user = 'newusername';
$db_pass = 'pass';
$command = "mysqldump --add-drop-table -u $db_user -p$db_pass database_name > backup.file.sql";
$output = `$command`;
echo "Your database has now been backed up.";
Now, to execute the file, run this from the command line:
php path/to/sqldumpfile.php
Hopefully you can adapt this pseudo-code. Best of luck!
How do you print it?
Debug it like this:
<?php
exec('/usr/bin/mysqldump -u root dbTracker > BAK/dbT.sql', $output);
var_dump($output);
First, you should get it working on the command line. Verify that this produces the desired results prior to using PHP's exec():
/usr/bin/mysqldump -u root -p YOUR_PASSWORD dbTracker > BAK/dbT.sql
If it DOES work, then it's an issue somewhere in your PHP config.
The first thing to check is safe_mode. Are you using safe_mode? What version of PHP are you running?
Another possibility may be that your PHP user does not have permission to use the mysqldump binary.

Categories