Search code examples
phpbashparsingsudo

Get all bash subshells from command in php


I need to detect if a command have sudo command in one of it's sub commands, so far I have this:

public function split_command($command) {
    // this regex is not perfect but work
    $separators = "/(?:\"[^\"\\\\]*(?:\\\\[\S\s][^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\[\S\s][^'\\\\]*)*')(*SKIP)(*F)|(\s+(?:&&|\|{1,2}|;)\s+)/";
    $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE;
    return preg_split($separators, $command, null, $flags);
}
public function have_sudo($command) {
    $re = "!^(sudo|" . shell_exec("which sudo") . ")!";
    foreach ($this->split_command($command) as $part) {
        if (preg_match($re, trim($part))) {
            return true;
        }
    }
}

but if fail is command look like this: echo `sudo whoami`. How can I parse the command to get list of sub shells.

it should also work for commands like this:

$(some command; `other command (different command) $(some command)`)

it should return array:

["some command; `other command (different command) $(some command)`",
 "`other command (different command) $(some command)",
 "different command", "some command"]

so I can call have_sudo recursively on each element of the array the other option is to return most outer subshells from command.


Solution

  • I've created a parser:

    public function get_subshells($command) {
        $re = '/(\$\(|\(|\)|`)/';
        $parts = preg_split($re, $command, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
        $subshells = array();
        $backtick = false;
        $stack = array();
        foreach ($parts as $part) {
            if (preg_match($re, $part)) {
                if ($part == "`") {
                    $backtick = !$backtick;
                }
                if ($backtick || preg_match("/\(/", $part)) {
                    $stack[] = array('tag' => $part, 'content' => '');
                } else {
                    $last = array_pop($stack);
                    $subshells[] = preg_replace('/^(\$\(|\(|`)/', '', $last['content']);
                }
            }
            if (count($stack) > 0) {
                foreach ($stack as &$subshell) {
                    $subshell['content'] .= $part;
                }
            }
    
        }
        return array_reverse($subshells);
    }