Запрос пароля командной строки в PHP

Я пишу инструмент командной строки, чтобы помочь моему веб-приложению. Для подключения к службе требуется пароль. Я хотел бы, чтобы script отображал приглашение пароля, поэтому мне не нужно передавать его в качестве аргумента командной строки.

Это достаточно легко, но я бы хотел, чтобы он не повторял пароль на экране, когда он печатался. Как я могу это сделать с PHP?

Бонусные баллы за выполнение этого в чистом PHP (no system('stty')) и замену символов на *.

EDIT:

script будет работать в системе типа unix (linux или mac). script написан на PHP и, скорее всего, останется таким.

Кроме того, для записи способ stty сделать это:

echo "Password: ";
system('stty -echo');
$password = trim(fgets(STDIN));
system('stty echo');
// add a new line since the users CR didn't echo
echo "\n";

Я бы предпочел, чтобы там не было вызовов system().

Ответ 1

Найдено на sitepoint.

function prompt_silent($prompt = "Enter Password:") {
  if (preg_match('/^win/i', PHP_OS)) {
    $vbscript = sys_get_temp_dir() . 'prompt_password.vbs';
    file_put_contents(
      $vbscript, 'wscript.echo(InputBox("'
      . addslashes($prompt)
      . '", "", "password here"))');
    $command = "cscript //nologo " . escapeshellarg($vbscript);
    $password = rtrim(shell_exec($command));
    unlink($vbscript);
    return $password;
  } else {
    $command = "/usr/bin/env bash -c 'echo OK'";
    if (rtrim(shell_exec($command)) !== 'OK') {
      trigger_error("Can't invoke bash");
      return;
    }
    $command = "/usr/bin/env bash -c 'read -s -p \""
      . addslashes($prompt)
      . "\" mypassword && echo \$mypassword'";
    $password = rtrim(shell_exec($command));
    echo "\n";
    return $password;
  }
}

Ответ 2

Вы можете использовать мой hiddeninput.exe файл для получения реального скрытого ввода без утечки информации в любом месте экрана.

<?php

echo 'Enter password: ';
$password = exec('hiddeninput.exe');
echo PHP_EOL;

echo 'Password was: ' . $password . PHP_EOL;

Если вы удалите последнее эхо, пароль никогда не появится, но вы можете использовать его для проверки obvoiusly.

Ответ 3

В зависимости от вашей среды (т.е. не в Windows) вы можете использовать библиотеку ncurses (в частности, ncurses_noecho() для остановки клавиатурное эхо и ncurses_getch(), чтобы прочитать ввод), чтобы получить пароль, не отображая его на экране.

Ответ 4

Я предполагаю, что нет простого способа сделать это (на самом деле я никак не могу придумать) без использования stty -echo. Если вы намереваетесь запустить его на окнах, вы можете создать пакетный пакет script, который предоставит непереписанную введенную информацию на ваш PHP скрипт.

@echo off
cls
SET /P uname=Enter Username:
echo hP1X500P[PZBBBfh#b##[email protected]`$fPf]f3/f1/5++u5>in.com
set /p password=Enter password :<nul
for /f "tokens=*" %%i in (’in.com’) do set password=%%i
del in.com
echo.
c:\php\php.exe d:\php\test.php %uname% "%password%"
Pause

пример, взятый из http://www.indiangnu.org/2008/php-hide-user-input-using-batch-script-windows/

Ответ 5

Это самое простое решение для всех платформ:

function prompt($message = 'prompt: ', $hidden = false) {
    if (PHP_SAPI !== 'cli') {
        return false;
    }
    echo $message;
    $ret = 
        $hidden
        ? exec(
            PHP_OS === 'WINNT' || PHP_OS === 'WIN32'
            ? __DIR__ . '\prompt_win.bat'
            : 'read -s PW; echo $PW'
        )
        : rtrim(fgets(STDIN), PHP_EOL)
    ;
    if ($hidden) {
        echo PHP_EOL;
    }
    return $ret;
}

Затем создайте prompt_win.bat в том же каталоге:

SetLocal DisableDelayedExpansion
Set "Line="
For /F %%# In ('"Prompt;$H & For %%# in (1) Do Rem"') Do (
    Set "BS=%%#"
)

:loop_start
    Set "Key="
    For /F "delims=" %%# In ('Xcopy /L /W "%~f0" "%~f0" 2^>Nul') Do (
        If Not Defined Key (
            Set "Key=%%#"
        )
    )
    Set "Key=%Key:~-1%"
    SetLocal EnableDelayedExpansion
    If Not Defined Key (
        Goto :loop_end
    )
    If %BS%==^%Key% (
        Set "Key="
        If Defined Line (
            Set "Line=!Line:~0,-1!"
        )
    )
    If Not Defined Line (
        EndLocal
        Set "Line=%Key%"
    ) Else (
        For /F "delims=" %%# In ("!Line!") Do (
            EndLocal
            Set "Line=%%#%Key%"
        )
    )
    Goto :loop_start
:loop_end

Echo;!Line!

Ответ 6

Почему бы не использовать SSH-соединение? Вы можете абстрагировать команды, перенаправить ввод/вывод и получить полный контроль.

Вы можете предоставить кому-то чистую чистую оболочку с минимальными правами, а также пароль, который будет POST'ed вместе с SSH2:: Connect(), чтобы открыть оболочку.

Я создал хороший класс для работы с расширением php SSH2, возможно, это поможет вам; (и он также обеспечивает безопасную передачу файлов)

<?php

/**
 * SSH2
 * 
 * @package Pork
 * @author SchizoDuckie
 * @version 1.0
 * @access public
 */
class SSH2
{
    private $host;
    private $port;
    private $connection;
    private $timeout;
    private $debugMode;
    private $debugPointer;
    public $connected; 
    public $error;


    /**
     * SSH2::__construct()
     * 
     * @param mixed $host
     * @param integer $port
     * @param integer $timeout
     * @return
     */
    function __construct($host, $port=22, $timeout=10)
    {
        $this->host = $host;
        $this->port = $port;
        $this->timeout = 10;
        $this->error = 'not connected';
        $this->connection = false;
        $this->debugMode = Settings::Load()->->get('Debug', 'Debugmode');
        $this->debugPointer = ($this->debugMode) ? fopen('./logs/'.date('Y-m-d--H-i-s').'.log', 'w+') : false;
        $this->connected = false;

    }


    /**
     * SSH2::connect()
     * 
     * @param mixed $username
     * @param mixed $password
     * @return
     */
    function connect($username, $password)
    {
        $this->connection = ssh2_connect($this->host, $this->port);
        if (!$this->connection) return $this->error("Could not connect to {$this->host}:{$this->port}");
        $this->debug("Connected to {$this->host}:{$this->port}");
        $authenticated = ssh2_auth_password($this->connection, $username, $password);
        if(!$authenticated) return $this->error("Could not authenticate: {$username}, check your password");
        $this->debug("Authenticated successfully as {$username}");
        $this->connected = true;

        return true;
    }

    /**
     * SSH2::exec()
     *
     * @param mixed $command shell command to execute
     * @param bool $onAvailableFunction a function to handle any available data.
     * @param bool $blocking blocking or non-blocking mode. This 'hangs' php execution until the command has completed if you set it to true. If you just want to start an import and go on, use this icm onAvailableFunction and false
     * @return
     */
    function exec($command, $onAvailableFunction=false, $blocking=true)
    {
        $output = '';
        $stream = ssh2_exec($this->connection, $command);
        $this->debug("Exec: {$command}");
        if($onAvailableFunction !== false)
        {
            $lastReceived = time();
            $timeout =false;
            while (!feof($stream) && !$timeout)
            {
                $input = fgets($stream, 1024);
                if(strlen($input) >0)
                {
                    call_user_func($onAvailableFunction, $input);
                    $this->debug($input);
                    $lastReceived = time();
                }
                else
                {
                    if(time() - $lastReceived >= $this->timeout)
                    {
                        $timeout = true;
                        $this->error('Connection timed out');
                        return($this->error);
                    }
                }
            }
        }
        if($blocking === true && $onAvailableFunction === false)
        {
            stream_set_blocking($stream, true);
            $output = stream_get_contents($stream);
            $this->debug($output);
        }
        fclose($stream);
        return($output);
    }


    /**
     * SSH2::createDirectory()
     *
     * Creates a directory via sftp
     *
     * @param string $dirname
     * @return boolean success
     *  
     */
    function createDirectory($dirname)
    {
        $ftpconnection = ssh2_sftp ($this->connection);
        $dircreated = ssh2_sftp_mkdir($ftpconnection, $dirname, true);
        if(!$dircreated) 
        {
            $this->debug("Directory not created: ".$dirname);
        }
        return $dircreated;
    }

    public function listFiles($dirname)
    {
        $input = $this->exec(escapeshellcmd("ls  {$dirname}"));
        return(explode("\n", trim($input)));

    }

    public function sendFile($filename, $remotename)
    {
        $this->debug("sending {$filename} to {$remotename} ");
        if(file_exists($filename) && is_readable($filename))
        {
            $result = ssh2_scp_send($this->connection, $filename, $remotename, 0664);
        }
        else
        {
            $this->debug("Unable to read file : ".$filename);
            return false;
        }
        if(!$result) $this->debug("Failure uploading {$filename} to {$remotename}");
        return $result;
    }

    public function getFile($remotename, $localfile)
    {
        $this->debug("grabbing {$remotename} to {$localfile}");
        $result = ssh2_scp_recv($this->connection, $remotename, $localfile);

        if(!$result) $this->debug("Failure downloading {$remotename} to {$localfile}");
        return $result;
    }

    /**
     * SSH2::debug()
     * 
     * @param mixed $message
     * @return
     */
    function debug($message) 
    {
        if($this->debugMode)
        {
            fwrite($this->debugPointer, date('Y-m-d H:i:s')." : ".$message."\n");
        }
    }



    /**
     * SSH2::error()
     * 
     * @param mixed $errorMsg
     * @return
     */
    function error($errorMsg) 
    {
        $this->error = $errorMsg;
        $this->debug($errorMsg);
        return false;
    }   

    /**
     * SSH2::__destruct()
     * 
     * @return
     */
    function __destruct() 
    {
        if($this->connection){
            $this->connection = null;
        }
        if($this->debugMode && $this->debugPointer)
        {
            fclose($this->debugPointer);
        }
    }       


}

Пример использования:

$settings = Settings::Load()->Get("SecureServer");
$ssh = new SSH2($settings['host']);
if( $ssh->connect($settings['username'], $settings['password']))
{
    echo $ssh->exec("ls -la ".$settings['path'], false, true);  
    flush();    
}

Ответ 7

Принятый ответ недостаточно хорош. Прежде всего, решение Windows не работает в Windows 7 и выше. Решение для других ОС зависит от Bash и Bash встроенного 'read'. Однако существуют системы, которые не используют Bash (например, OpenBSD), и это явно не сработает.

В этом blog Я обсуждал решение, которое работает практически на любой ОС на базе Unix и Windows с 95 до 8. Решение для Windows использует внешнюю программу, написанную на C поверх Win32 API. Решение для других ОС использует внешнюю команду 'stty'. Я еще не видел систему на основе Unix, которая не имеет "stty"

Ответ 8

Работает на каждой системе Windows, которая имеет поддержку powershell. (источник от: http://www.qxs.ch/2013/02/08/php-cli-password-prompts-on-windows-7/)

<?php
// please set the path to your powershell, here it is: C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe
$pwd=shell_exec('C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -Command "$Password=Read-Host -assecurestring \"Please enter your password\" ; $PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) ; echo $PlainPassword;"');
$pwd=explode("\n", $pwd); $pwd=$pwd[0];
echo "You have entered the following password: $pwd\n";

Ответ 9

Я переформатировал JMW трехстрочное решение, чтобы вы могли просто вырезать и вставить его в существующий PHP-код.

function getPassword()
{
  $pwd=shell_exec('C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -Command "$Password=Read-Host -assecurestring \"Please enter your password\" ; $PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) ; echo $PlainPassword;"');
  $pwd=explode("\n", $pwd); $pwd=$pwd[0];
  return $pwd;
}

Чтобы использовать его:

$usersPassword=getPassword();

Я на Powershell V5.0, но путь к каталогу по-прежнему отображается как v1.0, поэтому строка с кавычками в вызове shell_exec должна быть в порядке.

Ответ 10

Теоретически вы можете сделать это с помощью stream_set_blocking(), но похоже, что есть некоторые ошибки PHP, управляющие STDIN.

Посмотрите: http://bugs.php.net/bug.php?id=34972 http://bugs.php.net/bug.php?id=36030

Попробуйте сами:

echo "Enter Password: ";
$stdin = fopen('php://stdin','r');
// Trying to disable stream blocking
stream_set_blocking($stdin, FALSE) or die ('Failed to disable stdin blocking');
// Trying to set stream timeout to 1sec
stream_set_timeout ($stdin, 1) or die ('Failed to enable stdin timeout');