Changeset View
Changeset View
Standalone View
Standalone View
scripts/ssh/ssh-exec.php
| #!/usr/bin/env php | #!/usr/bin/env php | ||||
| <?php | <?php | ||||
| $root = dirname(dirname(dirname(__FILE__))); | $root = dirname(dirname(dirname(__FILE__))); | ||||
| require_once $root.'/scripts/__init_script__.php'; | require_once $root.'/scripts/__init_script__.php'; | ||||
| $original_command = getenv('SSH_ORIGINAL_COMMAND'); | // First, figure out the authenticated user. | ||||
| $original_argv = id(new PhutilShellLexer())->splitArguments($original_command); | |||||
| $argv = array_merge($argv, $original_argv); | |||||
| $args = new PhutilArgumentParser($argv); | $args = new PhutilArgumentParser($argv); | ||||
| $args->setTagline('receive SSH requests'); | $args->setTagline('receive SSH requests'); | ||||
| $args->setSynopsis(<<<EOSYNOPSIS | $args->setSynopsis(<<<EOSYNOPSIS | ||||
| **ssh-exec** --phabricator-ssh-user __user__ __commmand__ [__options__] | **ssh-exec** --phabricator-ssh-user __user__ [--ssh-command __commmand__] | ||||
| Receive SSH requests. | Receive SSH requests. | ||||
| EOSYNOPSIS | EOSYNOPSIS | ||||
| ); | ); | ||||
| // NOTE: Do NOT parse standard arguments. Arguments are coming from a remote | $args->parse( | ||||
| // client over SSH, and they should not be able to execute "--xprofile", | |||||
| // "--recon", etc. | |||||
| $args->parsePartial( | |||||
| array( | array( | ||||
| array( | array( | ||||
| 'name' => 'phabricator-ssh-user', | 'name' => 'phabricator-ssh-user', | ||||
| 'param' => 'username', | 'param' => 'username', | ||||
| ), | ), | ||||
| array( | |||||
| 'name' => 'ssh-command', | |||||
| 'param' => 'command', | |||||
| ), | |||||
| )); | )); | ||||
| try { | try { | ||||
| $user_name = $args->getArg('phabricator-ssh-user'); | $user_name = $args->getArg('phabricator-ssh-user'); | ||||
| if (!strlen($user_name)) { | if (!strlen($user_name)) { | ||||
| throw new Exception("No username."); | throw new Exception("No username."); | ||||
| } | } | ||||
| $user = id(new PhabricatorUser())->loadOneWhere( | $user = id(new PhabricatorUser())->loadOneWhere( | ||||
| 'userName = %s', | 'userName = %s', | ||||
| $user_name); | $user_name); | ||||
| if (!$user) { | if (!$user) { | ||||
| throw new Exception("Invalid username."); | throw new Exception("Invalid username."); | ||||
| } | } | ||||
| if ($user->getIsDisabled()) { | if ($user->getIsDisabled()) { | ||||
| throw new Exception("You have been exiled."); | throw new Exception("You have been exiled."); | ||||
| } | } | ||||
| if ($args->getArg('ssh-command')) { | |||||
| $original_command = $args->getArg('ssh-command'); | |||||
| } else { | |||||
| $original_command = getenv('SSH_ORIGINAL_COMMAND'); | |||||
| } | |||||
| // Now, rebuild the original command. | |||||
| $original_argv = id(new PhutilShellLexer()) | |||||
| ->splitArguments($original_command); | |||||
| if (!$original_argv) { | |||||
| throw new Exception("No interactive logins."); | |||||
| } | |||||
| $command = head($original_argv); | |||||
| array_unshift($original_argv, 'phabricator-ssh-exec'); | |||||
| $original_args = new PhutilArgumentParser($original_argv); | |||||
| $workflows = array( | $workflows = array( | ||||
| new ConduitSSHWorkflow(), | new ConduitSSHWorkflow(), | ||||
| ); | ); | ||||
| // This duplicates logic in parseWorkflows(), but allows us to raise more | |||||
| // concise/relevant exceptions when the client is a remote SSH. | |||||
| $remain = $args->getUnconsumedArgumentVector(); | |||||
| if (empty($remain)) { | |||||
| throw new Exception("No interactive logins."); | |||||
| } else { | |||||
| $command = head($remain); | |||||
| $workflow_names = mpull($workflows, 'getName', 'getName'); | $workflow_names = mpull($workflows, 'getName', 'getName'); | ||||
| if (empty($workflow_names[$command])) { | if (empty($workflow_names[$command])) { | ||||
| throw new Exception("Invalid command."); | throw new Exception("Invalid command."); | ||||
| } | } | ||||
| } | |||||
| $workflow = $args->parseWorkflows($workflows); | $workflow = $original_args->parseWorkflows($workflows); | ||||
| $workflow->setUser($user); | $workflow->setUser($user); | ||||
| $sock_stdin = fopen('php://stdin', 'r'); | $sock_stdin = fopen('php://stdin', 'r'); | ||||
| if (!$sock_stdin) { | if (!$sock_stdin) { | ||||
| throw new Exception("Unable to open stdin."); | throw new Exception("Unable to open stdin."); | ||||
| } | } | ||||
| $sock_stdout = fopen('php://stdout', 'w'); | $sock_stdout = fopen('php://stdout', 'w'); | ||||
| if (!$sock_stdout) { | if (!$sock_stdout) { | ||||
| throw new Exception("Unable to open stdout."); | throw new Exception("Unable to open stdout."); | ||||
| } | } | ||||
| $socket_channel = new PhutilSocketChannel( | $socket_channel = new PhutilSocketChannel( | ||||
| $sock_stdin, | $sock_stdin, | ||||
| $sock_stdout); | $sock_stdout); | ||||
| $metrics_channel = new PhutilMetricsChannel($socket_channel); | $metrics_channel = new PhutilMetricsChannel($socket_channel); | ||||
| $workflow->setIOChannel($metrics_channel); | $workflow->setIOChannel($metrics_channel); | ||||
| $err = $workflow->execute($args); | $err = $workflow->execute($original_args); | ||||
| $metrics_channel->flush(); | $metrics_channel->flush(); | ||||
| } catch (Exception $ex) { | } catch (Exception $ex) { | ||||
| echo "phabricator-ssh-exec: ".$ex->getMessage()."\n"; | echo "phabricator-ssh-exec: ".$ex->getMessage()."\n"; | ||||
| exit(1); | exit(1); | ||||
| } | } | ||||