Source for file EndDevice.php

Documentation is available at EndDevice.php

  1. <?php
  2. /**
  3.  * EndDevice.php
  4.  *
  5.  * PHP version 5
  6.  *
  7.  * LICENSE: This program is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU General Public License as published
  9.  * by the Free Software Foundation.
  10.  *
  11.  * @package            FreeNAC
  12.  * @author            Seiler Thomas (contributer)
  13.  * @author            Hector Ortiz (FreeNAC Core Team)
  14.  * @copyright            2007 FreeNAC
  15.  * @license            http://www.gnu.org/copyleft/gpl.html   GNU Public License Version 2
  16.  * @version            SVN: $Id$
  17.  * @link            http://www.freenac.net
  18.  */
  19.  
  20. /**
  21. * Health status(es) (Note: The Oxford English Dict defines the plural of status as statuses, but the Latin plural is status
  22. * Pick the one you like the most :) )
  23. */
  24. define('UNKNOWN',0);
  25. define('OK',1);
  26. define('TRANSITION',4);
  27. define('QUARANTINE',5);
  28. define('INFECTED',6);
  29.  
  30. /**
  31.  * This class represents a row in the systems table in the database.
  32.  * In the current version, the host is identified by its mac address.
  33.  * This class extends the {@link Common} class.
  34.  */
  35. class EndDevice extends Common 
  36. {
  37.    protected $mac;
  38.    protected $db_row = array();
  39.  
  40.    /** The constructor takes the mac address of the system and creates
  41.    * and instance representing that particular system.
  42.    * Access is read-only.
  43.    * @param object $object    A copy of the Request
  44.    * @throws            Deny if we received an invalid MAC address or if vlan assigned to this device is 0
  45.    */
  46.    public function __construct($object
  47.    {
  48.       #If we don't receive an object, DENY
  49.       if (!$object)
  50.          DENY('No object received in constructor');
  51.       parent::__construct();
  52.       if (($object instanceof VMPSRequest|| ($object instanceof SyslogRequest))
  53.       {
  54.          # Normalise mac address format by removing spaces, dashes, dots
  55.          # and collons, and by converting to lower case.
  56.          $mac=$object->mac;
  57.          $mac strtolower(preg_replace('/-|\.|\s|\:/'''$mac));
  58.          
  59.          # Sanity check - Is this a valid MAC address ??? 
  60.          if (!preg_match("/^[0-9a-f]{12}$/",$mac)) 
  61.             DENY("Invalid MAC address $mac");
  62.          if ($mac === '000000000000'
  63.             DENY("Invalid MAC address $mac");
  64.       
  65.          # Rewrite mac address according to Cisco convention, XXXX.XXXX.XXXX  
  66.          $this->mac="$mac[0]$mac[1]$mac[2]$mac[3].$mac[4]$mac[5]$mac[6]$mac[7].$mac[8]$mac[9]$mac[10]$mac[11]";          
  67.       
  68.          # Query systems table 
  69.          $sql_query=<<<EOF
  70. SELECT s.id AS sid, s.health, s.office, s.mac AS mac, 
  71.        s.name AS hostname, s.description, s.status, 
  72.        s.r_ip AS ip, s.expiry, s.email_on_connect,
  73.        u.id AS uid, u.username,
  74.        v.id AS vid, v.default_name AS vlan_name
  75. FROM systems s
  76.    LEFT JOIN users u ON s.uid=u.id 
  77.    LEFT JOIN vlan v ON s.vlan=v.id 
  78. WHERE s.mac='{$this->mac}' LIMIT 1;
  79. EOF;
  80.       
  81.          $this->logger->debug($sql_query,3);
  82.  
  83.          #System found in database, fill up the properties
  84.          if ($temp=mysql_fetch_one($sql_query))
  85.          {
  86.             $this->db_row=$temp;
  87.             $this->db_row['in_db']=true;
  88.             if (!$this->db_row['health'])
  89.                $this->db_row['health']=UNKNOWN;
  90.             if (($object instanceof VMPSRequest&& ($this->vid == 0))
  91.                $this->logger->logit("Note: Device {$this->mac}({$this->hostname}) has vlan zero. It will be blocked");
  92.             #   DENY('VLAN ID assigned to this EndDevice equals zero');
  93.             
  94.          }
  95.          else 
  96.          {
  97.             #Unknown system
  98.         $this->db_row['status']=0;
  99.         $this->db_row['mac']=$this->mac;
  100.         $this->db_row['in_db']=false;
  101.             $this->db_row['health']=UNKNOWN;
  102.      }
  103.          
  104.          # Save the result of vmpsd_external
  105.          if ( $object instanceof SyslogRequest )
  106.          {
  107.             $this->db_row['vmpsd_external_result'$object->vtp;    
  108.          }
  109.  
  110.          # This bit will be used for hub detection, if enabled
  111.          if ($this->conf->detect_hubs && ($object instanceof VMPSRequest))
  112.          {
  113.             $query = "SELECT id FROM vlan WHERE default_name='{$object->lastvlan}'";
  114.             $this->logger->debug($query,3)
  115.             $this->db_row['newvlan_id']=v_sql_1_select($query);
  116.             if ($this->db_row['newvlan_id'])
  117.                $this->db_row['newvlan_id'0;
  118.          }
  119.  
  120.          #Initial values for these vars. They'll be modified at some point in the future in this class
  121.          $this->db_row['port_id']=0;
  122.          $this->db_row['office_id']=1;
  123.      $this->db_row['lastvlan_id']=1;
  124.  
  125.          #Passing of information between objects
  126.          $this->setPortID($object->switch_port->getPortID());
  127.          $this->setOfficeID($object->switch_port->getOfficeID());
  128.          $this->setVlanID($object->switch_port->getLastVlanID());
  129.          $this->setAlertSubject($object->switch_port->getAlertSubject());
  130.          $this->setAlertMessage($object->switch_port->getAlertMessage());
  131.          $this->setNotifyInfo($object->switch_port->getNotifyInfo());
  132.          
  133.  
  134.       }
  135.       else
  136.       {
  137.          #Object is an unknown instance
  138.          DENY('Unknown instance of object passed to the constructor');
  139.       } 
  140.    }
  141.  
  142.    # Policy Checks ------------------------------------------------------------ 
  143.    /** 
  144.    * Check that the system is not yet expired
  145.    * Expiry date like 0000-00-00 00:00:00 is treated as never expire
  146.    * @return boolean     Tell if the system is expired
  147.    */
  148.    public function isExpired() 
  149.    {
  150.       # Check if expiry checks are enabled 
  151.       if ($this->conf->check_for_expired
  152.       {
  153.          # get systems expiry date
  154.          $expiry = $this->expiry;
  155.             
  156.          if ($expiry)
  157.          {
  158.             # 0000-00-00 00:00:00 means no expiry 
  159.             if(strcmp(trim($expiry),"0000-00-00 00:00:00")==0) 
  160.             {
  161.                return false;
  162.             }
  163.   
  164.             # Get the time difference between the current time and the expiry date        
  165.             $timestamp=date('Y-m-d H:i:s');
  166.             $time=time_diff($timestamp,$expiry);
  167.             if ((! $time) || ($time<0)) 
  168.             {
  169.                return true;
  170.             }
  171.          }
  172.       } 
  173.       else 
  174.       {
  175.          # Default is not to expire, do on debug1 to flood logs less
  176.          #$this->logger->logit("check_for_expired option is not enabled");
  177.          $this->logger->debug("check_for_expired option is not enabled");
  178.          return false;
  179.       }
  180.    }
  181.  
  182.    /** Is this EndDevice a Virtual Machine ?
  183.    * It is, if the vendor string associated to the first 3 bytes of the mac
  184.    * contains "vmware", "parallels" or "microsoft"
  185.    * @return boolean    Tell whether this EndDevice is a Virtual Machine
  186.    */
  187.    public function isVM()
  188.    {
  189.       # Check if VM checks are enabled 
  190.       if ($this->conf->vm_lan_like_host
  191.       {
  192.          if (stristr($this->getVendor(),"vmware")) 
  193.             return true;    # The original
  194.          if (stristr($this->getVendor(),"parallels")) 
  195.             return true;    # Mac VMWare-alike
  196.      if (stristr($this->getVendor(),"microsoft")) 
  197.             return true;    # VirtualPC
  198.       }
  199.       else
  200.       {
  201.          $this->logger->logit("vm_lan_like_host option is not enabled");
  202.          return false
  203.       }
  204.    }
  205.  
  206.    /** 
  207.    * Is this device 'killed'? 
  208.    * @return boolean    Tell if the EndDevice is killed
  209.    */
  210.    public function isKilled()
  211.    {
  212.       if ($this->status==7)
  213.          return true;
  214.       else
  215.          return false;
  216.    }
  217.  
  218.    /**
  219.    *  Is this device 'active'? 
  220.    * @return boolean    Tell if the EndDevice is active
  221.    */
  222.    public function isActive() 
  223.    {
  224.       if ($this->status==1)
  225.          return true;
  226.       else
  227.          return false;
  228.    }
  229.     
  230.    /** 
  231.    * Is this device 'unknown'? 
  232.    * @return boolean    Tell if the EndDevice is unknown
  233.    */
  234.    public function isUnknown() 
  235.    {
  236.       if ($this->status==0)
  237.          return true;
  238.       else
  239.          return false;
  240.    }
  241.  
  242.    /**
  243.    * Is this device 'unmanaged'?
  244.    * @return boolean    Tell if the EndDevice is unmanaged
  245.    */
  246.    public function isUnmanaged()
  247.    {
  248.       if ($this->status==3)
  249.          return true;
  250.       else
  251.          return false;
  252.    }
  253.     
  254.     
  255.    # Information about system -------------------------------------------------
  256.     
  257.    /**
  258.    * Return the vendor name associated to first 3 bytes of the mac address 
  259.    * @return mixed    Vendor for this MAC address
  260.    */
  261.    public function getVendor() 
  262.    {
  263.       /**
  264.       * @todo Implement a vendor Cache in an arry to save an sql statement per request ;-)
  265.       */
  266.       $mac=preg_replace('/\./','',$this->mac);
  267.       $prefix="$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
  268.       $query="SELECT vendor FROM ethernet WHERE mac LIKE '%$prefix%';";
  269.       $this->logger->debug($query,3);
  270.       $vendor=v_sql_1_select($query);
  271.       if $vendor )
  272.          return '';
  273.       $vendor=rtrim($vendor,',');
  274.       return trim($vendor);
  275.    }
  276.     
  277.    /**
  278.    * Return the Name of the systems' default VLAN 
  279.    * @return mixed     Default VLAN assigned to this device
  280.    */
  281.    public function getVlanName()
  282.    {
  283.       return $this->vlan_name;    
  284.    }
  285.  
  286.    /**
  287.    * Return the vlan id assigned to this EndDevice
  288.    * @return mixed    vid
  289.    */
  290.    public function getVlanID() 
  291.    {
  292.       return $this->vid;
  293.    }
  294.  
  295.    /**
  296.    * Universal Accessor Method
  297.    * We are redirecting all unresolved method calls to this handler, 
  298.    * so that we can emulate arbitraty accessor methods.
  299.    * With this trick, the user can add new fields to the system tables
  300.    * and will be able to access them in the policy as
  301.    * $system->getDBFieldName() without haveing to change this class
  302.    * @throws        If the db field does not exist, Log Error and Deny as default action
  303.    * @return mixed    Property
  304.    */
  305.    public function __call($methodName, $parameters) {
  306.       # If methodname starts with get 
  307.       if (substr($methodName,0,3) == "get") {
  308.          $dbfieldname = substr($methodName,3);
  309.      foreach(array_keys($this->db_rowas $key{
  310.         if (strtolower($key) == strtolower($dbfieldname)) {
  311.                if (is_numeric($this->db_row[$key]))
  312.                {
  313.                   if (stristr($this->db_row[$key],'.'))
  314.                      return $this->db_row[$key];
  315.                   else if $this->db_row[$key)
  316.                      return (int)$this->db_row[$key];
  317.                   else
  318.                      return false;
  319.                }
  320.                else
  321.                {
  322.                   return $this->db_row[$key];
  323.                }
  324.         }
  325.      }
  326.       }
  327.       $this->logger->debug("Field $methodName doesn't exist",2);
  328.    }
  329.     
  330.    /**
  331.    * Get the value of one property if it exists
  332.    * @param mixed $key          Property to lookup
  333.    * @return mixed              The value of the wanted property, or false if such a property doesn't exist
  334.    */
  335.    public function __get($key)                                                  //Get the value of one var
  336.    {
  337.       if (array_key_exists($key,$this->db_row))
  338.       {
  339.          if (is_numeric($this->db_row[$key]))
  340.          {
  341.             if (stristr($this->db_row[$key],'.'))
  342.                return $this->db_row[$key];
  343.             else if $this->db_row[$key)
  344.                return (int)$this->db_row[$key];
  345.             else 
  346.                return false;
  347.          }
  348.          else
  349.          {
  350.             return $this->db_row[$key];
  351.          }
  352.       }
  353.    }
  354.  
  355.    /**
  356.    * Set the value of one property if it exists
  357.    * @param mixed $key          Property to lookup
  358.    * @param mixed $value        Value to set the desired property to
  359.    * @throws             Deny if there was an attempt to set an unknown property
  360.    */
  361.    protected function __set($key,$value)                                                  //Set the value of one var
  362.    {
  363.       if (array_key_exists($key,$this->db_row))
  364.          $this->db_row[$key]=$value;
  365.       else
  366.          $this->logger->debug('Attempting to set an unknown property',2);
  367.    }
  368.  
  369.  
  370.    /**
  371.    * Return all properties assigned to this system. This method is here only for debugging purposes, please delete it after
  372.    * @return array     All properties present
  373.    */
  374.    public function getAllProps()
  375.    {
  376.       return $this->db_row;
  377.    }
  378.  
  379.    /**
  380.    * This method indicates if a system is in the db. This flag was set in the constructor.
  381.    * @return boolean    Value of the 'in_db' property
  382.    */
  383.    public function inDB()
  384.    {
  385.       return $this->in_db;
  386.    }
  387.  
  388.    /**
  389.    * Catch DB inserts.
  390.    * Check if the function is called from a postconnect method in an object which is a child of Policy.
  391.    * This function should be called from inside the update method. To prevent inserting of code in other methods, new code
  392.    * should be added.
  393.    * This is a basic checking, in the future this code may be enhanced.
  394.    * @return boolean        True if function was called from a valid method, false otherwise
  395.    */
  396.    function check_calling_method()
  397.    {
  398.       $backtrace = debug_backtrace();
  399.       array_shift($backtrace);    //Remove call to check_calling_method from the backtrace;
  400.       $ok=0;
  401.       #Are we calling from a child of EndDevice which uses CallWrapper?
  402.       if ( isset($backtrace[1]['class']) && isset($backtrace[3]['class']) 
  403.       && ( (strcasecmp(get_parent_class($this),'EndDevice')) == 0 ) && (strcasecmp($backtrace[3]['class'],'Callwrapper') ==0 ))
  404.       {
  405.          #If so, do the necessary corrections to our backtrace
  406.          $temp=array_shift($backtrace);
  407.          $backtrace[0]=$temp;
  408.       }
  409.       #$this->logger->logit(print_r($backtrace,true));
  410.       #$this->logger->logit(get_parent_class($this));
  411.       {
  412.          #Check if we are using callwrapper
  413.          if ( (strcasecmp($backtrace[2]['class'],'Callwrapper')==0) && (strcasecmp($backtrace[2]['function'],'__call')==0))
  414.          {
  415.             #Check if the class is a child of Policy and if calling method is postconnect
  416.             #if ( ( $backtrace[4]['class'] instanceof Policy ) && (strcasecmp($backtrace[4]['function'],'postconnect')!=0) )
  417.             if (strcasecmp($backtrace[4]['function'],'postconnect')!=0)
  418.             {
  419.                $this->logger->logit("{$backtrace[0]['function']} method must only be called from a postconnect, since it changes the database. Pre-connnect can run secondary servers (which have read-only tables), and must not change the DB. This method was called from {$backtrace[4]['function']}. Aborting insert operation",LOG_WARNING);
  420.                return false;
  421.             }
  422.             else
  423.             {
  424.                $ok++;
  425.             }
  426.          }
  427.          #Not using callwrapper
  428.          else if (strcasecmp($backtrace[0]['class'],'EndDevice')==0)
  429.          {
  430.             #Are we calling from a child of EndDevice?
  431.             if ( (strcasecmp($backtrace[1]['function'],'postconnect')) != 0 )
  432.             {
  433.                #If so, do the necessary corrections to our backtrace
  434.                $temp=array_shift($backtrace);
  435.                $backtrace[0]=$temp;
  436.             }
  437.             #Check if the class is a child of Policy and if calling method is postconnect
  438.             #if ( (strcasecmp($backtrace[1]['class'],'Policy')==0 ) && (strcasecmp($backtrace[1]['function'],'postconnect')!=0))
  439.             if (strcasecmp($backtrace[1]['function'],'postconnect')!=0)
  440.             {
  441.                $this->logger->logit("{$backtrace[0]['function']} method must only be called from a postconnect, since it changes the database. Pre-connnect can run secondary servers (which have read-only tables), and must not change the DB. This method was called from {$backtrace[4]['function']}. Aborting insert operation",LOG_WARNING);
  442.                return false;
  443.             }
  444.             else
  445.             {
  446.                $ok++;
  447.             }
  448.          }
  449.          else
  450.          {
  451.             $this->logger->logit("{$backtrace[0]['function']} method can only be called from a postconnect method, condition not met, aborting insert operation",LOG_WARNING);
  452.             return false;
  453.          }
  454.       }
  455.       if ($ok)
  456.          return true;
  457.       else 
  458.          return false;
  459.    }
  460.  
  461.    /**
  462.    * Update EndDevice information in the DB
  463.    * @return mixed    MAC address and hostname of the updated system, or false if no update was performed
  464.    */
  465.    public function update()
  466.    {
  467.       #Chech if the system is in the DB, and then perform the update
  468.       if ($this->check_calling_method(&& $this->inDB())
  469.       {
  470.          #Send an email alert on connect?
  471.          if ( ! empty($this->db_row['email_on_connect']) )
  472.          {
  473.             $this->logger->mailit("{$this->mac}($this->hostname) is connecting to the network",$this->alert_message.$this->alert_subject,$this->db_row['email_on_connect']);
  474.             #log2db('info',"{$this->mac}($this->hostname) is connecting to the network");
  475.          }
  476.  
  477.          #Check if it's not expired
  478.          if ($this->isExpired(&& $this->conf->disable_expired_devices)
  479.          {
  480.