Execute application from php

Run executable from php without spawning a shell

I need to call an executable from an imposed context of a PHP script. Both performance and security wise it’s better not to call a shell at all between web server process and executable. Of course I searched the web, without success (in such a PHP context). Many other languages allow that and document it clearly. Alas, backticks, exec() , shell_exec() , passthru() , system() , proc_open() , popen() call a shell. And pcntl_fork() seems unavailable.

How to test if a function calls a shell or not.

This was tested on a Debian 6 64bit with PHP 5.3.3-7+squeeze15 . Test code on http://pastebin.com/y4C7MeJz To get a meaningful test I used a trick which is to ask to execute a shell command not also available as an executable. A good example is umask . Any function returning something like 0022 definitely called a shell. exec() , shell_exec() , passthru() , system() , proc_open() all did. See detailed results on http://pastebin.com/RBcBz02F .

pcntl_fork fails

Now, back the the goal : how to execute arbitrary program without launching a shell ? Php’s exec takes as expected an array of string args instead of a unique string. But pcntl_fork just stops the requests without even a log. Edit: pcntl_fork failure is because the server uses Apache’s mod_php, see http://www.php.net/manual/en/function.pcntl-fork.php#49949 . Edit: added popen() to the tests, following @hakre suggestion. Any hint appreciated.

3 Answers 3

Both performance and security wise it’s better not to call a shell at all between web server process and executable.

About performances, well, yes, php internals forks, and the shell itself forks too so that’s a bit heavy. But you really need to execute a lot of processes to consider those performances issues.

About security, I do not see any issue here. PHP has the escapeshellarg function to sanitize arguments.

The only real problem I met with exec without pcntl is not a resource nor security issue : it is really difficult to create real deamons (without any attachment to its parent, particularily Apache). I solved this by using at , after double-escaping my command:

$arg1 = escapeshellarg($arg1); $arg2 = escapeshellarg($arg2); $command = escapeshellarg("/some/bin $arg1 $arg2 > /dev/null 2>&1 &"); exec("$command | at now -M"); 

To get back to your question, the only way I know to execute programs in a standard (fork+exec) way is to use the PCNTL extension (as already mentionned). Anyway, good luck!

To complete my answer, you can create an exec function yourself that does the same thing as pcntl_fork + pcntl_exec .

I made a my_exec extension that does a classic exec+fork, but actually, I do not think it will solve your issues if you’re running this function under apache, because the same behaviour as pcntl_fork will apply (apache2 will be forked and there may be unexpected behaviours with signal catching and so on when execv does not succeed).

Читайте также:  Write into txt file python

config.m4 the phpize configuration file

PHP_ARG_ENABLE(my_exec_extension, whether to enable my extension, [ --enable-my-extension Enable my extension]) if test "$PHP_MY_EXEC_EXTENSION" = "yes"; then AC_DEFINE(HAVE_MY_EXEC_EXTENSION, 1, [Whether you have my extension]) PHP_NEW_EXTENSION(my_exec_extension, my_exec_extension.c, $ext_shared) fi 

my_exec_extension.c the extension

#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include #include #include #include #define PHP_MY_EXEC_EXTENSION_VERSION "1.0" #define PHP_MY_EXEC_EXTENSION_EXTNAME "my_exec_extension" extern zend_module_entry my_exec_extension_module_entry; #define phpext_my_exec_extension_ptr &my_exec_extension_module_entry // declaration of a custom my_exec() PHP_FUNCTION(my_exec); // list of custom PHP functions provided by this extension // set as the last record to mark the end of list static function_entry my_functions[] = < PHP_FE(my_exec, NULL) >; // the following code creates an entry for the module and registers it with Zend. zend_module_entry my_exec_extension_module_entry = < #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_MY_EXEC_EXTENSION_EXTNAME, my_functions, NULL, // name of the MINIT function or NULL if not applicable NULL, // name of the MSHUTDOWN function or NULL if not applicable NULL, // name of the RINIT function or NULL if not applicable NULL, // name of the RSHUTDOWN function or NULL if not applicable NULL, // name of the MINFO function or NULL if not applicable #if ZEND_MODULE_API_NO >= 20010901 PHP_MY_EXEC_EXTENSION_VERSION, #endif STANDARD_MODULE_PROPERTIES >; ZEND_GET_MODULE(my_exec_extension) char *concat(char *old, char *buf, int buf_len) < int str_size = strlen(old) + buf_len; char *str = malloc((str_size + 1) * sizeof(char)); snprintf(str, str_size, "%s%s", old, buf); str[str_size] = '\0'; free(old); return str; >char *exec_and_return(char *command, char **argv) < int link[2], readlen; pid_t pid; char buffer[4096]; char *output; output = strdup(""); if (pipe(link) < 0) < return strdup("Could not pipe!"); >if ((pid = fork()) < 0) < return strdup("Could not fork!"); >if (pid == 0) < dup2(link[1], STDOUT_FILENO); close(link[0]); if (execv(command, argv) < 0) < printf("Command not found or access denied: %s\n", command); exit(1); >> else < close(link[1]); while ((readlen = read(link[0], buffer, sizeof(buffer))) >0) < output = concat(output, buffer, readlen); >wait(NULL); > return output; > PHP_FUNCTION(my_exec) < char *command; int command_len, argc, i; zval *arguments, **data; HashTable *arr_hash; HashPosition pointer; char **argv; // recovers a string (s) and an array (a) from arguments if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &command, &command_len, &arguments) == FAILURE) < RETURN_NULL(); >arr_hash = Z_ARRVAL_P(arguments); // creating argc and argv from our argument array argc = zend_hash_num_elements(arr_hash); argv = malloc((argc + 1) * sizeof(char *)); argv[argc] = NULL; for ( i = 0, zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer) ) < if (Z_TYPE_PP(data) == IS_STRING) < argv[i] = malloc((Z_STRLEN_PP(data) + 1) * sizeof(char)); argv[i][Z_STRLEN_PP(data)] = '\0'; strncpy(argv[i], Z_STRVAL_PP(data), Z_STRLEN_PP(data)); i++; >> char *output = exec_and_return(command, argv); // freeing allocated memory for (i = 0; (i < argc); i++) < free(argv[i]); >free(argv); // WARNING! I guess there is a memory leak here. // Second arguemnt to 1 means to PHP: do not free memory // But if I put 0, I get a segmentation fault // So I think I do not malloc correctly for a PHP extension. RETURN_STRING(output, 1); > 

shell script run those commands, of course use your own module directory

phpize ./configure make sudo cp modules/my_exec_extension.so /opt/local/lib/php/extensions/no-debug-non-zts-20090626/my_exec.so 
KolyMac:my_fork ninsuo$ php test.php string(329) ".DS_Store .Spotlight-V100 .Trashes .file .fseventsd .hidden .hotfiles.btree .vol AppleScript Applications Developer Installer Log File Library Microsoft Excel Documents Microsoft Word Documents Network System Users Volumes bin cores dev etc home lost+found mach_kernel net opt private sbin tmp usr var vc_command.txt vidotask.txt" 

I am not a C dev, so I think there are cleaner ways to achieve this. But you get the idea.

Читайте также:  Python form submit post

Источник

exec

If the output argument is present, then the specified array will be filled with every line of output from the command. Trailing whitespace, such as \n , is not included in this array. Note that if the array already contains some elements, exec() will append to the end of the array. If you do not want the function to append elements, call unset() on the array before passing it to exec() .

If the result_code argument is present along with the output argument, then the return status of the executed command will be written to this variable.

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.

Returns false on failure.

To get the output of the executed command, be sure to set and use the output parameter.

Errors/Exceptions

Emits an E_WARNING if exec() is unable to execute the command .

Throws a ValueError if command is empty or contains null bytes.

Changelog

Version Description
8.0.0 If command is empty or contains null bytes, exec() now throws a ValueError . Previously it emitted an E_WARNING and returned false .

Examples

Example #1 An exec() example

// outputs the username that owns the running php/httpd process
// (on a system with the «whoami» executable in the path)
$output = null ;
$retval = null ;
exec ( ‘whoami’ , $output , $retval );
echo «Returned with status $retval and output:\n» ;
print_r ( $output );
?>

The above example will output something similar to:

Returned with status 0 and output: Array ( [0] => cmb )

Notes

When allowing user-supplied data to be passed to this function, use escapeshellarg() or escapeshellcmd() to ensure that users cannot trick the system into executing arbitrary commands.

Note:

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.

Note:

On Windows exec() will first start cmd.exe to launch the command. If you want to start an external program without starting cmd.exe use proc_open() with the bypass_shell option set.

See Also

  • system() — Execute an external program and display the output
  • passthru() — Execute an external program and display raw output
  • escapeshellcmd() — Escape shell metacharacters
  • pcntl_exec() — Executes specified program in current process space
  • backtick operator
Читайте также:  Python os read env file

User Contributed Notes 20 notes

This will execute $cmd in the background (no cmd window) without PHP waiting for it to finish, on both Windows and Unix.

function execInBackground ( $cmd ) <
if ( substr ( php_uname (), 0 , 7 ) == «Windows» ) <
pclose ( popen ( «start /B » . $cmd , «r» ));
>
else <
exec ( $cmd . » > /dev/null &» );
>
>
?>

(This is for linux users only).

We know now how we can fork a process in linux with the & operator.
And by using command: nohup MY_COMMAND > /dev/null 2>&1 & echo $! we can return the pid of the process.

This small class is made so you can keep in track of your created processes ( meaning start/stop/status ).

You may use it to start a process or join an exisiting PID process.

// You may use status(), start(), and stop(). notice that start() method gets called automatically one time.
$process = new Process ( ‘ls -al’ );

// or if you got the pid, however here only the status() metod will work.
$process = new Process ();
$process . setPid ( my_pid );
?>

// Then you can start/stop/ check status of the job.
$process . stop ();
$process . start ();
if ( $process . status ()) echo «The process is currently running» ;
>else echo «The process is not running.» ;
>
?>

/* An easy way to keep in track of external processes.
* Ever wanted to execute a process in php, but you still wanted to have somewhat controll of the process ? Well.. This is a way of doing it.
* @compability: Linux only. (Windows does not work).
* @author: Peec
*/
class Process private $pid ;
private $command ;

public function __construct ( $cl = false ) if ( $cl != false ) $this -> command = $cl ;
$this -> runCom ();
>
>
private function runCom () $command = ‘nohup ‘ . $this -> command . ‘ > /dev/null 2>&1 & echo $!’ ;
exec ( $command , $op );
$this -> pid = (int) $op [ 0 ];
>

public function setPid ( $pid ) $this -> pid = $pid ;
>

public function getPid () return $this -> pid ;
>

public function status () $command = ‘ps -p ‘ . $this -> pid ;
exec ( $command , $op );
if (!isset( $op [ 1 ]))return false ;
else return true ;
>

public function start () if ( $this -> command != » ) $this -> runCom ();
else return true ;
>

public function stop () $command = ‘kill ‘ . $this -> pid ;
exec ( $command );
if ( $this -> status () == false )return true ;
else return false ;
>
>
?>

Источник

Оцените статью