<?php

class RouterosAPI
{
  var $ErrorNo;           // Variable for storing connection error number, if any
  var $ErrorStr;          // Variable for storing connection error text, if any
  var $Attempts;       // Connection attempt count
  var $Connected;  // Connection state
  var $Delay;          // Delay between connection attempts in seconds
  var $Port;        // Port to connect to
  var $Timeout;        // Connection attempt timeout and data read timeout
  var $Socket;             // Variable for storing socket resource
  var $Debug;
  var $SSL; // If SSL API connection is used. You need also change port to 8729

  function __construct()
  {
    $this->Attempts = 5;
    $this->Connected = false;
    $this->Delay = 3;
    $this->Port = 8728;
    $this->Timeout = 3;
    $this->Debug = false;
    $this->SSL = false;
  }

  function EncodeLength($Length)
  {
    if ($Length < 0x80) {
      $Length = chr($Length);
    } else if ($Length < 0x4000) {
      $Length |= 0x8000;
      $Length = chr(($Length >> 8) & 0xFF).chr($Length & 0xFF);
    } else if ($Length < 0x200000) {
      $Length |= 0xC00000;
      $Length = chr(($Length >> 16) & 0xFF).chr(($Length >> 8) & 0xFF).chr($Length & 0xFF);
    } else if ($Length < 0x10000000) {
      $Length |= 0xE0000000;
      $Length = chr(($Length >> 24) & 0xFF).chr(($Length >> 16) & 0xFF).chr(($Length >> 8) & 0xFF).chr($Length & 0xFF);
    } else if ($Length >= 0x10000000)
      $Length = chr(0xF0).chr(($Length >> 24) & 0xFF).chr(($Length >> 16) & 0xFF).chr(($Length >> 8) & 0xFF).chr($Length & 0xFF);
    return($Length);
  }

  function ConnectOnce($IP, $Login, $Password)
  {
    if($this->Connected) $this->Disconnect();
    if($this->SSL)
    {
      $IP = 'ssl://'.$IP;
    }
    $this->Socket = @fsockopen($IP, $this->Port, $this->ErrorNo, $this->ErrorStr, $this->Timeout);
    if($this->Socket)
    {
      socket_set_timeout($this->Socket, $this->Timeout);
      $this->Write('/login', false);
      $this->Write('=name=' . $Login, false);
      $this->Write('=password='.$Password);
      $Response = $this->Read(false);
      if((count($Response) > 0) and ($Response[0] == '!done')) $this->Connected = true;
      if(!$this->Connected) fclose($this->Socket);
    }
  }

  function Connect($IP, $Login, $Password)
  {
    for($Attempt = 1; $Attempt <= $this->Attempts; $Attempt++)
    {
      $this->ConnectOnce($IP, $Login, $Password);
      if($this->Connected) break;
      sleep($this->Delay);
    }
    return($this->Connected);
  }

  function Disconnect()
  {
    if($this->Connected)
    {
      fclose($this->Socket);
      $this->Connected = false;
    }
  }

  function ParseResponse($Response)
  {
    if (is_array($Response)) {
      $Parsed      = array();
      $Current     = null;
      $SingleValue = null;
      $count       = 0;
      foreach ($Response as $x) {
        if (in_array($x, array(
            '!fatal',
            '!re',
            '!trap'
        ))) {
          if ($x == '!re') {
            $Current =& $Parsed[];
          } else
            $Current =& $Parsed[$x][];
        } else if ($x != '!done') {
          if (preg_match_all('/[^=]+/i', $x, $Matches)) {
            if ($Matches[0][0] == 'ret') {
              $SingleValue = $Matches[0][1];
            }
            $Current[$Matches[0][0]] = (isset($Matches[0][1]) ? $Matches[0][1] : '');
          }
        }
      }
      if (empty($Parsed) && !is_null($SingleValue)) {
        $Parsed = $SingleValue;
      }
      return $Parsed;
    } else
      return array();
  }

  function ArrayChangeKeyName(&$array)
  {
    if (is_array($array)) {
      foreach ($array as $k => $v) {
        $tmp = str_replace("-", "_", $k);
        $tmp = str_replace("/", "_", $tmp);
        if ($tmp) {
          $array_new[$tmp] = $v;
        } else {
          $array_new[$k] = $v;
        }
      }
      return $array_new;
    } else {
      return $array;
    }
  }

  function Read($Parse = true)
  {
    $Line = '';
    $Response = array();
    while (true) {
      // Read the first byte of input which gives us some or all of the length
      // of the remaining reply.
      $Byte = ord(fread($this->Socket, 1));
      $Length = 0;
      // If the first bit is set then we need to remove the first four bits, shift left 8
      // and then read another byte in.
      // We repeat this for the second and third bits.
      // If the fourth bit is set, we need to remove anything left in the first byte
      // and then read in yet another byte.
      if ($Byte & 0x80) {
        if (($Byte & 0xc0) == 0x80) {
          $Length = (($Byte & 63) << 8) + ord(fread($this->Socket, 1));
        } else {
          if (($Byte & 0xe0) == 0xc0) {
            $Length = (($Byte & 31) << 8) + ord(fread($this->Socket, 1));
            $Length = ($Length << 8) + ord(fread($this->Socket, 1));
          } else {
            if (($Byte & 0xf0) == 0xe0) {
              $Length = (($Byte & 15) << 8) + ord(fread($this->Socket, 1));
              $Length = ($Length << 8) + ord(fread($this->Socket, 1));
              $Length = ($Length << 8) + ord(fread($this->Socket, 1));
            } else {
              $Length = ord(fread($this->Socket, 1));
              $Length = ($Length << 8) + ord(fread($this->Socket, 1));
              $Length = ($Length << 8) + ord(fread($this->Socket, 1));
              $Length = ($Length << 8) + ord(fread($this->Socket, 1));
            }
          }
        }
      } else {
        $Length = $Byte;
      }
      // If we have got more characters to read, read them in.
      if ($Length > 0) {
        $Line = '';
        $RetLen = 0;
        while ($RetLen < $Length) {
          $ToRead = $Length - $RetLen;
          $Line .= fread($this->Socket, $ToRead);
          $RetLen = strlen($Line);
        }
        $Response[] = $Line;
      }
      if($this->Debug) echo($Line);
      // If we get a !done, make a note of it.
      if ($Line == "!done") $ReceivedDone = true;
        else $ReceivedDone = false;
      $Status = socket_get_status($this->Socket);
      if ((!$this->Connected && !$Status['unread_bytes']) ||
          ($this->Connected && !$Status['unread_bytes'] && $ReceivedDone))
        break;
    }
    if($Parse) $Response = $this->ParseResponse($Response);
    return $Response;
  }

  function Write($Command, $Param2 = true)
  {
    if($Command)
    {
      $Data = explode("\n", $Command);
      foreach ($Data as $Com) {
        $Com = trim($Com);
        fwrite($this->Socket, $this->EncodeLength(strlen($Com)).$Com);
      }
      if (gettype($Param2) == 'integer') {
        fwrite($this->Socket, $this->EncodeLength(strlen('.tag='.$Param2)).'.tag='.$Param2.chr(0));
      } else if (gettype($Param2) == 'boolean')
        fwrite($this->Socket, ($Param2 ? chr(0) : ''));
      return true;
    } else
      return false;
  }

  function Comm($Com, $Arr = array())
  {
    $Count = count($Arr);
    $this->write($Com, !$Arr);
    $i = 0;
    foreach ($Arr as $k => $v) {
      switch ($k[0]) {
        case "?":
          $el = "$k=$v";
          break;
        case "~":
          $el = "$k~$v";
          break;
        default:
          $el = "=$k=$v";
          break;
      }
      $Last = ($i++ == $Count - 1);
      $this->Write($el, $Last);
    }
    return($this->Read());
  }
}
