Telnet to cisco switch using php

Buksy picture Buksy · May 18, 2012 · Viewed 11.8k times · Source

I need to telnet to cisco switch using php and execute show interface status command and get results. I tried some php classes I found on internet but none of them could connect to device. So I tried to write the script myself, but I have the same problem, I cant connect to device.

The host sends me banner message and then new line with username:. I send my username with \r\n, wait some time and tries to read data, but it looks to me like host is just ignoring my new line characters. This is response I got (explode('\n') on response):

Array
(
    [0] => % 
    [1] => User Access Verification
    [2] => Username:  timeout expired!
)

Why didn't I get prompt on password? I tried it with sending telnet headers, and without, no change. Can anyone please help me?

Here is my code

<?
$host = "switchName";
$name = "name";
$pass = "pass";
$port = 23;
$timeOut = 15;
$connected = false;
$skipNullLines = true;
$timeout = 125000;


$header1=chr(0xFF).chr(0xFB).chr(0x1F).chr(0xFF).chr(0xFB).chr(0x20).chr(0xFF).chr(0xFB).chr(0x18).chr(0xFF).chr(0xFB).chr(0x27).chr(0xFF).chr(0xFD).chr(0x01).chr(0xFF).chr(0xFB).chr(0x03).chr(0xFF).chr(0xFD).chr(0x03).chr(0xFF).chr(0xFC).chr(0x23).chr(0xFF).chr(0xFC).chr(0x24).chr(0xFF).chr(0xFA).chr(0x1F).chr(0x00).chr(0x50).chr(0x00).chr(0x18).chr(0xFF).chr(0xF0).chr(0xFF).chr(0xFA).chr(0x20).chr(0x00).chr(0x33).chr(0x38).chr(0x34).chr(0x30).chr(0x30).chr(0x2C).chr(0x33).chr(0x38).chr(0x34).chr(0x30).chr(0x30).chr(0xFF).chr(0xF0).chr(0xFF).chr(0xFA).chr(0x27).chr(0x00).chr(0xFF).chr(0xF0).chr(0xFF).chr(0xFA).chr(0x18).chr(0x00).chr(0x41).chr(0x4E).chr(0x53).chr(0x49).chr(0xFF).chr(0xF0);
$header2=chr(0xFF).chr(0xFC).chr(0x01).chr(0xFF).chr(0xFC).chr(0x22).chr(0xFF).chr(0xFE).chr(0x05).chr(0xFF).chr(0xFC).chr(0x21);


function read_string()
{
    global $fw,$host,$skipNullLines; 
    $string = "";

    while( !feof($fw) )
    {
        $read = fgets($fw);

        $string .= $read;

        // Probably prompt, stop reading
        if( strpos($read, ':') !== FALSE || strpos($read, '> (enable)') !== FALSE || strpos($read, $host.'#') !== FALSE)
        { break; }
    }

    $string =  explode("\n", $string);

    // Get rid of null lines
    $ret = array();
    for($i = 0; $i<count($string); $i++)
    {
        if( trim($string[$i]) == '' && $skipNullLines ) continue;

        $ret[] = $string[$i];
    }

    return $ret;
}

function send_string($string, $force=false)
{
    GLOBAL $timeout,$fw;
    $string = trim($string);

    // execute only strings that are preceded by "show" (if not forced)
    if(!$force && strpos($string, 'show ') !== 0) 
    {
        return 1;
    }

    fputs($fw, $string."\r\n");
    echo("SEND:".$string."\r\n");
    usleep($timeout);
}


$fw = fsockopen($host, $port, $errno, $errorstr, $timeOut);    
if($fw == false)
{
    echo("Cant connect");
}
else
{
    echo("Connected<br>");
    $connected = true;

    stream_set_timeout($fw, $timeout);

//  fputs($fw, $header1);
//  usleep($timeout);
//  fputs($fw, $header2);
//  usleep($timeout);
    print_r(read_string());
    send_string("test", true);
    print_r(read_string());
}

fclose($fw);
?>

UPDATE If I send username at first, and then I read, I get password prompt. I dont understand it, why cant I firstly read messages from host and then send my response. The way it works to me now (send response and then read for prompt) is no-sense! (and I still got "% Authentication failed." message event with right password/name).

...
$connected = true;
stream_set_timeout($fw, $timeout);
send_string("name", true);
send_string("password", true);
print_r(read_string());
...

Answer

Buksy picture Buksy · May 28, 2012

Okay, so I dont know what was the problem, but after "few" tests I was able to write this class that works for me. I dont know why other telnet classes dont work altough they do pretty much the same. So if anyone will have similar problem, you can try this:

class TELNET
{
  private $host;
  private $name;
  private $pass;
  private $port;
  private $connected;
  private $connect_timeout;
  private $stream_timetout;

  private $socket;

  public function TELNET()
  {
    $this->port = 23;
    $this->connected = false;         // connected?
    $this->connect_timeout = 10;      // timeout while asking for connection
    $this->stream_timeout = 380000;   // timeout between I/O operations
  }

  public function __destruct()
  {
    if($this->connected) { fclose($this->socket); }
  }

  // Connects to host
  // @$_host - addres (or hostname) of host
  // @$_user - name of user to log in as
  // $@_pass - password of user
  //
  // Return: TRUE on success, other way function will return error string got by fsockopen()
  public function Connect($_host, $_user, $_pass)
  {
    // If connected successfully
    if( ($this->socket = @fsockopen($_host, $this->port, $errno, $errorstr, $this->connect_timeout)) !== FALSE )
    {
      $this->host = $_host;
      $this->user = $_user;
      $this->pass = $_pass;

      $this->connected = true;

      stream_set_timeout($this->socket, 0, 380000);
      stream_set_blocking($this->socket, 1);

      return true;
    }
    // else if coulnt connect
    else return $errorstr;
  }


  // LogIn to host
  //
  // RETURN: will return true on success, other way returns false
  public function LogIn()
  {
    if(!$this->connected) return false;

    // Send name and password
    $this->SendString($this->user, true);
    $this->SendString($this->pass, true);

    // read answer
    $data = $this->ReadTo(array('#'));

    // did we get the prompt from host?
    if( strtolower(trim($data[count($data)-1])) == strtolower($this->host).'#' ) return true;
    else return false;
  }


  // Function will execute command on host and returns output
  //
  // @$_command - command to be executed, only commands beginning with "show " can be executed, you can change this by adding
  //              "true" (bool type) as the second argument for function SendString($command) inside this function (3rd line)
  //
  function GetOutputOf($_command)
  {
    if(!$this->connected) return false;

    $this->SendString($_command);

    $output = array();
    $work = true;

    //
    // Read whole output
    //
    // read_to( array( STRINGS ) ), STRINGS are meant as possible endings of outputs
    while( $work && $data = $this->ReadTo( array("--More--","#") ) )
    {
      // CHeck wheter we actually did read any data
      $null_data = true;
      foreach($data as $line)
      {
        if(trim($line) != "") {$null_data = false;break;}
      }
      if($null_data) { break;}

      // if device is paging output, send space to get rest
      if( trim($data[count($data)-1]) == '--More--')
      {
        // delete line with prompt (or  "--More--")
        unset($data[count($data)-1]);

        // if second line is blank, delete it
        if( trim($data[1]) == '' ) unset($data[1]);
        // If first line contains send command, delete it
        if( strpos($data[0], $_command)!==FALSE ) unset($data[0]);

        // send space
        fputs($this->socket, " ");
      }

      // ak ma vystup max dva riadky
      // alebo sme uz nacitali prompt
      // IF we got prompt (line ending with #)
      // OR string that we've read has only one line
      //    THEN we reached end of data and stop reading
      if( strpos($data[count($data)-1], '#')!==FALSE /* || (count($data) == 1 && $data[0] == "")*/  )
      {
        // delete line with prompt
        unset($data[count($data)-1]);

        // if second line is blank, delete it
        if( trim($data[1]) == '' ) unset($data[1]);
        // If first line contains send command, delete it
        if( strpos($data[0], $_command)!==FALSE ) unset($data[0]);

        // stop while cyclus
        $work = false;
      }

      // get rid of empty lines at the end
      for($i = count($data)-1; $i>0; $i--)
      {
        if(trim($data[$i]) == "") unset($data[$i]);
        else break;
      }

      // add new data to $output
      foreach($data as $v)
      { $output[] = $v; }
    }

    // return output
    return $output;
  }


  // Read from host until occurence of any index from $array_of_stops
  // @array_of_stops - array that contains strings of texts that may be at the end of output
  // RETURNS: output of command as array of lines
  function ReadTo($array_of_stops)
  {
    $ret = array();
    $max_empty_lines = 3;
    $count_empty_lines = 0;

    while( !feof($this->socket) )
    {
      $read = fgets($this->socket);
      $ret[] = $read;

      //
      // Stop reading after (int)"$max_empty_lines" empty lines
  //
      if(trim($read) == "")
      {
        if($count_empty_lines++ > $max_empty_lines) break;
      }
      else $count_empty_lines = 0;

      //
      // Does last line of readed data contain any of "Stop" strings ??
      $found = false;
      foreach($array_of_stops AS $stop)
      {
        if( strpos($read, $stop) !== FALSE ) { $found = true; break; }
      }
      // If so, stop reading
      if($found) break;
    }

    return $ret;
  }



  // Send string to host
  // If force is set to false (default), function sends to host only strings that begins with "show "
  //
  // @$string - command to be executed
  // @$force - force command? Execute if not preceeded by "show " ?
  // @$newLine - append character of new line at the end of command?
  function SendString($string, $force=false, $newLine=true)
  {
    $t1 = microtime(true);
    $string = trim($string);

    // execute only strings that are preceded by "show"
    // and execute only one command (no new line characters) !
    if(!$force && strpos($string, 'show ') !== 0 && count(explode("\n", $string)) == 1)
    {
      return 1;
    }


    if($newLine) $string .= "\n";
    fputs($this->socket, $string);

    $t2 = microtime(true);
  }

}


// EXAMPLE
$host = "hostname";
$name = "username";
$pass = "password";


$t = new TELNET();
echo("CONNECT:".$t->Connect($host, $name, $pass)."<br>");
echo("LOGIN:".(int)$t->LogIn());
echo("<br>OUTPUT:<br>");
print_r($t->GetOutputOf("show snmp"));
print_r($t->GetOutputOf("show users"));
print_r($t->GetOutputOf("show interface status"));

PS: my devices prompt is "hostname#", so you may need to edit Login function to make this code work with prompt of your device (so you may need in GetOutputOf() )