tags:

views:

4802

answers:

8

I'm writing a command line tool to help my web app. It needs a password to connect to the service. I'd like the script to show a password prompt so I don't have to pass it as a command line argument.

That's easy enough, but I'd like it to not echo the password to the screen as it's typed. How can I do this with PHP?

Bonus points for doing it in pure PHP (no system('stty')) and replacing the characters with *.

EDIT:

The script will run on a unix like system (linux or mac). The script is written in PHP, and will most likely stay like that.

Also, for the record, the stty way of doing it is:

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";

I'd prefer to not have the system() calls in there.

A: 

I guess that there is no simple way of doing it (actually I can't think of any way) without using stty -echo. If you intent running it on windows, you could create a batch script that would provide the unechoed typed info to your php script.

@echo off
cls
SET /P uname=Enter Username:
echo hP1X500P[PZBBBfh#b##fXf-V@`$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

example taken from http://www.indiangnu.org/2008/php-hide-user-input-using-batch-script-windows/

Gabriel Gilini
+3  A: 

Depending on your environment (i.e., not on Windows), you can use the ncurses library (specifically, the ncurses_noecho() function to stop keyboard echo and ncurses_getch() to read the input) to get the password without displaying it on screen.

Randy
+2  A: 

Why not use an SSH connection? You can abstract the commands away, redirect input/output and have full control.

You can provide someone with a pure clean shell with as little rights as neccesary, and let the password just be POST'ed along with to SSH2::Connect() to open the shell.

I created a nice class to work with the php SSH2 extension, maybe it helps you; (and it also does secure file transfers)

<?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);
  }
 }  


}

Usage example:

$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(); 
}
SchizoDuckie
+1  A: 

Theorically you can do it using stream_set_blocking(), but looks like there are some PHP bugs managing STDIN.

Look: http://bugs.php.net/bug.php?id=34972 http://bugs.php.net/bug.php?id=36030

Try yourself:

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');
Slipo
A: 

Wow... this is really php/system interaction, great!

+4  A: 

Found on 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;
  }
}
DaveHauenstein
A: 

That's weird from php! I really was hoping some function to do that easily

Fabio