1337 lines
45 KiB
PHP
1337 lines
45 KiB
PHP
<?php
|
|
/**
|
|
* ---------------------------------------------------------------------
|
|
* GLPI - Gestionnaire Libre de Parc Informatique
|
|
* Copyright (C) 2015-2020 Teclib' and contributors.
|
|
*
|
|
* http://glpi-project.org
|
|
*
|
|
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
|
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
|
*
|
|
* ---------------------------------------------------------------------
|
|
*
|
|
* LICENSE
|
|
*
|
|
* This file is part of GLPI.
|
|
*
|
|
* GLPI is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GLPI 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
|
* ---------------------------------------------------------------------
|
|
*/
|
|
|
|
/**
|
|
* Represent an IPv4 or an IPv6 address. Both textual (ie. human readable)
|
|
* and binary (ie. : used for request) are present
|
|
* @since 0.84
|
|
*/
|
|
|
|
if (!defined('GLPI_ROOT')) {
|
|
die("Sorry. You can't access this file directly");
|
|
}
|
|
|
|
/** Class IPAddress : Represents an IPv4 or an IPv6 address. Both textual (ie. human readable)
|
|
* and binary (ie. : used for SQL requests) are present inside the DB.
|
|
* The class itself contains three protected attributes. If the address is valid, then, these
|
|
* attributes are not empty.
|
|
* This object is usefull for SQL research and binary<=>textual conversions.
|
|
* @warning textual (ie. human readable) representation is not unique for IPv6 addresses :
|
|
* 2001:db8:0:85a3\::ac1f:8001 = 2001:db8:0:85a3:0:0:ac1f:8001
|
|
* @warning All textual representation of IPv6 addresses conforms to RFC 5952 : they are
|
|
* automatically converted by IPAddress::setAddressFromString().
|
|
* @since 0.84
|
|
**/
|
|
class IPAddress extends CommonDBChild {
|
|
|
|
// From CommonDBChild
|
|
static public $itemtype = 'itemtype';
|
|
static public $items_id = 'items_id';
|
|
public $dohistory = false;
|
|
|
|
public $history_blacklist = ['binary_0', 'binary_1', 'binary_2', 'binary_3'];
|
|
|
|
/// $version (integer) : version of the adresse. Should be 4 or 6, or empty if not valid address
|
|
protected $version = '';
|
|
/// $this->textual (string) : human readable of the IP adress (for instance : 192.168.0.0,
|
|
/// 2001:db8:0:85a3\::ac1f:8001)
|
|
protected $textual = '';
|
|
/// $this->binary (bytes[4]) : binary version for the SQL requests. For IPv4 addresses, the
|
|
/// first three bytes are set to [0, 0, 0xffff]
|
|
protected $binary = [0, 0, 0, 0];
|
|
|
|
static $rightname = 'internet';
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CommonDBTM related methods
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* @param IPAddress|string|integer[] $ipaddress (default '')
|
|
**/
|
|
function __construct($ipaddress = '') {
|
|
|
|
// First, be sure that the parent is correctly initialised
|
|
parent::__construct();
|
|
|
|
// If $ipaddress if empty, then, empty address !
|
|
if ($ipaddress != '') {
|
|
|
|
// If $ipaddress if an IPAddress, then just clone it
|
|
if ($ipaddress instanceof IPAddress) {
|
|
$this->version = $ipaddress->version;
|
|
$this->textual = $ipaddress->textual;
|
|
$this->binary = $ipaddress->binary;
|
|
$this->fields = $ipaddress->fields;
|
|
|
|
} else {
|
|
// Else, check a binary then a string
|
|
if (!$this->setAddressFromBinary($ipaddress)) {
|
|
$this->setAddressFromString($ipaddress);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static function getTypeName($nb = 0) {
|
|
return _n('IP address', 'IP addresses', $nb);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param $input
|
|
**/
|
|
function prepareInput($input) {
|
|
|
|
// If $input['name'] does not exists, then, don't check anything !
|
|
if (isset($input['name'])) {
|
|
// WARNING: we must in every case, because, sometimes, fields are partially feels
|
|
|
|
// If previous value differs from current one, then check it !
|
|
$this->setAddressFromString($input['name']);
|
|
if (!$this->is_valid()) {
|
|
if (isset($input['is_dynamic']) && $input['is_dynamic']) {
|
|
// We allow invalid IPs that are dynamics !
|
|
$input['version'] = 0;
|
|
$input['binary_0'] = 0;
|
|
$input['binary_1'] = 0;
|
|
$input['binary_2'] = 0;
|
|
$input['binary_3'] = 0;
|
|
return $input;
|
|
}
|
|
//TRANS: %s is the invalid address
|
|
$msg = sprintf(__('%1$s: %2$s'), __('Invalid IP address'), $input['name']);
|
|
Session::addMessageAfterRedirect($msg, false, ERROR);
|
|
return false;
|
|
}
|
|
}
|
|
if (isset($input['itemtype']) && isset($input['items_id'])) {
|
|
$input['mainitemtype'] = 'NULL';
|
|
$input['mainitems_id'] = 0;
|
|
if ($input['itemtype'] == 'NetworkName') {
|
|
$name = new NetworkName();
|
|
if ($name->getFromDB($input['items_id'])) {
|
|
if ($port = getItemForItemtype($name->getField('itemtype'))) {
|
|
if ($port->getFromDB($name->getField('items_id'))) {
|
|
if (isset($port->fields['itemtype']) && isset($port->fields['items_id'])) {
|
|
$input['mainitemtype'] = $port->fields['itemtype'];
|
|
$input['mainitems_id'] = $port->fields['items_id'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_merge($input, $this->setArrayFromAddress($input, "version", "name", "binary"));
|
|
}
|
|
|
|
|
|
function prepareInputForAdd($input) {
|
|
return parent::prepareInputForAdd($this->prepareInput($input));
|
|
}
|
|
|
|
|
|
function prepareInputForUpdate($input) {
|
|
return parent::prepareInputForUpdate($this->prepareInput($input));
|
|
}
|
|
|
|
|
|
function post_addItem() {
|
|
IPAddress_IPNetwork::addIPAddress($this);
|
|
parent::post_addItem();
|
|
}
|
|
|
|
|
|
function post_updateItem($history = 1) {
|
|
|
|
if ((isset($this->oldvalues['name']))
|
|
|| (isset($this->oldvalues['entities_id']))) {
|
|
|
|
$link = new IPAddress_IPNetwork();
|
|
$link->cleanDBonItemDelete($this->getType(), $this->getID());
|
|
$link->addIPAddress($this);
|
|
}
|
|
|
|
parent::post_updateItem($history);
|
|
}
|
|
|
|
|
|
function cleanDBonPurge() {
|
|
|
|
$this->deleteChildrenAndRelationsFromDb(
|
|
[
|
|
IPAddress_IPNetwork::class,
|
|
]
|
|
);
|
|
}
|
|
|
|
|
|
function post_getFromDB () {
|
|
|
|
// Don't forget set local object from DB field
|
|
$this->setAddressFromArray($this->fields, "version", "name", "binary");
|
|
}
|
|
|
|
|
|
static function showForItem(CommonGLPI $item, $withtemplate = 0) {
|
|
global $CFG_GLPI;
|
|
|
|
if ($item->getType() == 'IPNetwork') {
|
|
|
|
if (isset($_GET["start"])) {
|
|
$start = $_GET["start"];
|
|
} else {
|
|
$start = 0;
|
|
}
|
|
|
|
if (!empty($_GET["order"])) {
|
|
$table_options['order'] = $_GET["order"];
|
|
} else {
|
|
$table_options['order'] = 'ip';
|
|
}
|
|
|
|
$order_by_itemtype = ($table_options['order'] == 'itemtype');
|
|
|
|
$table_options['SQL_options'] = [
|
|
'LIMIT' => $_SESSION['glpilist_limit'],
|
|
'START' => $start
|
|
];
|
|
|
|
$table = new HTMLTableMain();
|
|
$content = "<a href='javascript:reloadTab(\"order=ip\");'>" .
|
|
self::getTypeName(Session::getPluralNumber()) . "</a>";
|
|
$internet_column = $table->addHeader('IP Address', $content);
|
|
$content = sprintf(__('%1$s - %2$s'), _n('Item', 'Items', Session::getPluralNumber()),
|
|
"<a href='javascript:reloadTab(\"order=itemtype\");'>" .
|
|
__('Order by item type') . "</a>");
|
|
$item_column = $table->addHeader('Item', $content);
|
|
|
|
if ($order_by_itemtype) {
|
|
foreach ($CFG_GLPI["networkport_types"] as $itemtype) {
|
|
$table_options['group_'.$itemtype] = $table->createGroup($itemtype,
|
|
$itemtype::getTypeName(Session::getPluralNumber()));
|
|
|
|
self::getHTMLTableHeader($item->getType(), $table_options['group_'.$itemtype],
|
|
$item_column, null, $table_options);
|
|
|
|
}
|
|
}
|
|
|
|
$table_options['group_None'] = $table->createGroup('Main', __('Other kind of items'));
|
|
|
|
self::getHTMLTableHeader($item->getType(), $table_options['group_None'], $item_column,
|
|
null, $table_options);
|
|
|
|
self::getHTMLTableCellsForItem(null, $item, null, $table_options);
|
|
|
|
if ($table->getNumberOfRows() > 0) {
|
|
$count = self::countForItem($item);
|
|
Html::printAjaxPager(self::getTypeName(Session::getPluralNumber()), $start, $count);
|
|
|
|
Session::initNavigateListItems(__CLASS__,
|
|
//TRANS : %1$s is the itemtype name,
|
|
// %2$s is the name of the item (used for headings of a list)
|
|
sprintf(__('%1$s = %2$s'),
|
|
$item->getTypeName(1), $item->getName()));
|
|
$table->display(['display_title_for_each_group' => $order_by_itemtype,
|
|
'display_super_for_each_group' => false,
|
|
'display_tfoot' => false]);
|
|
|
|
Html::printAjaxPager(self::getTypeName(Session::getPluralNumber()), $start, $count);
|
|
} else {
|
|
echo "<table class='tab_cadre_fixe'>";
|
|
echo "<tr><th>".__('No IP address found')."</th></tr>";
|
|
echo "</table>";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
|
|
|
|
switch ($item->getType()) {
|
|
case 'IPNetwork' :
|
|
self::showForItem($item, $withtemplate);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @param $item CommonDBTM object
|
|
**/
|
|
static function countForItem(CommonDBTM $item) {
|
|
global $DB;
|
|
|
|
switch ($item->getType()) {
|
|
case 'IPNetwork' :
|
|
$result = $DB->request([
|
|
'COUNT' => 'cpt',
|
|
'FROM' => 'glpi_ipaddresses_ipnetworks',
|
|
'WHERE' => [
|
|
'ipnetworks_id' => $item->getID()
|
|
]
|
|
])->next();
|
|
return $result['cpt'];
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @param $item CommonGLPI object
|
|
* @param $withtemplate (default 0)
|
|
*
|
|
* @return string
|
|
**/
|
|
function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {
|
|
|
|
if ($item->getID()
|
|
&& $item->can($item->getField('id'), READ)) {
|
|
$nb = 0;
|
|
if ($_SESSION['glpishow_count_on_tabs']) {
|
|
$nb = self::countForItem($item);
|
|
}
|
|
return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// IP address specific methods (check, transformation ...)
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Disable the address
|
|
**/
|
|
function disableAddress() {
|
|
|
|
$this->version = '';
|
|
$this->textual = '';
|
|
$this->binary = '';
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Fill an array from the the local address object
|
|
* Fill an array from the the local address object. Usefull for feeding $input variable for
|
|
* preparing input to alter database.
|
|
* If the field name is empty, then, the field is not set
|
|
* If the object is not valid, then, version = 0, textual = "" and binary = (0, 0, 0, 0)
|
|
*
|
|
* @param array $array the array to Fill
|
|
* @param string $versionField the name of the key inside $array that contains de IP version number
|
|
* @param string $textualField the name of the key inside $array that contains de textual version
|
|
* @param string $binaryField the name of the key inside $array that contains de binary.
|
|
* Each element of the array is post-fixed by _i, with i the index
|
|
*
|
|
* @return array the array altered
|
|
**/
|
|
function setArrayFromAddress(array $array, $versionField, $textualField, $binaryField) {
|
|
|
|
if (!empty($versionField)) {
|
|
$version = $this->getVersion();
|
|
if ($version !== false) {
|
|
$array[$versionField] = $version;
|
|
} else {
|
|
$array[$versionField] = "0";
|
|
}
|
|
}
|
|
|
|
if (!empty($textualField)) {
|
|
$textual = $this->getTextual();
|
|
if ($textual !== false) {
|
|
$array[$textualField] = $textual;
|
|
} else {
|
|
$array[$textualField] = "";
|
|
}
|
|
}
|
|
|
|
if (!empty($binaryField)) {
|
|
$binary = $this->getBinary();
|
|
for ($i = 0; $i < 4; ++$i) {
|
|
if ($binary !== false) {
|
|
$array[$binaryField."_".$i] = $binary[$i];
|
|
} else {
|
|
$array[$binaryField."_".$i] = '0';
|
|
}
|
|
}
|
|
}
|
|
return $array;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Fill the local address object from an array
|
|
* Fill the local address object from an array. Usefull for reading $input
|
|
*
|
|
* @param array $array the array to Fill
|
|
* @param string $versionField the name of the key inside $array that contains de IP version number
|
|
* @param string $textualField the name of the key inside $array that contains de textual version
|
|
* @param string $binaryField the name of the key inside $array that contains de binary.
|
|
* Each element of the array is post-fixed by _i, with i the index
|
|
*
|
|
* If the field name is empty, then, the field is not set
|
|
*
|
|
* @return true is succeffully defined
|
|
**/
|
|
function setAddressFromArray(array $array, $versionField, $textualField, $binaryField) {
|
|
|
|
// First, we empty the fields to notify that this address is not valid
|
|
$this->disableAddress();
|
|
|
|
if (!isset($array[$versionField])) {
|
|
return false;
|
|
}
|
|
if (!isset($array[$textualField])) {
|
|
return false;
|
|
}
|
|
if (!isset($array[$binaryField."_0"])
|
|
|| !isset($array[$binaryField."_1"])
|
|
|| !isset($array[$binaryField."_2"])
|
|
|| !isset($array[$binaryField."_3"])) {
|
|
return false;
|
|
}
|
|
|
|
$this->version = $array[$versionField];
|
|
$this->textual = $array[$textualField];
|
|
$this->binary = [];
|
|
$this->binary[0] = ($array[$binaryField."_0"] + 0);
|
|
$this->binary[1] = ($array[$binaryField."_1"] + 0);
|
|
$this->binary[2] = ($array[$binaryField."_2"] + 0);
|
|
$this->binary[3] = ($array[$binaryField."_3"] + 0);
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check address validity
|
|
**/
|
|
function is_valid() {
|
|
return (($this->version != '') && ($this->textual != '') && ($this->binary != ''));
|
|
}
|
|
|
|
|
|
function getVersion() {
|
|
|
|
if ($this->version != '') {
|
|
return $this->version;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function is_ipv4() {
|
|
return ($this->getVersion() == 4);
|
|
}
|
|
|
|
|
|
function is_ipv6() {
|
|
return ($this->getVersion() == 6);
|
|
}
|
|
|
|
|
|
function getTextual() {
|
|
|
|
if ($this->textual != '') {
|
|
return $this->textual;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function getBinary() {
|
|
|
|
if ($this->binary != '') {
|
|
return $this->binary;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Transform an IPv4 address to IPv6
|
|
*
|
|
* @param integer|integer[] $address (bytes[4] or bytes) the address to transform.
|
|
*
|
|
* @return integer[]|false IPv6 mapped address
|
|
**/
|
|
static function getIPv4ToIPv6Address($address) {
|
|
|
|
if (is_numeric($address)) {
|
|
return [0, 0, 0xffff, $address];
|
|
}
|
|
if ((is_array($address)) && (count($address) == 4)) {
|
|
return self::getIPv4ToIPv6Address($address[3]);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check an address to see if it is IPv4 mapped to IPv6 address
|
|
*
|
|
* @param integer[] $address (bytes[4]) the address to check
|
|
*
|
|
* @return true if the address is IPv4 mapped to IPv6
|
|
**/
|
|
static function isIPv4MappedToIPv6Address($address) {
|
|
|
|
if (is_array($address) && (count($address) == 4)) {
|
|
if (($address[0] == 0) && ($address[1] == 0) && ($address[2] == 0xffff)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Replace textual representation by its canonical form.
|
|
*
|
|
* @return void
|
|
**/
|
|
function canonicalizeTextual() {
|
|
$this->setAddressFromBinary($this->getBinary());
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief define an address from a string
|
|
* Convert a textual address (string) to binary one. Opposite function that
|
|
* setAddressFromBinary(). If item is valid ($itemtype not empty and $items_id > 0) then first
|
|
* try to find it inside the database and load it from database.
|
|
* \warning The resulting binary form is created inside the current object
|
|
*
|
|
* @param string $address textual (ie. human readable) address
|
|
* @param string $itemtype type of the item this address has to be attached (default '')
|
|
* @param integer $items_id id of the item this address has to be attached (default -1)
|
|
*
|
|
* @return true if the address is valid.
|
|
**/
|
|
function setAddressFromString($address, $itemtype = "", $items_id = -1) {
|
|
global $DB;
|
|
|
|
$this->disableAddress();
|
|
|
|
if (!is_string($address)) {
|
|
return false;
|
|
}
|
|
|
|
$address = trim($address);
|
|
|
|
if (empty($address)) {
|
|
return false;
|
|
}
|
|
|
|
if (!empty($itemtype)
|
|
&& ($items_id > 0)) {
|
|
$iterator = $DB->request([
|
|
'SELECT' => 'id',
|
|
'FROM' => $this->getTable(),
|
|
'WHERE' => [
|
|
'items_id' => $items_id,
|
|
'itemtype' => $itemtype,
|
|
'name' => $address
|
|
]
|
|
]);
|
|
|
|
if (count($iterator) == 1) {
|
|
$line = $iterator->next();
|
|
if ($this->getFromDB($line["id"])) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
unset($binary);
|
|
$singletons = explode(".", $address);
|
|
// First, check to see if it is an IPv4 address
|
|
if (count($singletons) == 4) {
|
|
$binary = 0;
|
|
foreach ($singletons as $singleton) {
|
|
if (!is_numeric($singleton)) {
|
|
return false;
|
|
}
|
|
$singleton = intval($singleton);
|
|
if (($singleton < 0) || ($singleton > 255)) {
|
|
return false;
|
|
}
|
|
$binary *= 256;
|
|
$binary += intval($singleton);
|
|
}
|
|
$binary = self::getIPv4ToIPv6Address($binary);
|
|
}
|
|
|
|
// Else, it should be an IPv6 address
|
|
$singletons = explode(":", $address);
|
|
// Minimum IPv6 address is "::". So, we check that there is at least 3 singletons in the array
|
|
// And no more than 8 singletons
|
|
if ((count($singletons) >= 3) && (count($singletons) <= 8)) {
|
|
$empty_count = 0;
|
|
foreach ($singletons as $singleton) {
|
|
$singleton = trim($singleton);
|
|
// First, we check that each singleton is 4 hexadecimal !
|
|
if (!preg_match("/^[0-9A-Fa-f]{0,4}$/", $singleton, $regs)) {
|
|
return false;
|
|
}
|
|
if ($singleton === '') {
|
|
$empty_count ++;
|
|
}
|
|
}
|
|
|
|
// EXTREMITY CHECKS :
|
|
// If it starts with colon : the second one must be empty too (ie.: :2001 is not valid)
|
|
$start_with_empty = ($singletons[0] === '');
|
|
if (($start_with_empty) && ($singletons[1] !== '')) {
|
|
return false;
|
|
}
|
|
|
|
// If it ends with colon : the previous one must be empty too (ie.: 2001: is not valid)
|
|
$end_with_empty = ($singletons[count($singletons) - 1] === '');
|
|
if (($end_with_empty) && ($singletons[count($singletons) - 2] !== '')) {
|
|
return false;
|
|
}
|
|
// END OF EXTREMITY CHECKS
|
|
|
|
// The number of empty singletons depends on the type of contraction
|
|
switch ($empty_count) {
|
|
|
|
case 0: // No empty singleton => no contraction at all
|
|
// Thus, its side must be 8 !
|
|
if (count($singletons) != 8) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
// One empty singleton : must be in the middle, otherwise EXTREMITY CHECKS
|
|
// would return false
|
|
break;
|
|
|
|
case 2: // If there is two empty singletons then it must be at the beginning or the end
|
|
if (!($start_with_empty XOR $end_with_empty)) {
|
|
return false;
|
|
}
|
|
// Thus remove one of both empty singletons.
|
|
if ($start_with_empty) {
|
|
unset($singletons[0]);
|
|
} else { // $end_with_empty == true
|
|
unset($singletons[count($singletons) - 1]);
|
|
}
|
|
break;
|
|
|
|
case 3: // Only '::' allows three empty singletons ('::x::' = four empty singletons)
|
|
if (!($start_with_empty AND $end_with_empty)) {
|
|
return false;
|
|
}
|
|
// Middle value must be '' otherwise EXTREMITY CHECKS returned an error
|
|
if (count($singletons) != 3) {
|
|
return false;
|
|
}
|
|
$singletons = [''];
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
|
|
}
|
|
|
|
// Here, we are sure that $singletons are valids and only contains 1 empty singleton that
|
|
// will be convert to as many '0' as necessary to reach 8 singletons
|
|
|
|
$numberEmpty = 9 - count($singletons); // = 8 - (count($singletons) - 1)
|
|
|
|
$epanded = [];
|
|
foreach ($singletons as $singleton) {
|
|
if ($singleton === '') {
|
|
$epanded = array_merge($epanded, array_fill(0, $numberEmpty, 0));
|
|
} else {
|
|
$epanded[] = hexdec($singleton);
|
|
}
|
|
}
|
|
|
|
$binary = [];
|
|
for ($i = 0; $i < 4; $i++) {
|
|
$binary[$i] = $epanded[2 * $i + 0] * 65536 + $epanded[2 * $i + 1];
|
|
}
|
|
|
|
}
|
|
|
|
// $binary is an array that is only defined for IPv4 or IPv6 address
|
|
if (isset($binary)) {
|
|
// Calling setAddressFromBinary is usefull to recheck one more time inside
|
|
// glpi_ipaddresses table and to make canonical textual version
|
|
return $this->setAddressFromBinary($binary, $itemtype, $items_id);
|
|
}
|
|
|
|
// Else, it is not IPv4 nor IPv6 address
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief define an address from a binary
|
|
* Convert a binary address (bytes[4]) to textual one. Opposite function that
|
|
* setAddressFromString(). If item is valid ($itemtype not empty and $items_id > 0) then first
|
|
* try to find it inside the database and load it from database. textual version is condensed
|
|
* one (ie : 2001:db8:0:85a3\::ac1f:8001 rather than 2001:0db8:0000:85a3:0000:0000:ac1f:8001)
|
|
* \warning The resulting binary form is created inside the current object
|
|
*
|
|
* @param integer[] $address (bytes[4]) binary (ie. SQL requests) address
|
|
* @param string $itemtype type of the item this address has to be attached (default '')
|
|
* @param integer $items_id id of the item this address has to be attached (default -1)
|
|
*
|
|
* @return true if the address is valid.
|
|
**/
|
|
function setAddressFromBinary($address, $itemtype = "", $items_id = -1) {
|
|
global $DB;
|
|
|
|
$this->disableAddress();
|
|
if ((!is_array($address)) || (count($address) != 4)) {
|
|
return false;
|
|
}
|
|
if (!empty($itemtype)
|
|
&& ($items_id > 0)) {
|
|
$where = [
|
|
'itemtype' => $itemtype,
|
|
'items_id' => $items_id
|
|
];
|
|
|
|
for ($i = 0; $i < 4; ++$i) {
|
|
$where["binary_$i"] = $address[$i];
|
|
}
|
|
|
|
$iterator = $DB->request([
|
|
'SELECT' => 'id',
|
|
'FROM' => $this->getTable(),
|
|
'WHERE' => $where
|
|
]);
|
|
|
|
if (count($iterator) == 1) {
|
|
$line = $iterator->next();
|
|
if ($this->getFromDB($line["id"])) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
$binary = [];
|
|
$textual = [];
|
|
$currentNull = "";
|
|
foreach ($address as $singleton) {
|
|
if (is_numeric($singleton)) {
|
|
$singleton = floatval($singleton);
|
|
}
|
|
if (is_float($singleton) || is_double($singleton)) {
|
|
$binary[] = floatval($singleton);
|
|
$singleton = str_pad(dechex($singleton), 8, "0", STR_PAD_LEFT);
|
|
$elt = ltrim(substr($singleton, 0, 4), "0");
|
|
if (empty($elt)) {
|
|
$textual[] = "0";
|
|
$currentNull .= "1";
|
|
} else {
|
|
$currentNull .= "0";
|
|
$textual[] = $elt;
|
|
}
|
|
$elt = ltrim(substr($singleton, 4, 4), "0");
|
|
if (empty($elt)) {
|
|
$textual[] = "0";
|
|
$currentNull .= "1";
|
|
} else {
|
|
$currentNull .= "0";
|
|
$textual[] = $elt;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (isset($binary) && (count($binary) == 4)) {
|
|
if (self::isIPv4MappedToIPv6Address($binary)) {
|
|
$this->version = 4;
|
|
} else {
|
|
$this->version = 6;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
$this->binary = $binary;
|
|
if ($this->getVersion() == 4) {
|
|
$hexValue = str_pad($textual[6], 4, "0", STR_PAD_LEFT).str_pad($textual[7], 4, "0",
|
|
STR_PAD_LEFT);
|
|
$textual = [];
|
|
for ($i = 0; $i < 4; $i++) {
|
|
$textual[] = hexdec($hexValue[2*$i+0].$hexValue[2*$i+1]);
|
|
}
|
|
$textual = implode('.', $textual);
|
|
} else {
|
|
foreach (["11111111", "1111111", "111111", "11111", "1111", "111", "11"] as $elt) {
|
|
$pos = strpos($currentNull, $elt);
|
|
if ($pos !== false) {
|
|
$first = array_slice($textual, 0, $pos);
|
|
if (count($first) == 0) {
|
|
$first = [""];
|
|
}
|
|
$second = array_slice($textual, $pos + strlen($elt));
|
|
if (count($second) == 0) {
|
|
$second = [""];
|
|
}
|
|
$textual = array_merge($first, [""], $second);
|
|
break;
|
|
}
|
|
}
|
|
$textual = implode(':', $textual);
|
|
}
|
|
$this->textual = $textual;
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief add value to the address for iterator on addresses
|
|
*
|
|
* @param integer[] $address (in and out) the address to increment or decrement
|
|
* @param integer $value the value to add or remove. Must be betwwen -0xffffffff and +0xffffffff
|
|
*
|
|
* @return true if the increment is valid
|
|
**/
|
|
static function addValueToAddress(&$address, $value) {
|
|
|
|
if (!is_array($address)
|
|
|| (count($address) != 4)
|
|
|| !is_numeric($value)
|
|
|| ($value < -0xffffffff)
|
|
|| ($value > 0xffffffff)) {
|
|
return false;
|
|
}
|
|
|
|
for ($i = 3; $i >= 0; --$i) {
|
|
$address[$i] += $value;
|
|
if ($address[$i] < 0) {
|
|
$address[$i] += (0x80000000 * 2);
|
|
$value = -1; // For next value for right to left ...
|
|
} else if ($address[$i] > 0xffffffff) {
|
|
$address[$i] -= (0x80000000 * 2);
|
|
$value = 1; // For next value for right to left ...
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief get absolute value of an integer
|
|
* Convert a negative integer to positiv float. That is usefull as integer, in PHP are signed 32
|
|
* bits values. As such, they are limited from +2 147 483 647 to ???2 147 483 648. Thus, when
|
|
* working on integer with bit-wise boolean operations (&, |, ^, ~), the sign of the operand
|
|
* remain inside the result. That make problem as IP address are only positiv ones.
|
|
*
|
|
* @param integer $value the integer that we want the absolute value
|
|
*
|
|
* @return float value that is the absolute of $value
|
|
*
|
|
**/
|
|
static function convertNegativeIntegerToPositiveFloat($value) {
|
|
|
|
if (intval($value) && ($value < 0)) {
|
|
$value = floatval($value) + floatval(0x80000000 * 2);
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Search IP Addresses
|
|
*
|
|
* @param string $IPaddress the address to search
|
|
*
|
|
* @return array each value of the array (corresponding to one IPAddress) is an array of the
|
|
* items from the master item to the IPAddress
|
|
**/
|
|
static function getItemsByIPAddress($IPaddress) {
|
|
global $DB;
|
|
|
|
// We must resolv binary address :
|
|
// 1??) we don't know if the IP address is valid
|
|
// 2??) we don't know its version
|
|
// 3??) binary request is more efficient than textual one (polymorphism of IPv6 addresses)
|
|
$address = new self();
|
|
|
|
if (!$address->setAddressFromString($IPaddress)) {
|
|
return [];
|
|
}
|
|
|
|
$criteria = [
|
|
'SELECT' => 'gip.id',
|
|
'FROM' => 'glpi_ipaddresses AS gip',
|
|
'WHERE' => ['gip.version' => $address->version]
|
|
];
|
|
$startIndex = (($address->version == 4) ? 3 : 1);
|
|
$binaryIP = $address->getBinary();
|
|
for ($i = $startIndex; $i < 4; ++$i) {
|
|
$criteria['WHERE']["gip.binary_$i"] = $binaryIP[$i];
|
|
}
|
|
$iterator = $DB->request($criteria);
|
|
$addressesWithItems = [];
|
|
while ($result = $iterator->next()) {
|
|
if ($address->getFromDB($result['id'])) {
|
|
$addressesWithItems[] = array_merge(array_reverse($address->recursivelyGetItems()),
|
|
[clone $address]);
|
|
}
|
|
}
|
|
return $addressesWithItems;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get an Object ID by its IP address (only if one result is found in the entity)
|
|
*
|
|
* @param string $value the ip address
|
|
* @param integer $entity the entity to look for
|
|
*
|
|
* @return array containing the object ID
|
|
* or an empty array is no value of serverals ID where found
|
|
**/
|
|
static function getUniqueItemByIPAddress($value, $entity) {
|
|
|
|
$addressesWithItems = self::getItemsByIPAddress($value);
|
|
|
|
// Filter : Do not keep ip not linked to asset
|
|
if (count($addressesWithItems)) {
|
|
foreach ($addressesWithItems as $key => $tab) {
|
|
if (isset($tab[0])
|
|
&& (($tab[0] instanceof NetworkName)
|
|
|| ($tab[0] instanceof IPAddress)
|
|
|| ($tab[0] instanceof NetworkPort)
|
|
|| $tab[0]->isDeleted()
|
|
|| $tab[0]->isTemplate()
|
|
|| ($tab[0]->getEntityID() != $entity))) {
|
|
unset($addressesWithItems[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($addressesWithItems)) {
|
|
// Get the first item that is matching entity
|
|
foreach ($addressesWithItems as $items) {
|
|
foreach ($items as $item) {
|
|
if ($item->getEntityID() == $entity) {
|
|
$result = ["id" => $item->getID(),
|
|
"itemtype" => $item->getType()];
|
|
unset($addressesWithItems);
|
|
return $result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if two addresses are equals
|
|
*
|
|
* @param IPAddress|string|integer[] $ipaddress the ip address to check with this
|
|
*
|
|
* @return boolean true if and only if both addresses are binary equals.
|
|
**/
|
|
function equals($ipaddress) {
|
|
|
|
// To normalise the address, just make new one
|
|
$ipaddress = new self($ipaddress);
|
|
|
|
if (!is_array($this->binary)
|
|
|| (count($this->binary) != 4)
|
|
|| (count($ipaddress->binary) != 4)
|
|
|| ($this->version != $ipaddress->version)) {
|
|
return false;
|
|
}
|
|
|
|
for ($index = 0; $index < 4; $index ++) {
|
|
if ($this->binary[$index] != $ipaddress->binary[$index]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param $itemtype
|
|
* @param $base HTMLTableBase object
|
|
* @param $super HTMLTableSuperHeader object (default NULL)
|
|
* @param $father HTMLTableHeader object (default NULL)
|
|
* @param $options array
|
|
**/
|
|
static function getHTMLTableHeader($itemtype, HTMLTableBase $base,
|
|
HTMLTableSuperHeader $super = null,
|
|
HTMLTableHeader $father = null, array $options = []) {
|
|
|
|
$column_name = __CLASS__;
|
|
|
|
$content = self::getTypeName();
|
|
|
|
if ($itemtype == 'IPNetwork') {
|
|
|
|
$base->addHeader('Item', _n('Item', 'Items', 1), $super, $father);
|
|
$base->addHeader('NetworkPort', NetworkPort::getTypeName(0), $super, $father);
|
|
$base->addHeader('NetworkName', NetworkName::getTypeName(1), $super, $father);
|
|
$base->addHeader('Entity', Entity::getTypeName(1), $super, $father);
|
|
} else {
|
|
|
|
if (isset($options['dont_display'][$column_name])) {
|
|
return;
|
|
}
|
|
|
|
if (isset($options['column_links'][$column_name])) {
|
|
$content = "<a href='".$options['column_links'][$column_name]."'>$content</a>";
|
|
}
|
|
|
|
$father = $base->addHeader($column_name, $content, $super, $father);
|
|
|
|
if (isset($options['display_isDynamic']) && ($options['display_isDynamic'])) {
|
|
$father = $base->addHeader($column_name.'_dynamic', __('Automatic inventory'),
|
|
$super, $father);
|
|
}
|
|
|
|
IPNetwork::getHTMLTableHeader(__CLASS__, $base, $super, $father, $options);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @param $row HTMLTableRow object (default NULL)
|
|
* @param $item CommonDBTM object (default NULL)
|
|
* @param $father HTMLTableCell object (default NULL)
|
|
* @param $options array
|
|
**/
|
|
static function getHTMLTableCellsForItem(HTMLTableRow $row = null, CommonDBTM $item = null,
|
|
HTMLTableCell $father = null, array $options = []) {
|
|
global $DB, $CFG_GLPI;
|
|
|
|
if (($item !== null)
|
|
&& ($item->getType() == 'IPNetwork')) {
|
|
|
|
$queries = [];
|
|
$main_criteria = [
|
|
'SELECT' => [
|
|
'ADDR.binary_0 AS binary_0',
|
|
'ADDR.binary_1 AS binary_1',
|
|
'ADDR.binary_2 AS binary_2',
|
|
'ADDR.binary_3 AS binary_3',
|
|
'ADDR.name AS ip',
|
|
'ADDR.id AS id',
|
|
'ADDR.itemtype AS addr_item_type',
|
|
'ADDR.items_id AS addr_item_id',
|
|
'glpi_entities.completename AS entity',
|
|
],
|
|
'FROM' => 'glpi_ipaddresses_ipnetworks AS LINK',
|
|
'INNER JOIN' => [
|
|
'glpi_ipaddresses AS ADDR' => [
|
|
'ON' => [
|
|
'ADDR' => 'id',
|
|
'LINK' => 'ipaddresses_id', [
|
|
'AND' => [
|
|
'ADDR.itemtype' => 'NetworkName',
|
|
'ADDR.is_deleted' => 0
|
|
]
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'LEFT JOIN' => [
|
|
'glpi_entities' => [
|
|
'ON' => [
|
|
'ADDR' => 'entities_id',
|
|
'glpi_entities' => 'id'
|
|
]
|
|
]
|
|
],
|
|
'WHERE' => [
|
|
'LINK.ipnetworks_id' => $item->getID(),
|
|
]
|
|
];
|
|
|
|
foreach ($CFG_GLPI["networkport_types"] as $itemtype) {
|
|
$table = getTableForItemType($itemtype);
|
|
$criteria = $main_criteria;
|
|
$criteria['SELECT'] = array_merge($criteria['SELECT'], [
|
|
'NAME.id AS name_id',
|
|
'PORT.id AS port_id',
|
|
'ITEM.id AS item_id',
|
|
new \QueryExpression("'$itemtype' AS " . $DB->quoteName('item_type'))
|
|
]);
|
|
$criteria['INNER JOIN'] = $criteria['INNER JOIN'] + [
|
|
'glpi_networknames AS NAME' => [
|
|
'ON' => [
|
|
'NAME' => 'id',
|
|
'ADDR' => 'items_id', [
|
|
'AND' => [
|
|
'NAME.itemtype' => 'NetworkPort'
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'glpi_networkports AS PORT' => [
|
|
'ON' => [
|
|
'NAME' => 'items_id',
|
|
'PORT' => 'id', [
|
|
'AND' => [
|
|
'PORT.itemtype' => $itemtype
|
|
]
|
|
]
|
|
]
|
|
],
|
|
"$table AS ITEM" => [
|
|
'ON' => [
|
|
'ITEM' => 'id',
|
|
'PORT' => 'items_id'
|
|
]
|
|
]
|
|
];
|
|
$queries[] = $criteria;
|
|
}
|
|
|
|
$criteria = $main_criteria;
|
|
$criteria['SELECT'] = array_merge($criteria['SELECT'], [
|
|
'NAME.id AS name_id',
|
|
'PORT.id AS port_id',
|
|
new \QueryExpression('NULL AS ' . $DB->quoteName('item_id')),
|
|
new \QueryExpression("NULL AS " . $DB->quoteName('item_type')),
|
|
]);
|
|
$criteria['INNER JOIN'] = $criteria['INNER JOIN'] + [
|
|
'glpi_networknames AS NAME' => [
|
|
'ON' => [
|
|
'NAME' => 'id',
|
|
'ADDR' => 'items_id', [
|
|
'AND' => [
|
|
'NAME.itemtype' => 'NetworkPort'
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'glpi_networkports AS PORT' => [
|
|
'ON' => [
|
|
'NAME' => 'items_id',
|
|
'PORT' => 'id', [
|
|
'AND' => [
|
|
'NOT' => [
|
|
'PORT.itemtype' => $CFG_GLPI['networkport_types']
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
$queries[] = $criteria;
|
|
|
|
$criteria = $main_criteria;
|
|
$criteria['SELECT'] = array_merge($criteria['SELECT'], [
|
|
'NAME.id AS name_id',
|
|
new \QueryExpression("NULL AS " . $DB->quoteName('port_id')),
|
|
new \QueryExpression('NULL AS ' . $DB->quoteName('item_id')),
|
|
new \QueryExpression("NULL AS " . $DB->quoteName('item_type'))
|
|
]);
|
|
$criteria['INNER JOIN'] = $criteria['INNER JOIN'] + [
|
|
'glpi_networknames AS NAME' => [
|
|
'ON' => [
|
|
'NAME' => 'id',
|
|
'ADDR' => 'items_id', [
|
|
'AND' => [
|
|
'NAME.itemtype' => ['!=', 'NetworkPort']
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
$queries[] = $criteria;
|
|
|
|
$criteria = $main_criteria;
|
|
$criteria['SELECT'] = array_merge($criteria['SELECT'], [
|
|
new \QueryExpression("NULL AS name_id"),
|
|
new \QueryExpression("NULL AS port_id"),
|
|
new \QueryExpression('NULL AS item_id'),
|
|
new \QueryExpression("NULL AS item_type")
|
|
]);
|
|
$criteria['INNER JOIN']['glpi_ipaddresses AS ADDR']['ON'][0]['AND']['ADDR.itemtype'] = ['!=', 'NetworkName'];
|
|
$queries[] = $criteria;
|
|
|
|
$union = new \QueryUnion($queries);
|
|
$criteria = [
|
|
'FROM' => $union,
|
|
];
|
|
|
|
if (($options['order'] == 'ip')
|
|
|| ($options['order'] == 'itemtype')) {
|
|
$criteria['ORDERBY'] = [
|
|
'binary_0',
|
|
'binary_1',
|
|
'binary_2',
|
|
'binary_3'
|
|
];
|
|
}
|
|
|
|
if (isset($options['SQL_options'])) {
|
|
$criteria = array_merge($criteria, $options['SQL_options']);
|
|
}
|
|
$iterator = $DB->request($criteria);
|
|
|
|
$canedit = (isset($options['canedit']) && $options['canedit']);
|
|
$options['createRow'] = false;
|
|
$address = new self();
|
|
|
|
$ipaddress = new self();
|
|
$networkname = new NetworkName();
|
|
$networkport = new NetworkPort();
|
|
|
|
$item = null;
|
|
while ($line = $iterator->next()) {
|
|
unset($row);
|
|
|
|
if (($options['order'] == 'itemtype')
|
|
&& !empty($line['item_type'])) {
|
|
$row = $options['group_'.$line['item_type']]->createRow();
|
|
}
|
|
|
|
if (!isset($row)) {
|
|
$row = $options['group_None']->createRow();
|
|
}
|
|
|
|
$ip_header = $row->getGroup()->getSuperHeaderByName('IP Address');
|
|
$item_header= $row->getGroup()->getHeaderByName('Item', 'Item');
|
|
$port_header= $row->getGroup()->getHeaderByName('Item', 'NetworkPort');
|
|
$name_header= $row->getGroup()->getHeaderByName('Item', 'NetworkName');
|
|
$entity_header= $row->getGroup()->getHeaderByName('Item', 'Entity');
|
|
|
|
$row->addCell($ip_header, $line['ip'], $father);
|
|
|
|
if (!empty($line['name_id'])) {
|
|
$networkname->getFromDB($line['name_id']);
|
|
$row->addCell($name_header, $networkname->getLink(), $father);
|
|
|
|
if (!empty($line['port_id'])) {
|
|
$networkport->getFromDB($line['port_id']);
|
|
$row->addCell($port_header, $networkport->getLink(), $father);
|
|
|
|
if ((!empty($line['item_id'])) && (!empty($line['item_type']))) {
|
|
$itemtype = $line['item_type'];
|
|
$item = new $itemtype();
|
|
$item->getFromDB($line['item_id']);
|
|
$row->addCell($item_header, $item->getLink(), $father);
|
|
}
|
|
}
|
|
$row->addCell($entity_header, $line['entity'], $father);
|
|
} else if ((!empty($line['addr_item_id'])) && (!empty($line['addr_item_type']))) {
|
|
$itemtype = $line['addr_item_type'];
|
|
$item = new $itemtype();
|
|
$item->getFromDB($line['addr_item_id']);
|
|
if ($item instanceof CommonDBChild) {
|
|
$items = $item->recursivelyGetItems();
|
|
$elements = [$item->getLink()];
|
|
foreach ($items as $item_) {
|
|
$elements[] = $item_->getLink();
|
|
}
|
|
$row->addCell($item_header, implode(' > ', $elements), $father);
|
|
} else {
|
|
$row->addCell($item_header, $item->getLink(), $father);
|
|
}
|
|
$row->addCell($entity_header, $line['entity'], $father);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if (isset($options['dont_display']['IPAddress'])) {
|
|
return;
|
|
}
|
|
|
|
$header= $row->getGroup()->getHeaderByName('Internet', __CLASS__);
|
|
if (!$header) {
|
|
return;
|
|
}
|
|
|
|
if (empty($item)) {
|
|
if (empty($father)) {
|
|
return;
|
|
}
|
|
$item = $father->getItem();
|
|
}
|
|
|
|
$iterator = $DB->request([
|
|
'SELECT' => 'id',
|
|
'FROM' => self::getTable(),
|
|
'WHERE' => [
|
|
'items_id' => $item->getID(),
|
|
'itemtype' => $item->getType(),
|
|
'is_deleted' => 0
|
|
]
|
|
]);
|
|
|
|
$canedit = (isset($options['canedit']) && $options['canedit']);
|
|
$createRow = (isset($options['createRow']) && $options['createRow']);
|
|
$options['createRow'] = false;
|
|
$address = new self();
|
|
|
|
while ($ipaddress = $iterator->next()) {
|
|
if ($address->getFromDB($ipaddress['id'])) {
|
|
|
|
if ($createRow) {
|
|
$row = $row->createRow();
|
|
}
|
|
|
|
$content = $address->fields['name'];
|
|
$this_cell = $row->addCell($header, $content, $father);
|
|
|
|
if (isset($options['display_isDynamic']) && ($options['display_isDynamic'])) {
|
|
$dyn_header = $row->getGroup()->getHeaderByName('Internet', __CLASS__.'_dynamic');
|
|
$this_cell = $row->addCell($dyn_header,
|
|
Dropdown::getYesNo($address->fields['is_dynamic']),
|
|
$this_cell);
|
|
}
|
|
|
|
IPNetwork::getHTMLTableCellsForItem($row, $address, $this_cell, $options);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|