popen for cli commands and pipes in php

Source: Pipes 1/3 by Jonah G.S. on Flickr.com
I got a question today about using commands that pipe output to other commands within PHP applications.

There are two functions in PHP that are perfect for the task: popen and proc_open.

The function "popen" opens a process file pointer, basically you have a pointer during the execution of a process. This functionality is often useful when you have one-way traffic (like piping commands on command line).

The function "proc_open" behaves the same as popen, but gives you access to the input and the output, which makes it very useful for reading and writing as you go along.

So let's say you have logic that generates a crontab entry, you can always do this using the commandline.

/usr/bin/php crontab.php | /usr/bin/crontab
But when you want to run it as a complete process, you can go about using exec, shell_exec, passthru or system and fiddle with escapeshellcmd. But often this looks messy and not reusable.

A better approach would be to use "popen". A small example would look something like this:

<?php

$output = '*/5 * * * * /bin/echo "Hello World!" 2>&1' . PHP_EOL;
$command = '/usr/bin/crontab';

var_dump(cmdPipe($output, $command));
/**
 * Functionality to pipe output
 *
 * @param string $input The command that needs to be executed
 * @param string $commandline The command the first command needs to be
 * piped to
 * @return string The output of the given command
 * @throws \RuntimeException
 */
function cmdPipe($input, $commandline)
{
    if (false === ($pipe = popen("echo \"$input\"|$commandline", 'r'))) {
        throw new \RuntimeException('Cannot open pipe');
    }
    $output = '';
    while (!feof($pipe)) {
        $output .= fread($pipe, 1024);
    }
    pclose($pipe);
    return $output;
}
DISCLAIMER: This is not secure code and should not be used as-is in production environments!

Build it as a feature element and you now have a piping functionality you can nest, embed but most of all: reuse.

Comments

  1. I suppose you say that this is no secure code because of the statement `"echo \"$input\"|$commandline"` that could inject commands into the shell. You can fix that by encoding $input with base64_encode() and to sent that to the pipe, adding `| base64 --decode` before `|$commandline`.

    ReplyDelete
    Replies
    1. I wouldn't use base64 encoding/decoding as your defence against malicious commands as you just convert strings into another format: base64_encode('evil-script') → base64_decode('ZXZpbC1zY3JpcHQK') → 'evil-script'.

      I would definitely look at filtering and validation functions like "filter_var", "ctype_*" and "is_*", or use filtering and validation features of a PHP framework as they offer custom types like credit cards, uuid, barcode, url and more.

      The reason I say the code is not secure is that you cannot trust any data, no matter where it comes from: OCR converted images to text, phone numbers on a PBX, urls in RSS feeds, XML from a service, … all things that are not hard-coded can be modified, and nowadays even hard-coded things can be circumvented. So don't trust any data and you'll be safe.

      Delete

Post a Comment

Popular Posts