<?php
/*
   phpsnmp - a PHP SNMP library
   Copyright (C) 2004 David Eder <david@eder,us>

   Based on snmp - a Python SNMP library
   Copyright (C) 2003 Unicity Pty Ltd <libsnmp@unicity.com.au>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'rfc1155.php');
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'rfc1157.php');
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'rfc1902.php');
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'rfc1905.php');

class snmp
{
  var $version = 1;	// version can be 1 or 2
  var $timeout = 10;	// timeout for waiting for a return packet

  function snmp(){}

 /**
  * Assign a unique requestID
  *
  * @return integer a request id
  */
  function assignRequestID()
  {
    static $nextRequestID = 0;
    if($nextRequestID == 0) $nextRequestID = mt_rand();
    return $nextRequestID++;
  }

 /**
  * Get microtime as a float
  *
  * @return float microtime
  */
  function microtime()
  {
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
  }

 /**
  * Send a packet to a host and get the response
  *
  * @param string $host hostname or ip address
  * @param string $packet to send
  * @return string packet from host
  */
  function send_packet($host, $packet)
  {
    $buffer = $port = NULL;
    $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
    if(socket_sendto($socket, $packet, strlen($packet), 0, $host, 161))
    {
      $t = $this->microtime();
      socket_set_nonblock($socket);
      do
      {
        @socket_recvfrom($socket, $buffer, 4096, 0, $host, $port);
      }
      while($buffer == '' && $this->microtime() - $t <= $this->timeout);
    }
    return $buffer;
  }

 /**
  * get/next an oid
  *
  * @param string $host hostname or ip address
  * @param mixed $target varbind array or oid (oids must be numeric)
  * @param string $community string
  * @param string $type is either get or getnext
  * @return array in the format $oid=>$value
  */
  function get($host, $target, $community='public', $type='get')
  {
    static $packet_cache = NULL;
    $ret = array();
    if(!is_array($target) && isset($packet_cache[$target][$community][$type]))
    {
      $packet = $packet_cache[$target][$community][$type];
      if($this->version == 1)
        $msg = new rfc1157_Message();
      elseif($this->version == 2)
        $msg = new rfc1905_Message();
      else
      {
        trigger_error("Unknown SNMP version {$this->version}", E_USER_WARNING);
        return $ret;
      }
    }
    else
    {
      if(is_array($target))
        $varbind = $target;
      else
        $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_Null()));
      if($this->version == 1)
      {
        if($type == 'get')
          $pdu = new rfc1157_Get($this->assignRequestID(), 0, 0, $varbind);
        else
          $pdu = new rfc1157_GetNext($this->assignRequestID(), 0, 0, $varbind);
        $msg = new rfc1157_Message(0, $community, $pdu);
      }
      elseif($this->version == 2)
      {
        if($type == 'get')
          $pdu = new rfc1905_Get($this->assignRequestID(), 0, 0, $varbind);
        else
          $pdu = new rfc1905_GetNext($this->assignRequestID(), 0, 0, $varbind);
        $msg = new rfc1905_Message(1, $community, $pdu);
      }
      else
      {
        trigger_error("Unknown SNMP version {$this->version}", E_USER_WARNING);
        return $ret;
      }
      unset($varbind, $pdu);
      $contents = $msg->encodeContents();
      $packet = chr(0x30) . $msg->encodeLength(strlen($contents)) . $contents;
      if(!is_array($target))
        $packet_cache[$target][$community][$type] = $packet;
    }
    $buffer = $this->send_packet($host, $packet);
    if($buffer != '')
    {
      $msg = $msg->decode($buffer);

      if($msg->data->errorStatus->value)
        trigger_error($msg->data->errorStatus->toString(), E_USER_WARNING);

      foreach($msg->data->varBindList->value as $val)
        $ret[$val->objectID->toString()] = $val->objectValue->toString();
    }
    return $ret;
  }

 /**
  * walk an oid
  *
  * @param string $host hostname or ip address
  * @param string $oid oids must be numeric
  * @param string $community string
  * @return array in the format $oid=>$value, $oid=>$value
  */
  function walk($host, $oid, $community='public')
  {
    $ret = array();
    $target = $oid;
    do
    {
      $p = $target;
      $z = $this->get($host, $target, $community, 'getnext');
      list($target) = each($z);
      $ok = strpos(' '.$target, $oid);
      if($ok) $ret += $z;
    } while($ok && $p != $target);
    return $ret;
  }

 /**
  * set a variable
  *
  * @param string $host hostname or ip address
  * @param mixed $target varbind array or oid (oids must be numeric)
  * @param mixed $value to set
  * @param string $type 'i' = integer; 't' = time ticks; 'x' = hex string; 's' = string; 'a' = IP address; 'o' = object ID; 'n' = null value
  * @param string $community string
  */
  function set($host, $target, $value=0, $type='i', $community='private')
  {
    if(is_array($target))
      $varbind = $target;
    else
    {
      switch($type)
      {
        case 'i': // integer
          $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_Integer($value)));
          break;
        case 't': // time ticks
          $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_TimeTicks($value)));
          break;
        case 'x': // hex string
          $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_OctetString(hexbin($value))));
          break;
        case 's': // string
          $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_OctetString($value)));
          break;
        case 'a': // ip address
          $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_IPAddress($value)));
          break;
        case 'o': // object id
          $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_ObjectID($value)));
          break;
        case 'n': // null value
          $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_Null()));
          break;
        default:
          trigger_error("Unknown type $type", E_USER_WARNING);
          $varbind = array(new rfc1157_VarBind(new rfc1155_ObjectID($target), new rfc1155_Null()));
      }
    }

    if($this->version == 1)
    {
      $pdu = new rfc1157_Set($this->assignRequestID(), 0, 0, $varbind);
      $msg = new rfc1157_Message(0, $community, $pdu);
    }
    elseif($this->version == 2)
    {
      $pdu = new rfc1905_Set($this->assignRequestID(), 0, 0, $varbind);
      $msg = new rfc1905_Message(1, $community, $pdu);
    }
    else
    {
      trigger_error("Unknown SNMP version {$this->version}", E_USER_WARNING);
      return;
    }
    unset($varbind, $pdu);
    $contents = $msg->encodeContents();
    $packet = chr(0x30) . $msg->encodeLength(strlen($contents)) . $contents;

    $this->send_packet($host, $packet);
  }
}
?>
