├── .gitignore ├── .gitmodules ├── README.md ├── config.dist.php ├── functions ├── PEAR │ └── Net │ │ ├── IPv4.php │ │ ├── IPv6.php │ │ └── Ping.php ├── checks │ └── check_php_build.php ├── classes │ ├── class.Addresses.php │ ├── class.Common.php │ ├── class.Config.php │ ├── class.Log.php │ ├── class.Mail.php │ ├── class.PDO.php │ ├── class.Result.php │ ├── class.Scan.php │ ├── class.Subnets.php │ ├── class.Thread.php │ └── class.phpipamAgent.php ├── functions.php ├── global_functions.php └── version.php ├── index.php └── misc ├── CHANGELOG └── gpl-3.0.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .svn 2 | db/bkp 3 | config.php 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "functions/PHPMailer"] 2 | path = functions/PHPMailer 3 | url = https://github.com/PHPMailer/PHPMailer.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | phpipam-agent is a scanning agent for a phpipam server to be deployed to remote servers 3 | 4 | ## License 5 | phpipam is released under the GPL v3 license. See misc/gpl-3.0.txt. 6 | 7 | ## Requirements 8 | - 64bit PHP version 5.4+ with following modules 9 | - pdo, pdo_mysql : Adds support for mysql connections (if type=mysql) 10 | - gmp : Adds support for dev-libs/gmp (GNU MP library) -> to calculate IPv6 networks 11 | - json : Adds supports for JSON data-interexchange format 12 | - pcntl : Adds supports for threading via CLI ( not supported by windows ) 13 | - PHP PEAR support (dev-php/pear) 14 | 15 | ## Install 16 | 17 | ``` 18 | git clone --recursive https://github.com/phpipam/phpipam-agent/ phpipam-agent 19 | ``` 20 | 21 | Just run index.php script with discover or update as argument. 22 | 23 | - Make sure this client has read/write access to main phpipam database. 24 | ```SQL 25 | GRANT SELECT on `phpipam`.* TO 'username'@'hostname' identified by "password"; 26 | GRANT INSERT,UPDATE on `phpipam`.`ipaddresses` TO 'username'@'hostname' identified by "password"; 27 | GRANT UPDATE on phpipam.scanAgents TO 'username'@'hostname' identified by "password"; 28 | ``` 29 | - If you will remove inactive dhcp/autodiscovered also this is needed 30 | ```SQL 31 | GRANT DELETE on `phpipam`.`ipaddresses` TO 'username'@'hostname' identified by "password"; 32 | ``` 33 | 34 | ## Update 35 | 36 | ``` 37 | cd phpipam-agent 38 | git pull 39 | git submodule update --init --recursive 40 | ``` 41 | 42 | ## Scheduled scans 43 | For scheduled scans you have to run a script from cron. Add something like the following to your cron to scan 44 | every 15 minutes: 45 | 46 | ``` 47 | */15 * * * * php /where/your/agent/index.php update 48 | */15 * * * * php /where/your/agent/index.php discover 49 | ``` 50 | ## Contact 51 | `miha.petkovsek@gmail.com` 52 | -------------------------------------------------------------------------------- /config.dist.php: -------------------------------------------------------------------------------- 1 | 16 | * @author Marco Kaiser 17 | * @author Florian Anderiasch 18 | * @copyright 1997-2005 The PHP Group 19 | * @license http://www.php.net/license/3_01.txt PHP License 3.01 20 | * @version CVS: $Id: IPv4.php 302879 2010-08-30 06:52:41Z bate $ 21 | * @link http://pear.php.net/package/Net_IPv4 22 | */ 23 | 24 | require_once 'PEAR.php'; 25 | 26 | // {{{ GLOBALS 27 | /** 28 | * Map of bitmasks to subnets 29 | * 30 | * This array contains every valid netmask. The index of the dot quad 31 | * netmask value is the corresponding CIDR notation (bitmask). 32 | * 33 | * @global array $GLOBALS['Net_IPv4_Netmask_Map'] 34 | */ 35 | $GLOBALS['Net_IPv4_Netmask_Map'] = array( 36 | 0 => "0.0.0.0", 37 | 1 => "128.0.0.0", 38 | 2 => "192.0.0.0", 39 | 3 => "224.0.0.0", 40 | 4 => "240.0.0.0", 41 | 5 => "248.0.0.0", 42 | 6 => "252.0.0.0", 43 | 7 => "254.0.0.0", 44 | 8 => "255.0.0.0", 45 | 9 => "255.128.0.0", 46 | 10 => "255.192.0.0", 47 | 11 => "255.224.0.0", 48 | 12 => "255.240.0.0", 49 | 13 => "255.248.0.0", 50 | 14 => "255.252.0.0", 51 | 15 => "255.254.0.0", 52 | 16 => "255.255.0.0", 53 | 17 => "255.255.128.0", 54 | 18 => "255.255.192.0", 55 | 19 => "255.255.224.0", 56 | 20 => "255.255.240.0", 57 | 21 => "255.255.248.0", 58 | 22 => "255.255.252.0", 59 | 23 => "255.255.254.0", 60 | 24 => "255.255.255.0", 61 | 25 => "255.255.255.128", 62 | 26 => "255.255.255.192", 63 | 27 => "255.255.255.224", 64 | 28 => "255.255.255.240", 65 | 29 => "255.255.255.248", 66 | 30 => "255.255.255.252", 67 | 31 => "255.255.255.254", 68 | 32 => "255.255.255.255" 69 | ); 70 | // }}} 71 | // {{{ Net_IPv4 72 | 73 | /** 74 | * Class to provide IPv4 calculations 75 | * 76 | * Provides methods for validating IP addresses, calculating netmasks, 77 | * broadcast addresses, network addresses, conversion routines, etc. 78 | * 79 | * @category Net 80 | * @package Net_IPv4 81 | * @author Eric Kilfoil 82 | * @author Marco Kaiser 83 | * @author Florian Anderiasch 84 | * @copyright 1997-2005 The PHP Group 85 | * @license http://www.php.net/license/3_01.txt PHP License 3.01 86 | * @version CVS: @package_version@ 87 | * @link http://pear.php.net/package/Net_IPv4 88 | * @access public 89 | */ 90 | class Net_IPv4 91 | { 92 | // {{{ properties 93 | var $ip = ""; 94 | var $bitmask = false; 95 | var $netmask = ""; 96 | var $network = ""; 97 | var $broadcast = ""; 98 | var $long = 0; 99 | 100 | //pear 101 | public $pear; 102 | 103 | 104 | //initialize PEAR object on init 105 | public function __construct () { 106 | $this->pear = new PEAR (); 107 | } 108 | 109 | // }}} 110 | // {{{ validateIP() 111 | 112 | /** 113 | * Validate the syntax of the given IP adress 114 | * 115 | * Using the PHP long2ip() and ip2long() functions, convert the IP 116 | * address from a string to a long and back. If the original still 117 | * matches the converted IP address, it's a valid address. This 118 | * function does not allow for IP addresses to be formatted as long 119 | * integers. 120 | * 121 | * @param string $ip IP address in the format x.x.x.x 122 | * @return bool true if syntax is valid, otherwise false 123 | */ 124 | function validateIP($ip) 125 | { 126 | if ($ip == long2ip(ip2long($ip))) { 127 | return true; 128 | } else { 129 | return false; 130 | } 131 | } 132 | 133 | // }}} 134 | // {{{ check_ip() 135 | 136 | /** 137 | * Validate the syntax of the given IP address (compatibility) 138 | * 139 | * This function is identical to Net_IPv4::validateIP(). It is included 140 | * merely for compatibility reasons. 141 | * 142 | * @param string $ip IP address 143 | * @return bool true if syntax is valid, otherwise false 144 | */ 145 | function check_ip($ip) 146 | { 147 | return $this->validateIP($ip); 148 | } 149 | 150 | // }}} 151 | // {{{ validateNetmask() 152 | 153 | /** 154 | * Validate the syntax of a four octet netmask 155 | * 156 | * There are 33 valid netmask values. This function will compare the 157 | * string passed as $netmask to the predefined 33 values and return 158 | * true or false. This is most likely much faster than performing the 159 | * calculation to determine the validity of the netmask. 160 | * 161 | * @param string $netmask Netmask 162 | * @return bool true if syntax is valid, otherwise false 163 | */ 164 | function validateNetmask($netmask) 165 | { 166 | if (! in_array($netmask, $GLOBALS['Net_IPv4_Netmask_Map'])) { 167 | return false; 168 | } 169 | return true; 170 | } 171 | 172 | // }}} 173 | // {{{ parseAddress() 174 | 175 | /** 176 | * Parse a formatted IP address 177 | * 178 | * Given a network qualified IP address, attempt to parse out the parts 179 | * and calculate qualities of the address. 180 | * 181 | * The following formats are possible: 182 | * 183 | * [dot quad ip]/[ bitmask ] 184 | * [dot quad ip]/[ dot quad netmask ] 185 | * [dot quad ip]/[ hex string netmask ] 186 | * 187 | * The first would be [IP Address]/[BitMask]: 188 | * 192.168.0.0/16 189 | * 190 | * The second would be [IP Address] [Subnet Mask in dot quad notation]: 191 | * 192.168.0.0/255.255.0.0 192 | * 193 | * The third would be [IP Address] [Subnet Mask as Hex string] 194 | * 192.168.0.0/ffff0000 195 | * 196 | * Usage: 197 | * 198 | * $cidr = '192.168.0.50/16'; 199 | * $net = Net_IPv4::parseAddress($cidr); 200 | * echo $net->network; // 192.168.0.0 201 | * echo $net->ip; // 192.168.0.50 202 | * echo $net->broadcast; // 192.168.255.255 203 | * echo $net->bitmask; // 16 204 | * echo $net->long; // 3232235520 (long/double version of 192.168.0.50) 205 | * echo $net->netmask; // 255.255.0.0 206 | * 207 | * @param string $ip IP address netmask combination 208 | * @return object true if syntax is valid, otherwise false 209 | */ 210 | function parseAddress($address) 211 | { 212 | $myself = new Net_IPv4; 213 | 214 | // ctype fix 215 | if(!function_exists('ctype_digit')) { 216 | function ctype_digit ($int) { 217 | return is_numeric($int); 218 | } 219 | } 220 | 221 | if (strchr($address, "/")) { 222 | $parts = explode("/", $address); 223 | if (! $myself->validateIP($parts[0])) { 224 | return $this->pear->raiseError("invalid IP address"); 225 | } 226 | $myself->ip = $parts[0]; 227 | 228 | // Check the style of netmask that was entered 229 | /* 230 | * a hexadecimal string was entered 231 | */ 232 | if (preg_match("/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i", $parts[1], $regs)) { 233 | // hexadecimal string 234 | $myself->netmask = hexdec($regs[1]) . "." . hexdec($regs[2]) . "." . 235 | hexdec($regs[3]) . "." . hexdec($regs[4]); 236 | 237 | /* 238 | * a standard dot quad netmask was entered. 239 | */ 240 | } else if (strchr($parts[1], ".")) { 241 | if (! $myself->validateNetmask($parts[1])) { 242 | return $this->pear->raiseError("invalid netmask value"); 243 | } 244 | $myself->netmask = $parts[1]; 245 | 246 | /* 247 | * a CIDR bitmask type was entered 248 | */ 249 | } else if (ctype_digit($parts[1]) && $parts[1] >= 0 && $parts[1] <= 32) { 250 | // bitmask was entered 251 | $myself->bitmask = $parts[1]; 252 | 253 | /* 254 | * Some unknown format of netmask was entered 255 | */ 256 | } else { 257 | return $this->pear->raiseError("invalid netmask value"); 258 | } 259 | $myself->calculate(); 260 | return $myself; 261 | } else if ($myself->validateIP($address)) { 262 | $myself->ip = $address; 263 | return $myself; 264 | } else { 265 | return $this->pear->raiseError("invalid IP address"); 266 | } 267 | } 268 | 269 | // }}} 270 | // {{{ calculate() 271 | 272 | /** 273 | * Calculates network information based on an IP address and netmask. 274 | * 275 | * Fully populates the object properties based on the IP address and 276 | * netmask/bitmask properties. Once these two fields are populated, 277 | * calculate() will perform calculations to determine the network and 278 | * broadcast address of the network. 279 | * 280 | * @return mixed true if no errors occured, otherwise PEAR_Error object 281 | */ 282 | function calculate() 283 | { 284 | $validNM = $GLOBALS['Net_IPv4_Netmask_Map']; 285 | 286 | if (! is_a($this, "net_ipv4")) { 287 | $myself = new Net_IPv4; 288 | return $this->pear->raiseError("cannot calculate on uninstantiated Net_IPv4 class"); 289 | } 290 | 291 | /* Find out if we were given an ip address in dot quad notation or 292 | * a network long ip address. Whichever was given, populate the 293 | * other field 294 | */ 295 | if (strlen($this->ip)) { 296 | if (! $this->validateIP($this->ip)) { 297 | return $this->pear->raiseError("invalid IP address"); 298 | } 299 | $this->long = $this->ip2double($this->ip); 300 | } else if (is_numeric($this->long)) { 301 | $this->ip = long2ip($this->long); 302 | } else { 303 | return $this->pear->raiseError("ip address not specified"); 304 | } 305 | 306 | /* 307 | * Check to see if we were supplied with a bitmask or a netmask. 308 | * Populate the other field as needed. 309 | */ 310 | if (strlen($this->bitmask)) { 311 | $this->netmask = $validNM[$this->bitmask]; 312 | } else if (strlen($this->netmask)) { 313 | $validNM_rev = array_flip($validNM); 314 | $this->bitmask = $validNM_rev[$this->netmask]; 315 | } else { 316 | return $this->pear->raiseError("netmask or bitmask are required for calculation"); 317 | } 318 | $this->network = long2ip(ip2long($this->ip) & ip2long($this->netmask)); 319 | $this->broadcast = long2ip(ip2long($this->ip) | 320 | (ip2long($this->netmask) ^ ip2long("255.255.255.255"))); 321 | return true; 322 | } 323 | 324 | // }}} 325 | // {{{ getNetmask() 326 | 327 | function getNetmask($length) 328 | { 329 | if (! PEAR::isError($ipobj = Net_IPv4::parseAddress("0.0.0.0/" . $length))) { 330 | $mask = $ipobj->netmask; 331 | unset($ipobj); 332 | return $mask; 333 | } 334 | return false; 335 | } 336 | 337 | // }}} 338 | // {{{ getNetLength() 339 | 340 | function getNetLength($netmask) 341 | { 342 | if (! PEAR::isError($ipobj = Net_IPv4::parseAddress("0.0.0.0/" . $netmask))) { 343 | $bitmask = $ipobj->bitmask; 344 | unset($ipobj); 345 | return $bitmask; 346 | } 347 | return false; 348 | } 349 | 350 | // }}} 351 | // {{{ getSubnet() 352 | 353 | function getSubnet($ip, $netmask) 354 | { 355 | if (! PEAR::isError($ipobj = Net_IPv4::parseAddress($ip . "/" . $netmask))) { 356 | $net = $ipobj->network; 357 | unset($ipobj); 358 | return $net; 359 | } 360 | return false; 361 | } 362 | 363 | // }}} 364 | // {{{ inSameSubnet() 365 | 366 | function inSameSubnet($ip1, $ip2) 367 | { 368 | if (! is_object($ip1) || strcasecmp(get_class($ip1), 'net_ipv4') <> 0) { 369 | $ipobj1 = Net_IPv4::parseAddress($ip1); 370 | if (PEAR::isError($ipobj)) { 371 | return $this->pear->raiseError("IP addresses must be an understood format or a Net_IPv4 object"); 372 | } 373 | } 374 | if (! is_object($ip2) || strcasecmp(get_class($ip2), 'net_ipv4') <> 0) { 375 | $ipobj2 = Net_IPv4::parseAddress($ip2); 376 | if (PEAR::isError($ipobj)) { 377 | return $this->pear->raiseError("IP addresses must be an understood format or a Net_IPv4 object"); 378 | } 379 | } 380 | if ($ipobj1->network == $ipobj2->network && 381 | $ipobj1->bitmask == $ipobj2->bitmask) { 382 | return true; 383 | } 384 | return false; 385 | } 386 | 387 | // }}} 388 | // {{{ atoh() 389 | 390 | /** 391 | * Converts a dot-quad formatted IP address into a hexadecimal string 392 | * @param string $addr IP-adress in dot-quad format 393 | * @return mixed false if invalid IP and hexadecimal representation as string if valid 394 | */ 395 | function atoh($addr) 396 | { 397 | if (! Net_IPv4::validateIP($addr)) { 398 | return false; 399 | } 400 | $ap = explode(".", $addr); 401 | return sprintf("%02x%02x%02x%02x", $ap[0], $ap[1], $ap[2], $ap[3]); 402 | } 403 | 404 | // }}} 405 | // {{{ htoa() 406 | 407 | /** 408 | * Converts a hexadecimal string into a dot-quad formatted IP address 409 | * @param string $addr IP-adress in hexadecimal format 410 | * @return mixed false if invalid IP and dot-quad formatted IP as string if valid 411 | */ 412 | function htoa($addr) 413 | { 414 | if (preg_match("/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i", 415 | $addr, $regs)) { 416 | return hexdec($regs[1]) . "." . hexdec($regs[2]) . "." . 417 | hexdec($regs[3]) . "." . hexdec($regs[4]); 418 | } 419 | return false; 420 | } 421 | 422 | // }}} 423 | // {{{ ip2double() 424 | 425 | /** 426 | * Converts an IP address to a PHP double. Better than ip2long because 427 | * a long in PHP is a signed integer. 428 | * @param string $ip dot-quad formatted IP adress 429 | * @return float IP adress as double - positive value unlike ip2long 430 | */ 431 | function ip2double($ip) 432 | { 433 | return (double)(sprintf("%u", ip2long($ip))); 434 | } 435 | 436 | // }}} 437 | // {{{ ipInNetwork() 438 | 439 | /** 440 | * Determines whether or not the supplied IP is within the supplied network. 441 | * 442 | * This function determines whether an IP address is within a network. 443 | * The IP address ($ip) must be supplied in dot-quad format, and the 444 | * network ($network) may be either a string containing a CIDR 445 | * formatted network definition, or a Net_IPv4 object. 446 | * 447 | * @param string $ip A dot quad representation of an IP address 448 | * @param string $network A string representing the network in CIDR format or a Net_IPv4 object. 449 | * @return bool true if the IP address exists within the network 450 | */ 451 | function ipInNetwork($ip, $network) 452 | { 453 | if (! is_object($network) || strcasecmp(get_class($network), 'net_ipv4') <> 0) { 454 | $network = Net_IPv4::parseAddress($network); 455 | } 456 | if (strcasecmp(get_class($network), 'pear_error') === 0) { 457 | return false; 458 | } 459 | $net = Net_IPv4::ip2double($network->network); 460 | $bcast = Net_IPv4::ip2double($network->broadcast); 461 | $ip = Net_IPv4::ip2double($ip); 462 | unset($network); 463 | if ($ip >= $net && $ip <= $bcast) { 464 | return true; 465 | } 466 | return false; 467 | } 468 | 469 | // }}} 470 | } 471 | 472 | // }}} 473 | 474 | /* 475 | * vim: sts=4 ts=4 sw=4 cindent fdm=marker 476 | */ 477 | ?> 478 | -------------------------------------------------------------------------------- /functions/PEAR/Net/IPv6.php: -------------------------------------------------------------------------------- 1 | 20 | * @copyright 2003-2005 The PHP Group 21 | * @license BSD License http://www.opensource.org/licenses/bsd-license.php 22 | * @version CVS: $Id: IPv6.php 336941 2015-06-14 13:19:33Z alexmerz $ 23 | * @link http://pear.php.net/package/Net_IPv6 24 | */ 25 | 26 | // {{{ constants 27 | 28 | /** 29 | * Error message if netmask bits was not found 30 | * @see isInNetmask 31 | */ 32 | define("NET_IPV6_NO_NETMASK_MSG", "Netmask length not found"); 33 | 34 | /** 35 | * Error code if netmask bits was not found 36 | * @see isInNetmask 37 | */ 38 | define("NET_IPV6_NO_NETMASK", 10); 39 | 40 | /** 41 | * Address Type: Unassigned (RFC 1884, Section 2.3) 42 | * @see getAddressType() 43 | */ 44 | define("NET_IPV6_UNASSIGNED", 1); 45 | 46 | /** 47 | * Address Type: Reserved (RFC 1884, Section 2.3) 48 | * @see getAddressType() 49 | */ 50 | define("NET_IPV6_RESERVED", 11); 51 | 52 | /** 53 | * Address Type: Reserved for NSAP Allocation (RFC 1884, Section 2.3) 54 | * @see getAddressType() 55 | */ 56 | define("NET_IPV6_RESERVED_NSAP", 12); 57 | 58 | /** 59 | * Address Type: Reserved for IPX Allocation (RFC 1884, Section 2.3) 60 | * @see getAddressType() 61 | */ 62 | define("NET_IPV6_RESERVED_IPX", 13); 63 | 64 | /** 65 | * Address Type: Reserved for Geographic-Based Unicast Addresses 66 | * (RFC 1884, Section 2.3) 67 | * @see getAddressType() 68 | */ 69 | define("NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC", 14); 70 | 71 | /** 72 | * Address Type: Provider-Based Unicast Address (RFC 1884, Section 2.3) 73 | * @see getAddressType() 74 | */ 75 | define("NET_IPV6_UNICAST_PROVIDER", 22); 76 | 77 | /** 78 | * Address Type: Multicast Addresses (RFC 1884, Section 2.3) 79 | * @see getAddressType() 80 | */ 81 | define("NET_IPV6_MULTICAST", 31); 82 | 83 | /** 84 | * Address Type: Link Local Use Addresses (RFC 1884, Section 2.3) 85 | * @see getAddressType() 86 | */ 87 | define("NET_IPV6_LOCAL_LINK", 42); 88 | 89 | /** 90 | * Address Type: Link Local Use Addresses (RFC 1884, Section 2.3) 91 | * @see getAddressType() 92 | */ 93 | define("NET_IPV6_LOCAL_SITE", 43); 94 | 95 | /** 96 | * Address Type: Address range to embedded IPv4 ip in an IPv6 address (RFC 4291, Section 2.5.5) 97 | * @see getAddressType() 98 | */ 99 | define("NET_IPV6_IPV4MAPPING", 51); 100 | 101 | /** 102 | * Address Type: Unspecified (RFC 4291, Section 2.5.2) 103 | * @see getAddressType() 104 | */ 105 | define("NET_IPV6_UNSPECIFIED", 52); 106 | 107 | /** 108 | * Address Type: Unspecified (RFC 4291, Section 2.5.3) 109 | * @see getAddressType() 110 | */ 111 | define("NET_IPV6_LOOPBACK", 53); 112 | 113 | /** 114 | * Address Type: address can not assigned to a specific type 115 | * @see getAddressType() 116 | */ 117 | define("NET_IPV6_UNKNOWN_TYPE", 1001); 118 | 119 | // }}} 120 | // {{{ Net_IPv6 121 | 122 | /** 123 | * Class to validate and to work with IPv6 addresses. 124 | * 125 | * @category Net 126 | * @package Net_IPv6 127 | * @author Alexander Merz 128 | * @author 129 | * @author Josh Peck 130 | * @copyright 2003-2010 The PHP Group 131 | * @license BSD License http://www.opensource.org/licenses/bsd-license.php 132 | * @version Release: 1.1.0RC5 133 | * @link http://pear.php.net/package/Net_IPv6 134 | */ 135 | class Net_IPv6 136 | { 137 | 138 | // {{{ separate() 139 | /** 140 | * Separates an IPv6 address into the address and a prefix length part 141 | * 142 | * @param String $ip the (compressed) IP as Hex representation 143 | * 144 | * @return Array the first element is the IP, the second the prefix length 145 | * @since 1.2.0 146 | * @access public 147 | * @static 148 | */ 149 | public static function separate($ip) 150 | { 151 | 152 | $addr = $ip; 153 | $spec = ''; 154 | 155 | if(false === strrpos($ip, '/')) { 156 | 157 | return array($addr, $spec); 158 | 159 | } 160 | 161 | $elements = explode('/', $ip); 162 | 163 | if(2 == count($elements)) { 164 | 165 | $addr = $elements[0]; 166 | $spec = $elements[1]; 167 | 168 | } 169 | 170 | return array($addr, $spec); 171 | 172 | } 173 | // }}} 174 | 175 | // {{{ removeNetmaskSpec() 176 | 177 | /** 178 | * Removes a possible existing prefix length/ netmask specification at an IP addresse. 179 | * 180 | * @param String $ip the (compressed) IP as Hex representation 181 | * 182 | * @return String the IP without netmask length 183 | * @since 1.1.0 184 | * @access public 185 | * @static 186 | */ 187 | public static function removeNetmaskSpec($ip) 188 | { 189 | 190 | $elements = Net_IPv6::separate($ip); 191 | 192 | return $elements[0]; 193 | 194 | } 195 | // }}} 196 | // {{{ removePrefixLength() 197 | 198 | /** 199 | * Tests for a prefix length specification in the address 200 | * and removes the prefix length, if exists 201 | * 202 | * The method is technically identical to removeNetmaskSpec() and 203 | * will be dropped in a future release. 204 | * 205 | * @param String $ip a valid ipv6 address 206 | * 207 | * @return String the address without a prefix length 208 | * @access public 209 | * @static 210 | * @see removeNetmaskSpec() 211 | * @deprecated 212 | */ 213 | public static function removePrefixLength($ip) 214 | { 215 | $pos = strrpos($ip, '/'); 216 | 217 | if (false !== $pos) { 218 | 219 | return substr($ip, 0, $pos); 220 | 221 | } 222 | 223 | return $ip; 224 | } 225 | 226 | // }}} 227 | // {{{ getNetmaskSpec() 228 | 229 | /** 230 | * Returns a possible existing prefix length/netmask specification on an IP addresse. 231 | * 232 | * @param String $ip the (compressed) IP as Hex representation 233 | * 234 | * @return String the netmask spec 235 | * @since 1.1.0 236 | * @access public 237 | * @static 238 | */ 239 | public static function getNetmaskSpec($ip) 240 | { 241 | 242 | $elements = Net_IPv6::separate($ip); 243 | 244 | return $elements[1]; 245 | 246 | } 247 | 248 | // }}} 249 | // {{{ getPrefixLength() 250 | 251 | /** 252 | * Tests for a prefix length specification in the address 253 | * and returns the prefix length, if exists 254 | * 255 | * The method is technically identical to getNetmaskSpec() and 256 | * will be dropped in a future release. 257 | * 258 | * @param String $ip a valid ipv6 address 259 | * 260 | * @return Mixed the prefix as String or false, if no prefix was found 261 | * @access public 262 | * @static 263 | * @deprecated 264 | */ 265 | public static function getPrefixLength($ip) 266 | { 267 | if (preg_match("/^([0-9a-fA-F:]{2,39})\/(\d{1,3})*$/", 268 | $ip, $matches)) { 269 | 270 | return $matches[2]; 271 | 272 | } else { 273 | 274 | return false; 275 | 276 | } 277 | 278 | } 279 | 280 | // }}} 281 | // {{{ getNetmask() 282 | 283 | /** 284 | * Calculates the network prefix based on the netmask bits. 285 | * 286 | * @param String $ip the (compressed) IP in Hex format 287 | * @param int $bits if the number of netmask bits is not part of the IP 288 | * you must provide the number of bits 289 | * 290 | * @return String the network prefix 291 | * @since 1.1.0 292 | * @access public 293 | * @static 294 | */ 295 | public static function getNetmask($ip, $bits = null) 296 | { 297 | if (null==$bits) { 298 | 299 | $elements = explode('/', $ip); 300 | 301 | if (2 == count($elements)) { 302 | 303 | $addr = $elements[0]; 304 | $bits = $elements[1]; 305 | 306 | } else { 307 | 308 | include_once 'PEAR.php'; 309 | 310 | return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG, 311 | NET_IPV6_NO_NETMASK); 312 | } 313 | 314 | } else { 315 | 316 | $addr = $ip; 317 | 318 | } 319 | 320 | $addr = Net_IPv6::uncompress($addr); 321 | $binNetmask = str_repeat('1', $bits).str_repeat('0', 128 - $bits); 322 | 323 | return Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($addr) & $binNetmask); 324 | } 325 | 326 | // }}} 327 | // {{{ isInNetmask() 328 | 329 | /** 330 | * Checks if an (compressed) IP is in a specific address space. 331 | * 332 | * IF the IP does not contains the number of netmask bits (F8000::FFFF/16) 333 | * then you have to use the $bits parameter. 334 | * 335 | * @param String $ip the IP to check (eg. F800::FFFF) 336 | * @param String $netmask the netmask (eg F800::) 337 | * @param int $bits the number of netmask bits to compare, 338 | * if not given in $ip 339 | * 340 | * @return boolean true if $ip is in the netmask 341 | * @since 1.1.0 342 | * @access public 343 | * @static 344 | */ 345 | public static function isInNetmask($ip, $netmask, $bits=null) 346 | { 347 | // try to get the bit count 348 | 349 | if (null == $bits) { 350 | 351 | $elements = explode('/', $ip); 352 | 353 | if (2 == count($elements)) { 354 | 355 | $ip = $elements[0]; 356 | $bits = $elements[1]; 357 | 358 | } else if (null == $bits) { 359 | 360 | $elements = explode('/', $netmask); 361 | 362 | if (2 == count($elements)) { 363 | 364 | $netmask = $elements[0]; 365 | $bits = $elements[1]; 366 | 367 | } 368 | 369 | if (null == $bits) { 370 | 371 | include_once 'PEAR.php'; 372 | return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG, 373 | NET_IPV6_NO_NETMASK); 374 | 375 | } 376 | 377 | } 378 | 379 | } 380 | 381 | $binIp = Net_IPv6::_ip2Bin(Net_IPv6::removeNetmaskSpec($ip)); 382 | $binNetmask = Net_IPv6::_ip2Bin(Net_IPv6::removeNetmaskSpec($netmask)); 383 | 384 | if (null != $bits 385 | && "" != $bits 386 | && 0 == strncmp($binNetmask, $binIp, $bits)) { 387 | 388 | return true; 389 | 390 | } 391 | 392 | return false; 393 | } 394 | 395 | // }}} 396 | // {{{ getAddressType() 397 | 398 | /** 399 | * Returns the type of an IPv6 address. 400 | * 401 | * RFC 2373, Section 2.3 describes several types of addresses in 402 | * the IPv6 addresse space. 403 | * Several addresse types are markers for reserved spaces and as 404 | * consequence a subject to change. 405 | * 406 | * @param String $ip the IP address in Hex format, 407 | * compressed IPs are allowed 408 | * 409 | * @return int one of the addresse type constants 410 | * @access public 411 | * @since 1.1.0 412 | * @static 413 | * 414 | * @see NET_IPV6_UNASSIGNED 415 | * @see NET_IPV6_RESERVED 416 | * @see NET_IPV6_RESERVED_NSAP 417 | * @see NET_IPV6_RESERVED_IPX 418 | * @see NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC 419 | * @see NET_IPV6_UNICAST_PROVIDER 420 | * @see NET_IPV6_MULTICAST 421 | * @see NET_IPV6_LOCAL_LINK 422 | * @see NET_IPV6_LOCAL_SITE 423 | * @see NET_IPV6_IPV4MAPPING 424 | * @see NET_IPV6_UNSPECIFIED 425 | * @see NET_IPV6_LOOPBACK 426 | * @see NET_IPV6_UNKNOWN_TYPE 427 | */ 428 | public static function getAddressType($ip) 429 | { 430 | $ip = Net_IPv6::removeNetmaskSpec($ip); 431 | $binip = Net_IPv6::_ip2Bin($ip); 432 | 433 | if(0 == strncmp(str_repeat('0', 128), $binip, 128)) { // ::/128 434 | 435 | return NET_IPV6_UNSPECIFIED; 436 | 437 | } else if(0 == strncmp(str_repeat('0', 127).'1', $binip, 128)) { // ::/128 438 | 439 | return NET_IPV6_LOOPBACK; 440 | 441 | } else if (0 == strncmp(str_repeat('0', 80).str_repeat('1', 16), $binip, 96)) { // ::ffff/96 442 | 443 | return NET_IPV6_IPV4MAPPING; 444 | 445 | } else if (0 == strncmp('1111111010', $binip, 10)) { 446 | 447 | return NET_IPV6_LOCAL_LINK; 448 | 449 | } else if (0 == strncmp('1111111011', $binip, 10)) { 450 | 451 | return NET_IPV6_LOCAL_SITE; 452 | 453 | } else if (0 == strncmp('111111100', $binip, 9)) { 454 | 455 | return NET_IPV6_UNASSIGNED; 456 | 457 | } else if (0 == strncmp('11111111', $binip, 8)) { 458 | 459 | return NET_IPV6_MULTICAST; 460 | 461 | } else if (0 == strncmp('00000000', $binip, 8)) { 462 | 463 | return NET_IPV6_RESERVED; 464 | 465 | } else if (0 == strncmp('00000001', $binip, 8) 466 | || 0 == strncmp('1111110', $binip, 7)) { 467 | 468 | return NET_IPV6_UNASSIGNED; 469 | 470 | } else if (0 == strncmp('0000001', $binip, 7)) { 471 | 472 | return NET_IPV6_RESERVED_NSAP; 473 | 474 | } else if (0 == strncmp('0000010', $binip, 7)) { 475 | 476 | return NET_IPV6_RESERVED_IPX;; 477 | 478 | } else if (0 == strncmp('0000011', $binip, 7) || 479 | 0 == strncmp('111110', $binip, 6) || 480 | 0 == strncmp('11110', $binip, 5) || 481 | 0 == strncmp('00001', $binip, 5) || 482 | 0 == strncmp('1110', $binip, 4) || 483 | 0 == strncmp('0001', $binip, 4) || 484 | 0 == strncmp('001', $binip, 3) || 485 | 0 == strncmp('011', $binip, 3) || 486 | 0 == strncmp('101', $binip, 3) || 487 | 0 == strncmp('110', $binip, 3)) { 488 | 489 | return NET_IPV6_UNASSIGNED; 490 | 491 | } else if (0 == strncmp('010', $binip, 3)) { 492 | 493 | return NET_IPV6_UNICAST_PROVIDER; 494 | 495 | } else if (0 == strncmp('100', $binip, 3)) { 496 | 497 | return NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC; 498 | 499 | } 500 | 501 | return NET_IPV6_UNKNOWN_TYPE; 502 | } 503 | 504 | // }}} 505 | // {{{ Uncompress() 506 | 507 | /** 508 | * Uncompresses an IPv6 adress 509 | * 510 | * RFC 2373 allows you to compress zeros in an adress to '::'. This 511 | * function expects an valid IPv6 adress and expands the '::' to 512 | * the required zeros. 513 | * 514 | * Example: FF01::101 -> FF01:0:0:0:0:0:0:101 515 | * ::1 -> 0:0:0:0:0:0:0:1 516 | * 517 | * @param String $ip a valid IPv6-adress (hex format) 518 | * @param Boolean $leadingZeros if true, leading zeros are added to each 519 | * block of the address 520 | * (FF01::101 -> 521 | * FF01:0000:0000:0000:0000:0000:0000:0101) 522 | * 523 | * @return String the uncompressed IPv6-adress (hex format) 524 | * @access public 525 | * @see Compress() 526 | * @static 527 | * @author Pascal Uhlmann 528 | */ 529 | public static function uncompress($ip, $leadingZeros = false) 530 | { 531 | 532 | $prefix = Net_IPv6::getPrefixLength($ip); 533 | 534 | if (false === $prefix) { 535 | 536 | $prefix = ''; 537 | 538 | } else { 539 | 540 | $ip = Net_IPv6::removePrefixLength($ip); 541 | $prefix = '/'.$prefix; 542 | 543 | } 544 | 545 | $netmask = Net_IPv6::getNetmaskSpec($ip); 546 | $uip = Net_IPv6::removeNetmaskSpec($ip); 547 | 548 | $c1 = -1; 549 | $c2 = -1; 550 | 551 | if (false !== strpos($uip, '::') ) { 552 | 553 | list($ip1, $ip2) = explode('::', $uip); 554 | 555 | if ("" == $ip1) { 556 | 557 | $c1 = -1; 558 | 559 | } else { 560 | 561 | $pos = 0; 562 | 563 | if (0 < ($pos = substr_count($ip1, ':'))) { 564 | 565 | $c1 = $pos; 566 | 567 | } else { 568 | 569 | $c1 = 0; 570 | 571 | } 572 | } 573 | if ("" == $ip2) { 574 | 575 | $c2 = -1; 576 | 577 | } else { 578 | 579 | $pos = 0; 580 | 581 | if (0 < ($pos = substr_count($ip2, ':'))) { 582 | 583 | $c2 = $pos; 584 | 585 | } else { 586 | 587 | $c2 = 0; 588 | 589 | } 590 | 591 | } 592 | 593 | if (strstr($ip2, '.')) { 594 | 595 | $c2++; 596 | 597 | } 598 | if (-1 == $c1 && -1 == $c2) { // :: 599 | 600 | $uip = "0:0:0:0:0:0:0:0"; 601 | 602 | } else if (-1 == $c1) { // ::xxx 603 | 604 | $fill = str_repeat('0:', 7-$c2); 605 | $uip = str_replace('::', $fill, $uip); 606 | 607 | } else if (-1 == $c2) { // xxx:: 608 | 609 | $fill = str_repeat(':0', 7-$c1); 610 | $uip = str_replace('::', $fill, $uip); 611 | 612 | } else { // xxx::xxx 613 | 614 | $fill = str_repeat(':0:', 6-$c2-$c1); 615 | $uip = str_replace('::', $fill, $uip); 616 | $uip = str_replace('::', ':', $uip); 617 | 618 | } 619 | } 620 | 621 | if(true == $leadingZeros) { 622 | 623 | $uipT = array(); 624 | $uiparts = explode(':', $uip); 625 | 626 | foreach($uiparts as $p) { 627 | 628 | $uipT[] = sprintf('%04s', $p); 629 | 630 | } 631 | 632 | $uip = implode(':', $uipT); 633 | } 634 | 635 | if ('' != $netmask) { 636 | 637 | $uip = $uip.'/'.$netmask; 638 | 639 | } 640 | 641 | return $uip.$prefix; 642 | } 643 | 644 | // }}} 645 | // {{{ Compress() 646 | 647 | /** 648 | * Compresses an IPv6 adress 649 | * 650 | * RFC 2373 allows you to compress zeros in an adress to '::'. This 651 | * function expects an valid IPv6 adress and compresses successive zeros 652 | * to '::' 653 | * 654 | * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 655 | * 0:0:0:0:0:0:0:1 -> ::1 656 | * 657 | * Whe $ip is an already compressed adress the methode returns the value as is, 658 | * also if the adress can be compressed further. 659 | * 660 | * Example: FF01::0:1 -> FF01::0:1 661 | * 662 | * To enforce maximum compression, you can set the second argument $force to true. 663 | * 664 | * Example: FF01::0:1 -> FF01::1 665 | * 666 | * @param String $ip a valid IPv6-adress (hex format) 667 | * @param boolean $force if true the adress will be compresses as best as possible (since 1.2.0) 668 | * 669 | * @return tring the compressed IPv6-adress (hex format) 670 | * @access public 671 | * @see Uncompress() 672 | * @static 673 | * @author elfrink at introweb dot nl 674 | */ 675 | public static function compress($ip, $force = false) 676 | { 677 | 678 | if(false !== strpos($ip, '::')) { // its already compressed 679 | 680 | if(true == $force) { 681 | 682 | $ip = Net_IPv6::uncompress($ip); 683 | 684 | } else { 685 | 686 | return $ip; 687 | 688 | } 689 | 690 | } 691 | 692 | $prefix = Net_IPv6::getPrefixLength($ip); 693 | 694 | if (false === $prefix) { 695 | 696 | $prefix = ''; 697 | 698 | } else { 699 | 700 | $ip = Net_IPv6::removePrefixLength($ip); 701 | $prefix = '/'.$prefix; 702 | 703 | } 704 | 705 | $netmask = Net_IPv6::getNetmaskSpec($ip); 706 | $ip = Net_IPv6::removeNetmaskSpec($ip); 707 | 708 | $ipp = explode(':', $ip); 709 | 710 | for ($i = 0; $i < count($ipp); $i++) { 711 | 712 | $ipp[$i] = dechex(hexdec($ipp[$i])); 713 | 714 | } 715 | 716 | $cip = ':' . join(':', $ipp) . ':'; 717 | 718 | preg_match_all("/(:0)(:0)+/", $cip, $zeros); 719 | 720 | if (count($zeros[0]) > 0) { 721 | 722 | $match = ''; 723 | 724 | foreach ($zeros[0] as $zero) { 725 | 726 | if (strlen($zero) > strlen($match)) { 727 | 728 | $match = $zero; 729 | 730 | } 731 | } 732 | 733 | $cip = preg_replace('/' . $match . '/', ':', $cip, 1); 734 | 735 | } 736 | 737 | $cip = preg_replace('/((^:)|(:$))/', '', $cip); 738 | $cip = preg_replace('/((^:)|(:$))/', '::', $cip); 739 | 740 | if ('' != $netmask) { 741 | 742 | $cip = $cip.'/'.$netmask; 743 | 744 | } 745 | 746 | return $cip.$prefix; 747 | 748 | } 749 | 750 | // }}} 751 | // {{{ recommendedFormat() 752 | /** 753 | * Represent IPv6 address in RFC5952 format. 754 | * 755 | * @param String $ip a valid IPv6-adress (hex format) 756 | * 757 | * @return String the recommended representation of IPv6-adress (hex format) 758 | * @access public 759 | * @see compress() 760 | * @static 761 | * @author koyama at hoge dot org 762 | * @todo This method may become a part of compress() in a further releases 763 | */ 764 | public static function recommendedFormat($ip) 765 | { 766 | $compressed = self::compress($ip, true); 767 | // RFC5952 4.2.2 768 | // The symbol "::" MUST NOT be used to shorten just one 769 | // 16-bit 0 field. 770 | if ((substr_count($compressed, ':') == 7) && 771 | (strpos($compressed, '::') !== false)) { 772 | $compressed = str_replace('::', ':0:', $compressed); 773 | } 774 | return $compressed; 775 | } 776 | // }}} 777 | 778 | // {{{ isCompressible() 779 | 780 | /** 781 | * Checks, if an IPv6 adress can be compressed 782 | * 783 | * @param String $ip a valid IPv6 adress 784 | * 785 | * @return Boolean true, if adress can be compressed 786 | * 787 | * @access public 788 | * @since 1.2.0b 789 | * @static 790 | * @author Manuel Schmitt 791 | */ 792 | public static function isCompressible($ip) 793 | { 794 | 795 | return (bool)($ip != Net_IPv6::compress($address)); 796 | 797 | } 798 | 799 | // }}} 800 | // {{{ SplitV64() 801 | 802 | /** 803 | * Splits an IPv6 adress into the IPv6 and a possible IPv4 part 804 | * 805 | * RFC 2373 allows you to note the last two parts of an IPv6 adress as 806 | * an IPv4 compatible adress 807 | * 808 | * Example: 0:0:0:0:0:0:13.1.68.3 809 | * 0:0:0:0:0:FFFF:129.144.52.38 810 | * 811 | * @param String $ip a valid IPv6-adress (hex format) 812 | * @param Boolean $uncompress if true, the address will be uncompressed 813 | * before processing 814 | * 815 | * @return Array [0] contains the IPv6 part, 816 | * [1] the IPv4 part (hex format) 817 | * @access public 818 | * @static 819 | */ 820 | public static function SplitV64($ip, $uncompress = true) 821 | { 822 | $ip = Net_IPv6::removeNetmaskSpec($ip); 823 | 824 | if ($uncompress) { 825 | 826 | $ip = Net_IPv6::Uncompress($ip); 827 | 828 | } 829 | 830 | if (strstr($ip, '.')) { 831 | 832 | $pos = strrpos($ip, ':'); 833 | 834 | if(false === $pos) { 835 | return array("", $ip); 836 | } 837 | 838 | $ip{$pos} = '_'; 839 | $ipPart = explode('_', $ip); 840 | 841 | return $ipPart; 842 | 843 | } else { 844 | 845 | return array($ip, ""); 846 | 847 | } 848 | } 849 | 850 | // }}} 851 | // {{{ checkIPv6() 852 | 853 | /** 854 | * Checks an IPv6 adress 855 | * 856 | * Checks if the given IP is IPv6-compatible 857 | * 858 | * @param String $ip a valid IPv6-adress 859 | * 860 | * @return Boolean true if $ip is an IPv6 adress 861 | * @access public 862 | * @static 863 | */ 864 | public static function checkIPv6($ip) 865 | { 866 | 867 | $elements = Net_IPv6::separate($ip); 868 | 869 | $ip = $elements[0]; 870 | 871 | if('' != $elements[1] && ( !is_numeric($elements[1]) || 0 > $elements || 128 < $elements[1])) { 872 | 873 | return false; 874 | 875 | } 876 | 877 | $ipPart = Net_IPv6::SplitV64($ip); 878 | $count = 0; 879 | 880 | if (!empty($ipPart[0])) { 881 | $ipv6 = explode(':', $ipPart[0]); 882 | 883 | foreach($ipv6 as $element) { // made a validate precheck 884 | if(!preg_match('/[0-9a-fA-F]*/', $element)) { 885 | return false; 886 | } 887 | } 888 | 889 | for ($i = 0; $i < count($ipv6); $i++) { 890 | 891 | if(4 < strlen($ipv6[$i])) { 892 | 893 | return false; 894 | 895 | } 896 | 897 | $dec = hexdec($ipv6[$i]); 898 | $hex = strtoupper(preg_replace("/^[0]{1,3}(.*[0-9a-fA-F])$/", 899 | "\\1", 900 | $ipv6[$i])); 901 | 902 | if ($ipv6[$i] >= 0 && $dec <= 65535 903 | && $hex == strtoupper(dechex($dec))) { 904 | 905 | $count++; 906 | 907 | } 908 | 909 | } 910 | 911 | if (8 == $count) { 912 | 913 | return true; 914 | 915 | } else if (6 == $count and !empty($ipPart[1])) { 916 | 917 | $ipv4 = explode('.', $ipPart[1]); 918 | $count = 0; 919 | 920 | for ($i = 0; $i < count($ipv4); $i++) { 921 | 922 | if ($ipv4[$i] >= 0 && (integer)$ipv4[$i] <= 255 923 | && preg_match("/^\d{1,3}$/", $ipv4[$i])) { 924 | 925 | $count++; 926 | 927 | } 928 | 929 | } 930 | 931 | if (4 == $count) { 932 | 933 | return true; 934 | 935 | } 936 | 937 | } else { 938 | 939 | return false; 940 | 941 | } 942 | 943 | } else { 944 | 945 | return false; 946 | 947 | } 948 | 949 | } 950 | 951 | // }}} 952 | 953 | // {{{ _parseAddress() 954 | 955 | /** 956 | * Returns the lowest and highest IPv6 address 957 | * for a given IP and netmask specification 958 | * 959 | * The netmask may be a part of the $ip or 960 | * the number of netwask bits is provided via $bits 961 | * 962 | * The result is an indexed array. The key 'start' 963 | * contains the lowest possible IP adress. The key 964 | * 'end' the highest address. 965 | * 966 | * @param String $ipToParse the IPv6 address 967 | * @param String $bits the optional count of netmask bits 968 | * 969 | * @return Array ['start', 'end'] the lowest and highest IPv6 address 970 | * @access public 971 | * @static 972 | * @author Nicholas Williams 973 | */ 974 | 975 | public static function parseAddress($ipToParse, $bits = null) 976 | { 977 | 978 | $ip = null; 979 | $bitmask = null; 980 | 981 | if ( null == $bits ) { 982 | 983 | $elements = explode('/', $ipToParse); 984 | 985 | if ( 2 == count($elements) ) { 986 | 987 | $ip = Net_IPv6::uncompress($elements[0]); 988 | $bitmask = $elements[1]; 989 | 990 | } else { 991 | 992 | include_once 'PEAR.php'; 993 | 994 | return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG, 995 | NET_IPV6_NO_NETMASK); 996 | } 997 | } else { 998 | 999 | $ip = Net_IPv6::uncompress($ipToParse); 1000 | $bitmask = $bits; 1001 | 1002 | } 1003 | 1004 | $binNetmask = str_repeat('1', $bitmask). 1005 | str_repeat('0', 128 - $bitmask); 1006 | $maxNetmask = str_repeat('1', 128); 1007 | $netmask = Net_IPv6::_bin2Ip($binNetmask); 1008 | 1009 | $startAddress = Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($ip) 1010 | & $binNetmask); 1011 | $endAddress = Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($ip) 1012 | | ($binNetmask ^ $maxNetmask)); 1013 | 1014 | return array('start' => $startAddress, 'end' => $endAddress); 1015 | } 1016 | 1017 | // }}} 1018 | 1019 | // {{{ _ip2Bin() 1020 | 1021 | /** 1022 | * Converts an IPv6 address from Hex into Binary representation. 1023 | * 1024 | * @param String $ip the IP to convert (a:b:c:d:e:f:g:h), 1025 | * compressed IPs are allowed 1026 | * 1027 | * @return String the binary representation 1028 | * @access private 1029 | @ @since 1.1.0 1030 | */ 1031 | protected static function _ip2Bin($ip) 1032 | { 1033 | $binstr = ''; 1034 | 1035 | $ip = Net_IPv6::removeNetmaskSpec($ip); 1036 | $ip = Net_IPv6::Uncompress($ip); 1037 | 1038 | $parts = explode(':', $ip); 1039 | 1040 | foreach ( $parts as $v ) { 1041 | 1042 | $str = base_convert($v, 16, 2); 1043 | $binstr .= str_pad($str, 16, '0', STR_PAD_LEFT); 1044 | 1045 | } 1046 | 1047 | return $binstr; 1048 | } 1049 | 1050 | // }}} 1051 | // {{{ _bin2Ip() 1052 | 1053 | /** 1054 | * Converts an IPv6 address from Binary into Hex representation. 1055 | * 1056 | * @param String $bin the IP address as binary 1057 | * 1058 | * @return String the uncompressed Hex representation 1059 | * @access private 1060 | @ @since 1.1.0 1061 | */ 1062 | protected static function _bin2Ip($bin) 1063 | { 1064 | $ip = ""; 1065 | 1066 | if (strlen($bin) < 128) { 1067 | 1068 | $bin = str_pad($bin, 128, '0', STR_PAD_LEFT); 1069 | 1070 | } 1071 | 1072 | $parts = str_split($bin, "16"); 1073 | 1074 | foreach ( $parts as $v ) { 1075 | 1076 | $str = base_convert($v, 2, 16); 1077 | $ip .= $str.":"; 1078 | 1079 | } 1080 | 1081 | $ip = substr($ip, 0, -1); 1082 | 1083 | return $ip; 1084 | } 1085 | 1086 | // }}} 1087 | } 1088 | // }}} 1089 | 1090 | /* 1091 | * Local variables: 1092 | * tab-width: 4 1093 | * c-basic-offset: 4 1094 | * c-hanging-comment-ender-p: nil 1095 | * End: 1096 | */ 1097 | 1098 | ?> 1099 | -------------------------------------------------------------------------------- /functions/checks/check_php_build.php: -------------------------------------------------------------------------------- 1 | 0) { 48 | $error[] = _('The following required PHP extensions are missing'); 49 | foreach ($missingExt as $missing) { 50 | $error[] = $missing; 51 | } 52 | $error[] = _('Please recompile PHP to include missing extensions.'); 53 | } 54 | /* php version error */ 55 | elseif(phpversion() < "5.4") { 56 | $error[] = _('Unsupported PHP version'); 57 | $error[] = _('From release 1.3.2+, at least PHP version 5.4 is required!'); 58 | $error[] = _("Detected PHP version: ").phpversion(); 59 | } 60 | /* 32-bit systems */ 61 | else { 62 | $error[] = _('From phpIPAM release 1.4 onwards, 64bit system is required!'); 63 | } 64 | 65 | $error[] = ""; 66 | 67 | die(implode("\n", $error)); 68 | } -------------------------------------------------------------------------------- /functions/classes/class.Config.php: -------------------------------------------------------------------------------- 1 | {$name})) 32 | return self::$config->{$name}; 33 | else 34 | return $default_value; 35 | } 36 | } -------------------------------------------------------------------------------- /functions/classes/class.Log.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 45 | $this->mail_settings = $mail_settings; 46 | 47 | # we need phpmailer 48 | if(file_exists(dirname(__FILE__).'/../PHPMailer/PHPMailerAutoload.php')) { 49 | // legacy versions 50 | require_once( dirname(__FILE__).'/../PHPMailer/PHPMailerAutoload.php'); 51 | 52 | # initialize object 53 | $this->Php_mailer = new PHPMailer(true); //localhost by default 54 | } 55 | elseif (file_exists(dirname(__FILE__).'/../PHPMailer/src/Exception.php')) { 56 | require_once( dirname(__FILE__).'/../PHPMailer/src/Exception.php'); 57 | require_once( dirname(__FILE__).'/../PHPMailer/src/PHPMailer.php'); 58 | require_once( dirname(__FILE__).'/../PHPMailer/src/SMTP.php'); 59 | 60 | $this->Php_mailer = new PHPMailer\PHPMailer\PHPMailer(); 61 | } else { 62 | throw new Exception(_('PHPMailer submodule is missing.')); 63 | } 64 | 65 | $this->Php_mailer->CharSet="UTF-8"; //set utf8 66 | $this->Php_mailer->SMTPDebug = 0; //default no debugging 67 | 68 | # localhost or smtp? 69 | if ($this->mail_settings->mtype=="smtp") { $this->set_smtp(); } 70 | } 71 | 72 | /** 73 | * Sets SMTP parameters 74 | * 75 | * @access private 76 | * @return void 77 | */ 78 | private function set_smtp() { 79 | //set smtp 80 | $this->Php_mailer->isSMTP(); 81 | //tls, ssl? 82 | if($this->mail_settings->msecure!='none') { 83 | $this->Php_mailer->SMTPAutoTLS = true; 84 | $this->Php_mailer->SMTPSecure = $this->mail_settings->msecure=='ssl' ? 'ssl' : 'tls'; 85 | } 86 | else { 87 | $this->Php_mailer->SMTPAutoTLS = false; 88 | $this->Php_mailer->SMTPSecure = ''; 89 | } 90 | //server 91 | $this->Php_mailer->Host = $this->mail_settings->mserver; 92 | $this->Php_mailer->Port = $this->mail_settings->mport; 93 | //permit self-signed certs and dont verify certs 94 | $this->Php_mailer->SMTPOptions = array("ssl"=>array("verify_peer"=>false, "verify_peer_name"=>false, "allow_self_signed"=>true)); 95 | // uncomment this to disable AUTOTLS if security is set to none 96 | $this->Php_mailer->SMTPAutoTLS = false; 97 | //set smtp auth 98 | $this->set_smtp_auth(); 99 | } 100 | 101 | /** 102 | * Set SMTP login parameters 103 | * 104 | * @access private 105 | * @return void 106 | */ 107 | private function set_smtp_auth() { 108 | if ($this->mail_settings->mauth == "yes") { 109 | $this->Php_mailer->SMTPAuth = true; 110 | $this->Php_mailer->Username = $this->mail_settings->muser; 111 | $this->Php_mailer->Password = $this->mail_settings->mpass; 112 | } else { 113 | $this->Php_mailer->SMTPAuth = false; 114 | } 115 | } 116 | 117 | /** 118 | * Overrides mail settings in database. For sending test emails. 119 | * 120 | * @access public 121 | * @param mixed $override_settings 122 | * @return void 123 | */ 124 | public function override_settings($override_settings) { 125 | foreach ($override_settings as $k=>$s) { 126 | $this->mail_settings->{$k} = $s; 127 | } 128 | } 129 | 130 | /** 131 | * Resets SMTP debugging 132 | * 133 | * @access public 134 | * @param int $level (default: 2) 135 | * @return void 136 | */ 137 | public function set_debugging ($level = 2) { 138 | $this->Php_mailer->SMTPDebug = $level == 1 ? 1 : 2; 139 | // output 140 | $this->Php_mailer->Debugoutput = 'html'; 141 | } 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | /** 151 | * Generates mail message 152 | * 153 | * @access public 154 | * @param string $body 155 | * @return string 156 | */ 157 | public function generate_message ($body) { 158 | $html = array(); 159 | $html[] = $this->set_header (); //set header 160 | $html[] = $this->set_body_start (); //start body 161 | $html[] = $body; //set body 162 | $html[] = $this->set_footer (); //set footer 163 | $html[] = $this->set_body_end (); //end 164 | # return 165 | return implode("\n", $html); 166 | } 167 | 168 | /** 169 | * Generates plain text mail 170 | * 171 | * @access public 172 | * @param mixed $body 173 | * @return void 174 | */ 175 | public function generate_message_plain ($body) { 176 | $html = array(); 177 | $html[] = $body; //set body 178 | $html[] = $this->set_footer_plain (); //set footer 179 | } 180 | 181 | /** 182 | * set_header function. 183 | * 184 | * @access private 185 | * @return string 186 | */ 187 | private function set_header () { 188 | $html = array(); 189 | $html[] = ""; 190 | $html[] = ""; 191 | $html[] = ""; 192 | $html[] = ""; 193 | $html[] = ""; 194 | # return 195 | return implode("\n", $html); 196 | } 197 | 198 | /** 199 | * Begins message body 200 | * 201 | * @access private 202 | * @return string 203 | */ 204 | private function set_body_start () { 205 | # read config 206 | $config = Config::ValueOf('config'); 207 | 208 | // set width 209 | $logo_width = isset($config['logo_width']) ? $config['logo_width'] : 220; 210 | 211 | $html = array(); 212 | $html[] = ""; 213 | # logo 214 | if(!file_exists( dirname(__FILE__)."/../../css/images/logo/logo.png")) { 215 | $img = ''; // Load built-in image 216 | require( dirname(__FILE__).'/../../app/admin/settings/logo/logo-builtin.php' ); 217 | $html[] = $img; 218 | } 219 | else { 220 | $html[] = "phpipam"; 221 | } 222 | 223 | # return 224 | return implode("\n", $html); 225 | } 226 | 227 | /** 228 | * Sets message body 229 | * 230 | * @access public 231 | * @param mixed $body 232 | * @return void 233 | */ 234 | public function set_body ($body) { 235 | return is_array($body) ? implode("\n", $body) : $body; 236 | } 237 | 238 | /** 239 | * ends message body and html 240 | * 241 | * @access private 242 | * @return string 243 | */ 244 | private function set_body_end () { 245 | return ""; 246 | } 247 | 248 | /** 249 | * Sets footer 250 | * 251 | * @access public 252 | * @return string 253 | */ 254 | public function set_footer () { 255 | $html = array(); 256 | $html[] = "
"; 257 | $html[] = "
"; 258 | $html[] = "      $this->mail_font_style_light This email was automatically generated. You can change your notification settings in account details!
"; 259 | $html[] = "      $this->mail_font_style_href ".$this->settings->siteURL."
"; 260 | $html[] = "
"; 261 | 262 | # return 263 | return implode("\n", $html); 264 | } 265 | 266 | /** 267 | * Sets plain footer 268 | * 269 | * @access public 270 | * @return string 271 | */ 272 | public function set_footer_plain () { 273 | return "\r\n------------------------------\r\n".$this->settings->siteAdminName." (".$this->settings->siteAdminMail.") :: ".$this->settings->siteURL; 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /functions/classes/class.PDO.php: -------------------------------------------------------------------------------- 1 | username = $username; 111 | if (isset($password)) $this->password = $password; 112 | if (isset($charset)) $this->charset = $charset; 113 | # ssl 114 | if ($ssl) { 115 | $this->ssl = $ssl; 116 | } 117 | } 118 | 119 | /** 120 | * convert a date object/string ready for use in sql 121 | * 122 | * @access public 123 | * @static 124 | * @param mixed $date (default: null) 125 | * @return void 126 | */ 127 | public static function toDate($date = null) { 128 | if (is_int($date)) { 129 | return date('Y-m-d H:i:s', $date); 130 | } else if (is_string($date)) { 131 | return date('Y-m-d H:i:s', strtotime($date)); 132 | } else { 133 | return date('Y-m-d H:i:s'); 134 | } 135 | } 136 | 137 | /** 138 | * Connect to the database 139 | * Call whenever a connection is needed to be made 140 | * 141 | * @access public 142 | * @return void 143 | */ 144 | public function connect() { 145 | $dsn = $this->makeDsn(); 146 | 147 | try { 148 | # ssl? 149 | if ($this->ssl) { 150 | $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->ssl); 151 | } 152 | else { 153 | $this->pdo = new \PDO($dsn, $this->username, $this->password); 154 | } 155 | 156 | $this->setErrMode(\PDO::ERRMODE_EXCEPTION); 157 | 158 | } catch (\PDOException $e) { 159 | throw new Exception ("Could not connect to database! ".$e->getMessage()); 160 | } 161 | 162 | @$this->pdo->query('SET NAMES \'' . $this->charset . '\';'); 163 | } 164 | 165 | /** 166 | * Set PDO error mode 167 | * @param mixed $mode 168 | */ 169 | public function setErrMode($mode = \PDO::ERRMODE_EXCEPTION) { 170 | $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, $mode); 171 | } 172 | 173 | /** 174 | * makeDsn function. 175 | * 176 | * @access protected 177 | * @return void 178 | */ 179 | protected function makeDsn() { 180 | return ':charset=' . $this->charset; 181 | } 182 | 183 | /** 184 | * resets conection. 185 | * 186 | * @access public 187 | * @return void 188 | */ 189 | public function resetConn() { 190 | unset($this->pdo); 191 | $this->install = false; 192 | } 193 | 194 | /** 195 | * logs queries to file 196 | * 197 | * @access private 198 | * @param mixed $query 199 | * @param array $values 200 | * @return void 201 | */ 202 | private function log_query ($query, $values = false) { 203 | if($this->debug) { 204 | 205 | $myFile = "/tmp/queries.txt"; 206 | $fh = fopen($myFile, 'a') or die("can't open file"); 207 | // query 208 | fwrite($fh, $query->queryString); 209 | // values 210 | if(is_array($values)) { 211 | fwrite($fh, " Params: ".implode(", ", $values)); 212 | } 213 | // break 214 | fwrite($fh, "\n"); 215 | fclose($fh); 216 | } 217 | } 218 | 219 | /** 220 | * Remove outer quotes from a string 221 | * 222 | * @access public 223 | * @static 224 | * @param mixed $str 225 | * @return void 226 | */ 227 | public static function unquote_outer($str) { 228 | $len = strlen($str); 229 | 230 | if ($len>1) { 231 | if ($str[0] == "'" && $str[$len-1] == "'") { 232 | return substr($str, 1, -1); 233 | } else if ($str[0] == "'") { 234 | return substr($str, 1); 235 | } else if ($str[$len-1] == "'") { 236 | return substr($str, 0, -1); 237 | } 238 | } else if ($len>0) { 239 | if ($str[0] == "'") { 240 | return ''; 241 | } 242 | } 243 | 244 | return $str; 245 | } 246 | 247 | /** 248 | * Are we currently connected to the database 249 | * 250 | * @access public 251 | * @return void 252 | */ 253 | public function isConnected() { 254 | return ($this->pdo !== null); 255 | } 256 | 257 | /** 258 | * Returns last insert ID 259 | * 260 | * @access public 261 | * @return void 262 | */ 263 | public function lastInsertId() { 264 | return $this->pdo->lastInsertId(); 265 | } 266 | 267 | /** 268 | * Run a statement on the database 269 | * Note: no objects are fetched 270 | * 271 | * @access public 272 | * @param mixed $query 273 | * @param array $values (default: array()) 274 | * @param integer|null &$rowCount (default: null) 275 | * @return void 276 | */ 277 | public function runQuery($query, $values = array(), &$rowCount = null) { 278 | if (!$this->isConnected()) $this->connect(); 279 | 280 | $result = null; 281 | 282 | $statement = $this->pdo->prepare($query); 283 | 284 | //debuq 285 | $this->log_query($statement, $values); 286 | 287 | if (is_object($statement)) { 288 | $result = $statement->execute((array)$values); //this array cast allows single values to be used as the parameter 289 | $rowCount = $statement->rowCount(); 290 | } 291 | return $result; 292 | } 293 | 294 | /** 295 | * Allow a value to be escaped, ready for insertion as a mysql parameter 296 | * Note: for usage as a value (rather than prepared statements), you MUST manually quote around. 297 | * 298 | * @access public 299 | * @param mixed $str 300 | * @return void 301 | */ 302 | public function escape($str) { 303 | $str = (string) $str; 304 | if (strlen($str) == 0) return ""; 305 | 306 | if (!$this->isConnected()) $this->connect(); 307 | 308 | // SQL Injection - strip backquote character 309 | $str = str_replace('`', '', $str); 310 | return $this->unquote_outer($this->pdo->quote($str)); 311 | } 312 | 313 | /** 314 | * Get a quick number of objects in a table 315 | * 316 | * @access public 317 | * @param mixed $tableName 318 | * @return void 319 | */ 320 | public function numObjects($tableName) { 321 | if (!$this->isConnected()) $this->connect(); 322 | 323 | $tableName = $this->escape($tableName); 324 | $statement = $this->pdo->prepare('SELECT COUNT(*) as `num` FROM `'.$tableName.'`;'); 325 | 326 | //debuq 327 | $this->log_query ($statement); 328 | $statement->execute(); 329 | 330 | return $statement->fetchColumn(); 331 | } 332 | 333 | /** 334 | * Get a quick number of objects in a table for filtered field 335 | * 336 | * @access public 337 | * @param mixed $tableName 338 | * @param mixed $method 339 | * @param boolean $like (default: false) 340 | * @param mixed $value 341 | * @return void 342 | */ 343 | public function numObjectsFilter($tableName, $method, $value, $like = false) { 344 | if (!$this->isConnected()) $this->connect(); 345 | 346 | $like === true ? $operator = "LIKE" : $operator = "="; 347 | 348 | $tableName = $this->escape($tableName); 349 | $statement = $this->pdo->prepare('SELECT COUNT(*) as `num` FROM `'.$tableName.'` where `'.$method.'` '.$operator.' ?;'); 350 | 351 | //debuq 352 | $this->log_query ($statement, (array) $value); 353 | $statement->execute(array($value)); 354 | 355 | return $statement->fetchColumn(); 356 | } 357 | 358 | /** 359 | * Update an object in a table with values given 360 | * 361 | * Note: the id of the object is assumed to be in. 362 | * 363 | * @access public 364 | * @param mixed $tableName 365 | * @param mixed $obj 366 | * @param string $primarykey (default: 'id') 367 | * @param mixed $primarykey2 (default: null) 368 | * @return void 369 | */ 370 | public function updateObject($tableName, $obj, $primarykey = 'id', $primarykey2 = null) { 371 | if (!$this->isConnected()) $this->connect(); 372 | 373 | $obj = (array)$obj; 374 | 375 | //we cannot update an object without an id specified so quit 376 | if (!isset($obj[$primarykey])) { 377 | throw new Exception('Missing primary key'); 378 | return false; 379 | } 380 | 381 | $tableName = $this->escape($tableName); 382 | 383 | //get the objects id from the provided object and knock it off from the object so we dont try to update it 384 | $objId[] = $obj[$primarykey]; 385 | unset($obj[$primarykey]); 386 | 387 | //secondary primary key? 388 | if(!is_null($primarykey2)) { 389 | $objId[] = $obj[$primarykey2]; 390 | unset($obj[$primarykey2]); 391 | } 392 | 393 | //TODO: validate given object parameters with that of the table (this validates parameters names) 394 | 395 | //formulate an update statement based on the object parameters 396 | $objParams = array_keys($obj); 397 | 398 | $preparedParamArr = array(); 399 | foreach ($objParams as $objParam) { 400 | $preparedParamArr[] = '`' . $this->escape($objParam) . '`=?'; 401 | } 402 | 403 | // exit on no parameters 404 | if(sizeof($preparedParamArr)==0) { 405 | throw new Exception('No values to update'); 406 | return false; 407 | } 408 | 409 | $preparedParamStr = implode(',', $preparedParamArr); 410 | 411 | //primary key 2? 412 | if(!is_null($primarykey2)) 413 | $statement = $this->pdo->prepare('UPDATE `' . $tableName . '` SET ' . $preparedParamStr . ' WHERE `' . $primarykey . '`=? AND `' . $primarykey2 . '`=?;'); 414 | else 415 | $statement = $this->pdo->prepare('UPDATE `' . $tableName . '` SET ' . $preparedParamStr . ' WHERE `' . $primarykey . '`=?;'); 416 | 417 | //merge the parameters and values 418 | $paramValues = array_merge(array_values($obj), $objId); 419 | 420 | //debuq 421 | $this->log_query ($statement, $paramValues); 422 | //run the update on the object 423 | return $statement->execute($paramValues); 424 | } 425 | 426 | /** 427 | * Update multiple objects at once. 428 | * 429 | * @access public 430 | * @param string $tableName 431 | * @param array $ids 432 | * @param array $values 433 | * @return void 434 | */ 435 | public function updateMultipleObjects($tableName, $ids, $values) { 436 | $tableName = $this->escape($tableName); 437 | //set ids 438 | $num = count($ids); 439 | $idParts = array_fill(0, $num, '`id`=?'); 440 | //set values 441 | $objParams = array_keys($values); 442 | $preparedParamArr = array(); 443 | foreach ($objParams as $objParam) { 444 | $preparedParamArr[] = '`' . $this->escape($objParam) . '`=?'; 445 | } 446 | //set values 447 | $all_values = array_merge(array_values($values),$ids); 448 | //execute 449 | return $this->runQuery('UPDATE `'.$tableName.'` SET '.implode(',', $preparedParamArr).' WHERE '.implode(' OR ', $idParts), $all_values); 450 | } 451 | 452 | /** 453 | * Insert an object into a table 454 | * Note: an id field is ignored if specified. 455 | * 456 | * @access public 457 | * @param string $tableName 458 | * @param object|array $obj 459 | * @param bool $raw (default: false) 460 | * @param bool $replace (default: false) 461 | * @param bool $ignoreId (default: true) 462 | * @return void 463 | */ 464 | public function insertObject($tableName, $obj, $raw = false, $replace = false, $ignoreId = true) { 465 | if (!$this->isConnected()) $this->connect(); 466 | 467 | $obj = (array)$obj; 468 | 469 | $tableName = $this->escape($tableName); 470 | 471 | if (!$raw && array_key_exists('id', $obj) && $ignoreId) { 472 | unset($obj['id']); 473 | } 474 | 475 | if (count($obj)<1) { 476 | return true; 477 | } 478 | 479 | //formulate an update statement based on the object parameters 480 | $objValues = array_values($obj); 481 | 482 | $preparedParamsArr = array(); 483 | foreach ($obj as $key => $value) { 484 | $preparedParamsArr[] = '`' . $this->escape($key) . '`'; 485 | } 486 | 487 | $preparedParamsStr = implode(', ', $preparedParamsArr); 488 | $preparedValuesStr = implode(', ', array_fill(0, count($objValues), '?')); 489 | 490 | if ($replace) { 491 | $statement = $this->pdo->prepare('REPLACE INTO `' . $tableName . '` (' . $preparedParamsStr . ') VALUES (' . $preparedValuesStr . ');'); 492 | } else { 493 | $statement = $this->pdo->prepare('INSERT INTO `' . $tableName . '` (' . $preparedParamsStr . ') VALUES (' . $preparedValuesStr . ');'); 494 | } 495 | 496 | //run the update on the object 497 | if (!$statement->execute($objValues)) { 498 | $errObj = $statement->errorInfo(); 499 | 500 | //return false; 501 | throw new Exception($errObj[2]); 502 | } 503 | 504 | return $this->pdo->lastInsertId(); 505 | } 506 | 507 | 508 | /** 509 | * Check if an object exists. 510 | * 511 | * @access public 512 | * @param string $tableName 513 | * @param string $query (default: null) 514 | * @param array $values (default: array()) 515 | * @param mixed $id (default: null) 516 | * @return void 517 | */ 518 | public function objectExists($tableName, $query = null, $values = array(), $id = null) { 519 | return is_object($this->getObject($tableName, $id)); 520 | } 521 | 522 | /** 523 | * Get a filtered list of objects from the database. 524 | * 525 | * @access public 526 | * @param string $tableName 527 | * @param string $sortField (default: 'id') 528 | * @param bool $sortAsc (default: true) 529 | * @param mixed $numRecords (default: null) 530 | * @param int $offset (default: 0) 531 | * @param string $class (default: 'stdClass') 532 | * @return void 533 | */ 534 | public function getObjects($tableName, $sortField = 'id', $sortAsc = true, $numRecords = null, $offset = 0, $class = 'stdClass') { 535 | if (!$this->isConnected()) $this->connect(); 536 | 537 | $sortStr = ''; 538 | if (!$sortAsc) { 539 | $sortStr = 'DESC'; 540 | } 541 | 542 | // change sort fields for vlans and vrfs. ugly :/ 543 | if ($tableName=='vlans' && $sortField=='id') { $sortField = "vlanId"; } 544 | if ($tableName=='vrf' && $sortField=='id') { $sortField = "vrfId"; } 545 | 546 | //we should escape all of the params that we need to 547 | $tableName = $this->escape($tableName); 548 | $sortField = $this->escape($sortField); 549 | 550 | if ($numRecords === null) { 551 | //get all (no limit) 552 | $statement = $this->pdo->query('SELECT * FROM `'.$tableName.'` ORDER BY `'.$sortField.'` '.$sortStr.';'); 553 | } else { 554 | //get a limited range of objects 555 | $statement = $this->pdo->query('SELECT * FROM `'.$tableName.'` ORDER BY `'.$sortField.'` '.$sortStr.' LIMIT '.$numRecords.' OFFSET '.$offset.';'); 556 | } 557 | 558 | $results = array(); 559 | 560 | if (is_object($statement)) { 561 | $results = $statement->fetchAll($class == 'stdClass' ? PDO::FETCH_CLASS : PDO::FETCH_NUM); 562 | } 563 | 564 | return $results; 565 | } 566 | 567 | 568 | /** 569 | * use this function to conserve memory and read rows one by one rather than reading all of them 570 | * 571 | * @access public 572 | * @param mixed $query (default: null) 573 | * @param array $values (default: array()) 574 | * @param mixed $callback (default: null) 575 | * @return void 576 | */ 577 | public function getObjectsQueryIncremental($query = null, $values = array(), $callback = null) { 578 | if (!$this->isConnected()) $this->connect(); 579 | 580 | $statement = $this->pdo->prepare($query); 581 | 582 | //debuq 583 | $this->log_query ($statement, $values); 584 | $statement->execute((array)$values); 585 | 586 | if (is_object($statement)) { 587 | if ($callback) { 588 | while ($newObj = $statement->fetchObject('stdClass')) { 589 | if ($callback($newObj)===false) { 590 | return false; 591 | } 592 | } 593 | } 594 | } 595 | 596 | return true; 597 | } 598 | 599 | 600 | /** 601 | * Get all objects matching values 602 | * 603 | * @access public 604 | * @param mixed $query (default: null) 605 | * @param array $values (default: array()) 606 | * @param string $class (default: 'stdClass') 607 | * @return void 608 | */ 609 | public function getObjectsQuery($query = null, $values = array(), $class = 'stdClass') { 610 | if (!$this->isConnected()) $this->connect(); 611 | 612 | $statement = $this->pdo->prepare($query); 613 | 614 | //debug 615 | $this->log_query ($statement, $values); 616 | $statement->execute((array)$values); 617 | 618 | $results = array(); 619 | 620 | if (is_object($statement)) { 621 | $results = $statement->fetchAll($class == 'stdClass' ? PDO::FETCH_CLASS : PDO::FETCH_NUM); 622 | } 623 | 624 | return $results; 625 | } 626 | 627 | /** 628 | * Get all objects groped by $groupField, array of (id,count(*)) pairs 629 | * 630 | * @param string $tableName 631 | * @param string $groupField 632 | * @return array 633 | */ 634 | public function getGroupBy($tableName, $groupField = 'id') { 635 | if (!$this->isConnected()) $this->connect(); 636 | 637 | $statement = $this->pdo->prepare("SELECT `$groupField`,COUNT(*) FROM `$tableName` GROUP BY `$groupField`"); 638 | 639 | //debug 640 | $this->log_query ($statement, array()); 641 | $statement->execute(); 642 | 643 | $results = array(); 644 | 645 | if (is_object($statement)) { 646 | $results = $statement->fetchAll(PDO::FETCH_KEY_PAIR); 647 | } 648 | 649 | return $results; 650 | } 651 | 652 | /** 653 | * Get a single object from the database 654 | * 655 | * @access public 656 | * @param mixed $tableName 657 | * @param mixed $id (default: null) 658 | * @param string $class (default: 'stdClass') 659 | * @return void 660 | */ 661 | public function getObject($tableName, $id = null, $class = 'stdClass') { 662 | if (!$this->isConnected()) $this->connect(); 663 | $id = intval($id); 664 | 665 | //has a custom query been provided? 666 | $tableName = $this->escape($tableName); 667 | 668 | //prepare a statement to get a single object from the database 669 | if ($id !== null) { 670 | $statement = $this->pdo->prepare('SELECT * FROM `'.$tableName.'` WHERE `id`=? LIMIT 1;'); 671 | $statement->bindParam(1, $id, \PDO::PARAM_INT); 672 | } else { 673 | $statement = $this->pdo->prepare('SELECT * FROM `'.$tableName.'` LIMIT 1;'); 674 | } 675 | 676 | //debuq 677 | $this->log_query ($statement, array($id)); 678 | $statement->execute(); 679 | 680 | //we can then extract the single object (if we have a result) 681 | $resultObj = $statement->fetchObject($class); 682 | 683 | if ($resultObj === false) { 684 | return null; 685 | } else { 686 | return $resultObj; 687 | } 688 | } 689 | 690 | /** 691 | * Fetches single object from provided query 692 | * 693 | * @access public 694 | * @param mixed $query (default: null) 695 | * @param array $values (default: array()) 696 | * @param string $class (default: 'stdClass') 697 | * @return void 698 | */ 699 | public function getObjectQuery($query = null, $values = array(), $class = 'stdClass') { 700 | if (!$this->isConnected()) $this->connect(); 701 | 702 | $statement = $this->pdo->prepare($query); 703 | //debuq 704 | $this->log_query ($statement, $values); 705 | $statement->execute((array)$values); 706 | 707 | $resultObj = $statement->fetchObject($class); 708 | 709 | if ($resultObj === false) { 710 | return null; 711 | } else { 712 | return $resultObj; 713 | } 714 | } 715 | 716 | /** 717 | * Get single value 718 | * 719 | * @access public 720 | * @param mixed $query (default: null) 721 | * @param array $values (default: array()) 722 | * @param string $class (default: 'stdClass') 723 | * @return void 724 | */ 725 | public function getValueQuery($query = null, $values = array(), $class = 'stdClass') { 726 | $obj = $this->getObjectQuery($query, $values, $class); 727 | 728 | if (is_object($obj)) { 729 | $obj = (array)$obj; 730 | return reset($obj); 731 | } else { 732 | return null; 733 | } 734 | } 735 | 736 | /** 737 | * Escape $result_fields parameter 738 | * 739 | * @access public 740 | * @param string|array $result_fields 741 | * @return string 742 | */ 743 | public function escape_result_fields($result_fields) { 744 | if (empty($result_fields)) return "*"; 745 | 746 | if (is_array($result_fields)) { 747 | foreach ($result_fields as $i => $f) $result_fields[$i] = "`$f`"; 748 | $result_fields = implode(',', $result_fields); 749 | } 750 | return $result_fields; 751 | } 752 | 753 | /** 754 | * Searches for object in database 755 | * 756 | * @access public 757 | * @param mixed $table 758 | * @param mixed $field 759 | * @param mixed $value 760 | * @param string $sortField (default: 'id') 761 | * @param bool $sortAsc (default: true) 762 | * @param bool $like (default: false) 763 | * @param bool $negate (default: false) 764 | * @param string|array $result_fields (default: "*") 765 | * @return void 766 | */ 767 | public function findObjects($table, $field, $value, $sortField = 'id', $sortAsc = true, $like = false, $negate = false, $result_fields = "*") { 768 | $table = $this->escape($table); 769 | $field = $this->escape($field); 770 | $sortField = $this->escape($sortField); 771 | $like === true ? $operator = "LIKE" : $operator = "="; 772 | $negate === true ? $negate_operator = "NOT " : $negate_operator = ""; 773 | 774 | $result_fields = $this->escape_result_fields($result_fields); 775 | 776 | // change sort fields for vlans and vrfs. ugly :/ 777 | if ($table=='vlans' && $sortField=='id') { $sortField = "vlanId"; } 778 | if ($table=='vrf' && $sortField=='id') { $sortField = "vrfId"; } 779 | 780 | // subnets 781 | if ($table=='subnets' && $sortField=='subnet') { 782 | return $this->getObjectsQuery('SELECT '.$result_fields.' FROM `' . $table . '` WHERE `'. $field .'`'.$negate_operator. $operator .'? ORDER BY LPAD(`subnet`,39,0) ' . ($sortAsc ? '' : 'DESC') . ';', array($value)); 783 | } else { 784 | return $this->getObjectsQuery('SELECT '.$result_fields.' FROM `' . $table . '` WHERE `'. $field .'`'.$negate_operator. $operator .'? ORDER BY `'.$sortField.'` ' . ($sortAsc ? '' : 'DESC') . ';', array($value)); 785 | } 786 | } 787 | 788 | /** 789 | * Searches for single object. 790 | * 791 | * @access public 792 | * @param mixed $table 793 | * @param mixed $field 794 | * @param mixed $value 795 | * @return void 796 | */ 797 | public function findObject($table, $field, $value) { 798 | $table = $this->escape($table); 799 | $field = $this->escape($field); 800 | 801 | return $this->getObjectQuery('SELECT * FROM `' . $table . '` WHERE `' . $field . '` = ? LIMIT 1;', array($value)); 802 | } 803 | 804 | /** 805 | * Get list of items. 806 | * 807 | * @access public 808 | * @param mixed $query (default: null) 809 | * @param array $values (default: array()) 810 | * @param string $class (default: 'stdClass') 811 | * @return void 812 | */ 813 | public function getList($query = null, $values = array(), $class = 'stdClass') { 814 | $objs = $this->getObjectsQuery($query, $values, $class); 815 | 816 | $list = array(); 817 | 818 | if (!is_array($objs)) 819 | return $list; 820 | 821 | foreach ($objs as $obj) { 822 | $columns = array_values((array)$obj); 823 | $list[] = $columns[0]; 824 | } 825 | 826 | return $list; 827 | } 828 | 829 | /** 830 | * Delete an object from the database 831 | * 832 | * @param {string} table name 833 | * @param {int} object id 834 | * @return {boolean} success 835 | */ 836 | public function deleteObject($tableName, $id) { 837 | $tableName = $this->escape($tableName); 838 | 839 | return $this->runQuery('DELETE FROM `'.$tableName.'` WHERE `id`=?;', array($id)); 840 | } 841 | 842 | /** 843 | * Delete a list of objects from the database 844 | * 845 | * @param {string} table name 846 | * @param {array} list of ids 847 | * @return {boolean} success 848 | */ 849 | public function deleteObjects($tableName, $ids) { 850 | $tableName = $this->escape($tableName); 851 | $num = count($ids); 852 | $idParts = array_fill(0, $num, '`id`=?'); 853 | 854 | return $this->runQuery('DELETE FROM `'.$tableName.'` WHERE ' . implode(' OR ', $idParts), $ids); 855 | } 856 | 857 | /** 858 | * Delete a list of objects from the database based on identifier 859 | * 860 | * @method deleteObjects 861 | * @param string $tableName 862 | * @param string $identifier 863 | * @param mixed $ids 864 | * @return bool 865 | */ 866 | public function deleteObjectsByIdentifier($tableName, $identifier = "id", $id = 0) { 867 | $tableName = $this->escape($tableName); 868 | $identifier = $this->escape($identifier); 869 | 870 | return $this->runQuery('DELETE FROM `'.$tableName.'` WHERE `'.$identifier.'` = ?', $id); 871 | } 872 | 873 | /** 874 | * Delete specified row 875 | * 876 | * @access public 877 | * @param {string} $tableName 878 | * @param {string $field 879 | * @param {string $value 880 | * @return void 881 | */ 882 | public function deleteRow($tableName, $field, $value, $field2=null, $value2 = null) { 883 | $tableName = $this->escape($tableName); 884 | $field = $this->escape($field); 885 | $field2 = $this->escape($field2); 886 | 887 | //multiple 888 | if(!empty($field2)) 889 | return $this->runQuery('DELETE FROM `'.$tableName.'` WHERE `'.$field.'`=? and `'.$field2.'`=?;', array($value, $value2)); 890 | else 891 | return $this->runQuery('DELETE FROM `'.$tableName.'` WHERE `'.$field.'`=?;', array($value)); 892 | } 893 | 894 | /** 895 | * truncate specified table 896 | * 897 | * @access public 898 | * @param {string} $tableName 899 | * @return void 900 | */ 901 | public function emptyTable($tableName) { 902 | //escape talbe name 903 | $tableName = $this->escape($tableName); 904 | //execute 905 | return $this->runQuery('TRUNCATE TABLE `'.$tableName.'`;'); 906 | } 907 | 908 | /** 909 | * Begin SQL Transaction 910 | * 911 | * @access public 912 | * @return bool 913 | */ 914 | public function beginTransaction() { 915 | return $this->pdo->beginTransaction(); 916 | } 917 | 918 | /** 919 | * Commit SQL Transaction 920 | * 921 | * @access public 922 | * @return bool 923 | */ 924 | public function commit() { 925 | return $this->pdo->commit(); 926 | } 927 | 928 | /** 929 | * Commit SQL Transaction 930 | * 931 | * @access public 932 | * @return bool 933 | */ 934 | public function rollBack() { 935 | return $this->pdo->rollBack(); 936 | } 937 | } 938 | 939 | 940 | /** 941 | * 942 | * PDO class wrapper 943 | * Database class 944 | * 945 | */ 946 | class Database_PDO extends DB { 947 | 948 | 949 | /** 950 | * SSL options for db connection 951 | * 952 | * (default value: array ()) 953 | * 954 | * @var array 955 | * @access protected 956 | */ 957 | protected $pdo_ssl_opts = array (); 958 | 959 | /** 960 | * flag if installation is happenig! 961 | * 962 | * (default value: false) 963 | * 964 | * @var bool 965 | * @access public 966 | */ 967 | public $install = false; 968 | 969 | /** 970 | * Debugging flag 971 | * 972 | * (default value: false) 973 | * 974 | * @var bool 975 | * @access protected 976 | */ 977 | protected $debug = false; 978 | 979 | 980 | 981 | 982 | 983 | 984 | /** 985 | * __construct function. 986 | * 987 | * @access public 988 | * @param mixed $host (default: null) 989 | * @param mixed $port (default: null) 990 | * @param mixed $dbname (default: null) 991 | * @param mixed $username (default: null) 992 | * @param mixed $password (default: null) 993 | * @param mixed $charset (default: null) 994 | */ 995 | public function __construct($username=null, $password=null, $host=null, $port=null, $dbname=null, $charset=null) { 996 | # set parameters 997 | $this->set_db_params (); 998 | # rewrite user/pass if requested - for installation 999 | $username==null ? : $this->username = $username; 1000 | $password==null ? : $this->password = $password; 1001 | $host==null ? : $this->host = $host; 1002 | $port==null ? : $this->port = $port; 1003 | $dbname==null ? : $this->dbname = $dbname; 1004 | 1005 | # construct 1006 | parent::__construct($this->username, $this->password, $this->charset, $this->ssl); 1007 | } 1008 | 1009 | 1010 | /** 1011 | * get database parameters from config.php 1012 | * 1013 | * @access private 1014 | * @return void 1015 | */ 1016 | private function set_db_params () { 1017 | # use config file 1018 | $config = Config::ValueOf('config'); 1019 | $db = $config['db']; 1020 | 1021 | # set 1022 | $this->host = $db['host']; 1023 | $this->port = $db['port']; 1024 | $this->username = $db['user']; 1025 | $this->password = $db['pass']; 1026 | $this->dbname = $db['name']; 1027 | 1028 | $this->ssl = false; 1029 | if (@$db['ssl']===true) { 1030 | 1031 | $this->pdo_ssl_opts = array ( 1032 | 'ssl_key' => PDO::MYSQL_ATTR_SSL_KEY, 1033 | 'ssl_cert' => PDO::MYSQL_ATTR_SSL_CERT, 1034 | 'ssl_ca' => PDO::MYSQL_ATTR_SSL_CA, 1035 | 'ssl_cipher' => PDO::MYSQL_ATTR_SSL_CIPHER, 1036 | 'ssl_capath' => PDO::MYSQL_ATTR_SSL_CAPATH 1037 | ); 1038 | 1039 | $this->ssl = array(); 1040 | 1041 | if ($db['ssl_verify']===false) { 1042 | $this->ssl[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false; 1043 | } 1044 | 1045 | foreach ($this->pdo_ssl_opts as $key => $pdoopt) { 1046 | if ($db[$key]) { 1047 | $this->ssl[$pdoopt] = $db[$key]; 1048 | } 1049 | } 1050 | 1051 | } 1052 | 1053 | } 1054 | 1055 | /** 1056 | * connect function. 1057 | * 1058 | * @access public 1059 | * @return void 1060 | */ 1061 | public function connect() { 1062 | parent::connect(); 1063 | //@$this->pdo->query('SET NAMES \'' . $this->charset . '\';'); 1064 | } 1065 | 1066 | /** 1067 | * makeDsn function 1068 | * 1069 | * @access protected 1070 | * @return void 1071 | */ 1072 | protected function makeDsn() { 1073 | # for installation 1074 | if($this->install) { return 'mysql:host=' . $this->host . ';port=' . $this->port . ';charset=' . $this->charset; } 1075 | else { return 'mysql:host=' . $this->host . ';port=' . $this->port . ';dbname=' . $this->dbname . ';charset=' . $this->charset; } 1076 | } 1077 | 1078 | /** 1079 | * more generic static useful methods 1080 | * 1081 | * @access public 1082 | * @return void 1083 | */ 1084 | public function getColumnInfo() { 1085 | $columns = $this->getObjectsQuery(" 1086 | SELECT `table_name`, `column_name`, `column_default`, `is_nullable`, `data_type`,`column_key`, `extra` 1087 | FROM `columns` 1088 | WHERE `table_schema`='" . $this->dbname . "'; 1089 | "); 1090 | 1091 | $columnsByTable = array(); 1092 | 1093 | if (!is_array($columns)) 1094 | return $columnsByTable; 1095 | 1096 | foreach ($columns as $column) { 1097 | if (!isset($columnsByTable[$column->table_name])) { 1098 | $columnsByTable[$column->table_name] = array(); 1099 | } 1100 | 1101 | $columnsByTable[$column->table_name][$column->column_name] = $column; 1102 | } 1103 | 1104 | return $columnsByTable; 1105 | } 1106 | 1107 | /** 1108 | * Returns field info. 1109 | * 1110 | * @access public 1111 | * @param bool $tableName (default: false) 1112 | * @param bool $field (default: false) 1113 | * @return void|object 1114 | */ 1115 | public function getFieldInfo ($tableName = false, $field = false) { 1116 | //escape 1117 | $tableName = $this->escape($tableName); 1118 | $field = $this->escape($field); 1119 | // fetch and return 1120 | return $this->getObjectQuery("SHOW FIELDS FROM `$tableName` where Field = ?", array($field)); 1121 | 1122 | } 1123 | 1124 | /** 1125 | * getForeignKeyInfo function. 1126 | * 1127 | * @access public 1128 | * @return void 1129 | */ 1130 | public function getForeignKeyInfo() { 1131 | $foreignLinks = $this->getObjectsQuery(" 1132 | SELECT i.`table_name`, k.`column_name`, i.`constraint_type`, i.`constraint_name`, k.`referenced_table_name`, k.`referenced_column_name` 1133 | FROM `table_constraints` i 1134 | LEFT JOIN `key_column_usage` k ON i.`constraint_name` = k.`constraint_name` 1135 | WHERE i.`constraint_type` = 'FOREIGN KEY' AND i.`table_schema`='" . $this->dbname . "'; 1136 | "); 1137 | 1138 | $foreignLinksByTable = array(); 1139 | $foreignLinksByRefTable = array(); 1140 | 1141 | if (!is_array($foreignLinks)) 1142 | return array($foreignLinksByTable, $foreignLinksByRefTable); 1143 | 1144 | foreach ($foreignLinks as $foreignLink) { 1145 | if (!isset($foreignLinksByTable[$foreignLink->table_name])) { 1146 | $foreignLinksByTable[$foreignLink->table_name] = array(); 1147 | } 1148 | 1149 | if (!isset($foreignLinksByRefTable[$foreignLink->referenced_table_name])) { 1150 | $foreignLinksByRefTable[$foreignLink->referenced_table_name] = array(); 1151 | } 1152 | 1153 | $foreignLinksByTable[$foreignLink->table_name][$foreignLink->column_name] = $foreignLink; 1154 | $foreignLinksByRefTable[$foreignLink->referenced_table_name][$foreignLink->table_name] = $foreignLink; 1155 | } 1156 | 1157 | return array($foreignLinksByTable, $foreignLinksByRefTable); 1158 | } 1159 | } 1160 | -------------------------------------------------------------------------------- /functions/classes/class.Result.php: -------------------------------------------------------------------------------- 1 | set_error_codes (); 71 | } 72 | 73 | /** 74 | * Sets error code object 75 | * 76 | * http://www.restapitutorial.com/httpstatuscodes.html 77 | * 78 | * @access private 79 | * @return void 80 | */ 81 | private function set_error_codes () { 82 | // OK 83 | $this->errors[200] = _("OK"); 84 | $this->errors[201] = _("Created"); 85 | $this->errors[202] = _("Accepted"); 86 | $this->errors[204] = _("No Content"); 87 | // Client errors 88 | $this->errors[400] = _("Bad Request"); 89 | $this->errors[401] = _("Unauthorized"); 90 | $this->errors[403] = _("Forbidden"); 91 | $this->errors[404] = _("Not Found"); 92 | $this->errors[405] = _("Method Not Allowed"); 93 | $this->errors[409] = _("Conflict"); 94 | $this->errors[415] = _("Unsupported Media Type"); 95 | // Server errors 96 | $this->errors[500] = _("Internal Server Error"); 97 | $this->errors[501] = _("Not Implemented"); 98 | $this->errors[503] = _("Service Unavailable"); 99 | $this->errors[505] = _("HTTP Version Not Supported"); 100 | //$this->errors[511] = _("Network Authentication Required"; 101 | } 102 | 103 | /** 104 | * Show result 105 | * 106 | * @access public 107 | * @param string $class (default: "muted") result class - danger, success, warning, info 108 | * @param string|array|object $text (default: "No value provided") text to display 109 | * @param bool $die (default: false) controls stop of php execution 110 | * @param bool $popup (default: false) print result as popup 111 | * @param bool $inline (default: false) return, not print 112 | * @param bool $popup2 (default: false) close for JS for popup2 113 | * @param bool $reload (default: false) reload 114 | * @return void 115 | */ 116 | public function show($class="muted", $text="No value provided", $die=false, $popup=false, $inline = false, $popup2 = false, $reload = false) { 117 | 118 | # set die 119 | $this->die = $die; 120 | 121 | # API - throw exception 122 | if($this->exit_method == "exception") { 123 | # ok, just return success 124 | if ($class=="success") { return true; } 125 | else { return $this->throw_exception (500, $text); } 126 | } 127 | else { 128 | # cli or GUI 129 | if (php_sapi_name()=="cli") { print $this->show_cli_message ($text); } 130 | else { 131 | # return or print 132 | if ($inline) { return $this->show_message ($class, $text, $popup, $popup2, $reload); } 133 | else { print $this->show_message ($class, $text, $popup, $popup2, $reload); } 134 | } 135 | 136 | # die 137 | if($this->die===true) {die(); } 138 | } 139 | } 140 | 141 | /** 142 | * Alias for show method for backwards compatibility 143 | * 144 | * @access public 145 | * @param string|array|object $text (default: "No value provided") 146 | * @param bool $die (default: false) 147 | * @return void 148 | */ 149 | public function show_cli ($text="No value provided", $die=false) { 150 | $this->show(false, $text, $die, false, false, false); 151 | } 152 | 153 | /** 154 | * Shows result for cli functions 155 | * 156 | * @access public 157 | * @param string $text (default: "No value provided") 158 | * @return void 159 | */ 160 | public function show_cli_message ($text="No value provided") { 161 | // array - join 162 | if (is_array($text) && sizeof($text)>0) { 163 | // 1 element 164 | if(sizeof( $text )==1) { 165 | $text = $text[0]; 166 | } 167 | // multiple - format 168 | else { 169 | foreach( $text as $l ) { $out[] = "\t* $l"; } 170 | // join 171 | $text = implode("\n", $out); 172 | } 173 | } 174 | # print 175 | return $text."\n"; 176 | } 177 | 178 | /** 179 | * Show GUI result 180 | * 181 | * @access public 182 | * @param mixed $class 183 | * @param string|array|object $text 184 | * @param mixed $popup 185 | * @param mixed $popup2 186 | * @param bool $reload 187 | * @return void 188 | */ 189 | public function show_message ($class, $text, $popup, $popup2, $reload) { 190 | // to array if object 191 | if (is_object($text)) { $text = (array) $text; } 192 | // format if array 193 | if(is_array($text)) { 194 | // single value 195 | if(sizeof( $text )==1) { 196 | $out = $text; 197 | } 198 | // multiple values 199 | else { 200 | $out[] = "
    "; 201 | foreach( $text as $l ) { $out[] = "
  • $l
  • "; } 202 | $out[] = "
"; 203 | } 204 | // join 205 | $text = implode("\n", $out); 206 | } 207 | 208 | # print popup or normal 209 | if($popup===false) { 210 | return "
".$text."
"; 211 | } 212 | else { 213 | // set close class for JS 214 | $pclass = $popup2===false ? "hidePopups" : "hidePopup2"; 215 | // change danger to error for popup 216 | $htext = $class==="danger" ? "error" : $class; 217 | 218 | $out[] = '
'._(ucwords($htext)).'
'; 219 | $out[] = '
'; 220 | $out[] = '
'.$text.'
'; 221 | $out[] = '
'; 222 | // reload 223 | if($reload===true) 224 | $out[] = '
'; 225 | else 226 | $out[] = '
'; 227 | 228 | // return 229 | return implode("\n", $out); 230 | } 231 | } 232 | 233 | /** 234 | * Sets new header and throws exception 235 | * 236 | * @access public 237 | * @param int $code (default: 500) 238 | * @param mixed $exception 239 | * @return void 240 | */ 241 | public function throw_exception ($code = 500, $exception) { 242 | // set failed 243 | $this->exception = true; 244 | 245 | // set success 246 | $this->result['success'] = false; 247 | // set exit code 248 | $this->result['code'] = $code; 249 | 250 | // set message 251 | $this->result['message'] = $exception; 252 | 253 | // set header 254 | $this->set_header (); 255 | 256 | // throw exception 257 | throw new Exception($exception); 258 | } 259 | 260 | /** 261 | * Sets location header for newly created objects 262 | * 263 | * @access protected 264 | * @param mixed $location 265 | * @return void 266 | */ 267 | protected function set_location_header ($location) { 268 | # validate location header 269 | if(!preg_match('/^[a-zA-Z0-9\-\_\/.]+$/i',$location)) { 270 | $this->throw_exception (500, "Invalid location header"); 271 | } 272 | # set 273 | header("Location: ".$location); 274 | } 275 | 276 | /** 277 | * Sets header based on provided HTTP code 278 | * 279 | * @access protected 280 | * @return void 281 | */ 282 | protected function set_header () { 283 | // wrong code 284 | if(!isset($this->errors[$this->result['code']])) { 285 | $this->throw_exception (500, _("Invalid result code")); 286 | } 287 | 288 | // 401 - add location 289 | if ($this->result['code']==401) { 290 | $this->set_location_header ("/api/".$_REQUEST['app_id']."/user/"); 291 | } 292 | header("HTTP/1.1 ".$this->result['code']." ".$this->errors[$this->result['code']]); 293 | } 294 | 295 | /** 296 | * __destruct function 297 | * 298 | * @access public 299 | * @return void 300 | */ 301 | public function __destruct() { 302 | // exit if required 303 | if ($this->die === true) { die(); } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /functions/classes/class.Scan.php: -------------------------------------------------------------------------------- 1 | Database = $database; 115 | # initialize Result 116 | $this->Result = new Result (); 117 | 118 | # fetch settings 119 | $settings = is_null($this->settings) ? $this->get_settings() : (object) $this->settings; 120 | $config = Config::ValueOf('config'); 121 | 122 | $this->ping_type = $config['method']; 123 | $this->ping_path = ($this->ping_type == "ping") ? $config['pingpath'] : ''; 124 | $this->fping_path = ($this->ping_type == "fping") ? $config['pingpath'] : ''; 125 | 126 | # set type 127 | $this->reset_scan_method ($this->ping_type); 128 | # set OS type 129 | $this->set_os_type (); 130 | # set php exec 131 | $this->set_php_exec (); 132 | # Log object 133 | $this->Log = new Logging ($this->Database, $settings); 134 | 135 | if ($errmsg = php_feature_missing(null, ['exec'])) 136 | $this->Result->show("danger", $errmsg, true); 137 | } 138 | 139 | /** 140 | * Returns all possible ping types / apps 141 | * 142 | * @access public 143 | * @return array 144 | */ 145 | public function ping_fetch_types () { 146 | return array("ping", "pear", "fping"); 147 | } 148 | 149 | /** 150 | * This functin resets the scan method, for cron scripts 151 | * 152 | * @access public 153 | * @param mixed $method 154 | * @return void 155 | */ 156 | public function reset_scan_method ($method) { 157 | // fetch possible methods 158 | $possible = $this->ping_fetch_types (); 159 | //check 160 | if(!in_array($method, $possible)) { 161 | //die or print? 162 | if($this->icmp_exit) { die(json_encode(array("status"=>1, "error"=>"Invalid scan method"))); } 163 | else { $this->Result->show("danger", "Invalid scan method", true); } 164 | } 165 | //ok 166 | else { 167 | $this->icmp_type = $method; 168 | } 169 | } 170 | 171 | /** 172 | * Sets php exec 173 | * 174 | * @access private 175 | * @return void 176 | */ 177 | private function set_php_exec () { 178 | // Invoked via CLI, use current php-cli binary if known (>php5.3) 179 | if ( php_sapi_name() === "cli" && defined('PHP_BINARY') ) { 180 | $this->php_exec = PHP_BINARY; 181 | return; 182 | } 183 | 184 | // Invoked via HTML (or php5.3) 185 | $php_cli_binary = Config::ValueOf('php_cli_binary'); 186 | 187 | // Check for user specified php-cli binary (Multiple php versions installed) 188 | if ( !empty($php_cli_binary) ) { 189 | $this->php_exec = $php_cli_binary; 190 | return; 191 | } 192 | 193 | // Default: Use system default php version symlinked to php in PHP_BINDIR 194 | $this->php_exec = $this->os_type=="Windows" ? PHP_BINARY : PHP_BINDIR."/php"; 195 | } 196 | 197 | /** 198 | * Sets OS type 199 | * 200 | * @method set_os_type 201 | */ 202 | private function set_os_type () { 203 | if (PHP_OS == "FreeBSD" || PHP_OS == "NetBSD") { $this->os_type = "FreeBSD"; } 204 | elseif(PHP_OS == "Linux" || PHP_OS == "OpenBSD") { $this->os_type = "Linux"; } 205 | elseif(PHP_OS == "WIN32" || PHP_OS == "Windows" || PHP_OS == "WINNT") { $this->os_type = "Windows"; } 206 | } 207 | 208 | /** 209 | * Set weather the code should exit or return 210 | * 211 | * Default is return 212 | * 213 | * @access public 214 | * @param bool $exit (default: false) 215 | * @return void 216 | */ 217 | public function ping_set_exit ($exit = false) { 218 | $this->icmp_exit = $exit; 219 | } 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | /** 230 | * @ping @icmp methods 231 | * -------------------------------- 232 | */ 233 | 234 | /** 235 | * Function that pings address and checks if it responds 236 | * 237 | * any script can be used by extension, important are results 238 | * 239 | * 0 = Alive 240 | * 1 = Offline 241 | * 2 = Offline 242 | * 243 | * all other codes can be explained in ping_exit_explain method 244 | * 245 | * @access public 246 | * @param mixed $address 247 | * @param int $count (default: 1) 248 | * @param int $timeout (default: 1) 249 | * @param bool $exit (default: false) 250 | * @return void 251 | */ 252 | public function ping_address ($address, $count=1, $timeout = 1) { 253 | #set parameters 254 | $this->icmp_timeout = $timeout; 255 | $this->icmp_count = $count; 256 | 257 | # escape address 258 | $address = escapeshellarg($address); 259 | 260 | # make sure it is in right format 261 | $address = $this->transform_address ($address, "dotted"); 262 | # set method name variable 263 | $ping_method = "ping_address_method_".$this->icmp_type; 264 | # ping with selected method 265 | return $this->{$ping_method} ($address); 266 | } 267 | 268 | /** 269 | * Ping selected address and return response 270 | * 271 | * timeout value: for miliseconds multiplyy by 1000 272 | * 273 | * @access protected 274 | * @param ip $address 275 | * @return void 276 | */ 277 | protected function ping_address_method_ping ($address) { 278 | # if ipv6 append 6 279 | $ping_path = ($this->identify_address ($address)=="IPv6") ? $this->ping_path."6" : $this->ping_path; 280 | 281 | # verify ping path 282 | $this->ping_verify_path ($ping_path); 283 | 284 | # set ping command based on OS type 285 | if ($this->os_type == "FreeBSD") { $cmd = $ping_path." -c $this->icmp_count -W ".($this->icmp_timeout*1000)." $address 1>/dev/null 2>&1"; } 286 | elseif($this->os_type == "Linux") { $cmd = $ping_path." -c $this->icmp_count -W $this->icmp_timeout $address 1>/dev/null 2>&1"; } 287 | elseif($this->os_type == "Windows") { $cmd = $ping_path." -n $this->icmp_count -w ".($this->icmp_timeout*1000)." $address"; } 288 | else { $cmd = $ping_path." -c $this->icmp_count -n $address 1>/dev/null 2>&1"; } 289 | 290 | # for IPv6 remove wait 291 | if ($this->identify_address ($address)=="IPv6") { 292 | $cmd = explode(" ", $cmd); 293 | unset($cmd[3], $cmd[4]); 294 | $cmd = implode(" ", $cmd); 295 | } 296 | 297 | # execute command, return $retval 298 | exec($cmd, $output, $retval); 299 | 300 | # return result for web or cmd 301 | if($this->icmp_exit) { exit ($retval); } 302 | else { return $retval; } 303 | } 304 | 305 | /** 306 | * Ping selected address with PEAR ping package 307 | * 308 | * @access protected 309 | * @param ip $address 310 | * @return void 311 | */ 312 | protected function ping_address_method_pear ($address) { 313 | # we need pear ping package 314 | require_once(dirname(__FILE__) . '/../../functions/PEAR/Net/Ping.php'); 315 | $ping = Net_Ping::factory(); 316 | 317 | # ipv6 not supported 318 | if (!is_object($ping) || $this->identify_address ($address)=="IPv6") { 319 | //return result for web or cmd 320 | if($this->icmp_exit) { exit (255); } 321 | else { return 255; } 322 | } 323 | 324 | # Check for PEAR_Error 325 | if ($ping instanceof PEAR_Error) { 326 | //return result for web or cmd 327 | if($this->icmp_exit) { exit ($ping->code); } 328 | else { return $ping->code; } 329 | } 330 | 331 | # check for errors 332 | if($ping->pear->isError($ping)) { 333 | if($this->icmp_exit) { exit ($ping->getMessage()); } 334 | else { return $ping->getMessage(); } 335 | } 336 | else { 337 | //set count and timeout 338 | $ping->setArgs(array('count' => $this->icmp_timeout, 'timeout' => $this->icmp_timeout)); 339 | //execute 340 | $ping_response = $ping->ping($address); 341 | //check response for error 342 | if($ping->pear->isError($ping_response)) { 343 | $result['code'] = 2; 344 | } 345 | else { 346 | //all good 347 | if($ping_response->_transmitted == $ping_response->_received) { 348 | $result['code'] = 0; 349 | $this->rtt = "RTT: ". strstr($ping_response->_round_trip['avg'], ".", true); 350 | } 351 | //ping loss 352 | elseif($ping_response->_received == 0) { 353 | $result['code'] = 1; 354 | } 355 | //failed 356 | else { 357 | $result['code'] = 3; 358 | } 359 | } 360 | } 361 | 362 | //return result for web or cmd 363 | if($this->icmp_exit) { exit ($result['code']); } 364 | else { return $result['code']; } 365 | } 366 | 367 | /** 368 | * Ping selected address with fping function 369 | * 370 | * Exit status is: 371 | * 0 if all the hosts are reachable, 372 | * 1 if some hosts were unreachable, 373 | * 2 if any IP addresses were not found, 374 | * 3 for invalid command line arguments, 375 | * 4 for a system call failure. 376 | * 377 | * fping cannot be run from web, it needs root privileges to be able to open raw socket :/ 378 | * 379 | * @access public 380 | * @param mixed $subnet //CIDR 381 | * @return void 382 | */ 383 | public function ping_address_method_fping ($address) { 384 | # if ipv6 append 6 385 | $fping_path = ($this->identify_address ($address)=="IPv6") ? $this->fping_path."6" : $this->fping_path; 386 | 387 | # verify ping path 388 | $this->ping_verify_path ($fping_path); 389 | 390 | # set command 391 | $cmd = $fping_path." -c $this->icmp_count -t ".($this->icmp_timeout*1000)." $address"; 392 | # execute command, return $retval 393 | exec($cmd, $output, $retval); 394 | 395 | # save result 396 | if($retval==0) { 397 | $this->save_fping_rtt ($output[0]); 398 | } 399 | 400 | # return result for web or cmd 401 | if($this->icmp_exit) { exit ($retval); } 402 | else { return $retval; } 403 | } 404 | 405 | /** 406 | * Saves RTT for fping 407 | * 408 | * @access private 409 | * @param mixed $line 410 | * @return void 411 | */ 412 | private function save_fping_rtt ($line) { 413 | // 173.192.112.30 : xmt/rcv/%loss = 1/1/0%, min/avg/max = 160/160/160 414 | $tmp = explode(" ",$line); 415 | 416 | # save rtt 417 | @$this->rtt = "RTT: ".str_replace("(", "", $tmp[7]); 418 | } 419 | 420 | /** 421 | * Ping selected address with fping function 422 | * 423 | * Exit status is: 424 | * 0 if all the hosts are reachable, 425 | * 1 if some hosts were unreachable, 426 | * 2 if any IP addresses were not found, 427 | * 3 for invalid command line arguments, 428 | * 4 for a system call failure. 429 | * 430 | * fping cannot be run from web, it needs root privileges to be able to open raw socket :/ 431 | * 432 | * @access public 433 | * @param mixed $subnet //CIDR 434 | * @return void 435 | */ 436 | public function ping_address_method_fping_subnet ($subnet_cidr, $return_result = false) { 437 | # if ipv6 append 6 438 | $fping_path = ($this->identify_address ($subnet_cidr)=="IPv6") ? $this->fping_path."6" : $this->fping_path; 439 | 440 | # verify ping path 441 | $this->ping_verify_path ($fping_path); 442 | 443 | # set command 444 | $cmd = $fping_path." -c $this->icmp_count -t ".($this->icmp_timeout*1000)." -Ag $subnet_cidr"; 445 | # execute command, return $retval 446 | exec($cmd, $output, $retval); 447 | 448 | # save result 449 | if(sizeof($output)>0) { 450 | foreach($output as $line) { 451 | if (!preg_match('/timed out/', $line)) { 452 | $tmp = explode(" ",$line); 453 | $out[] = $tmp[0]; 454 | } 455 | } 456 | } 457 | 458 | # save to var 459 | $this->fping_result = $out; 460 | 461 | # return result? 462 | if($return_result) { return $out; } 463 | 464 | # return result for web or cmd 465 | if($this->icmp_exit) { exit ($retval); } 466 | else { return $retval; } 467 | } 468 | 469 | /** 470 | * Verifies that ping file exists 471 | * 472 | * @access private 473 | * @param mixed $path 474 | * @return void 475 | */ 476 | private function ping_verify_path ($path) { 477 | // Windows 478 | if($this->os_type=="Windows") { 479 | if(!file_exists('"'.$path.'"')) { 480 | if($this->icmp_exit) { exit ($this->ping_exit_explain(1000)); } 481 | else { return $this->Result->show("danger", _($this->ping_exit_explain(1000)), true); } 482 | } 483 | } 484 | else { 485 | if(!file_exists($path)) { 486 | if($this->icmp_exit) { exit ($this->ping_exit_explain(1000)); } 487 | else { return $this->Result->show("danger", _($this->ping_exit_explain(1000)), true); } 488 | } 489 | } 490 | } 491 | 492 | /** 493 | * Explains invalid error codes 494 | * 495 | * @access public 496 | * @param mixed $code 497 | * @return void 498 | */ 499 | public function ping_exit_explain ($code) { 500 | # fetch explain codes 501 | $explain_codes = $this->ping_set_exit_code_explains (); 502 | 503 | # return code 504 | return isset($explain_codes[$code]) ? $explain_codes[$code] : false; 505 | } 506 | 507 | /** 508 | * This function sets ping exit code and message mappings 509 | * 510 | * http://www.freebsd.org/cgi/man.cgi?query=sysexits&apropos=0&sektion=0&manpath=FreeBSD+4.3-RELEASE&arch=default&format=ascii 511 | * 512 | * extend if needed for future scripts 513 | * 514 | * @access public 515 | * @return void 516 | */ 517 | public function ping_set_exit_code_explains () { 518 | $explain_codes[0] = "SUCCESS"; 519 | $explain_codes[1] = "OFFLINE"; 520 | $explain_codes[2] = "ERROR"; 521 | $explain_codes[3] = "UNKNOWN ERROR"; 522 | $explain_codes[64] = "EX_USAGE"; 523 | $explain_codes[65] = "EX_DATAERR"; 524 | $explain_codes[68] = "EX_NOHOST"; 525 | $explain_codes[70] = "EX_SOFTWARE"; 526 | $explain_codes[71] = "EX_OSERR"; 527 | $explain_codes[72] = "EX_OSFILE"; 528 | $explain_codes[73] = "EX_CANTCREAT"; 529 | $explain_codes[74] = "EX_IOERR"; 530 | $explain_codes[75] = "EX_TEMPFAIL"; 531 | $explain_codes[77] = "EX_NOPERM"; 532 | $explain_codes[255] = "EX_NOT_SUPPORTED"; 533 | $explain_codes[1000] = "Invalid ping path"; 534 | # return codes 535 | return $explain_codes; 536 | } 537 | 538 | /** 539 | * Update lastseen field for specific IP address 540 | * 541 | * @access public 542 | * @param int $id 543 | * @param datetime $datetime 544 | * @return void 545 | */ 546 | public function ping_update_lastseen ($id, $datetime = null) { 547 | # set datetime 548 | $datetime = is_null($datetime) ? date("Y-m-d H:i:s") : $datetime; 549 | # execute 550 | try { $this->Database->updateObject("ipaddresses", array("id"=>$id, "lastSeen"=>$datetime), "id"); } 551 | catch (Exception $e) { 552 | !$this->debugging ? : $this->Result->show("danger", $e->getMessage(), false); 553 | # log 554 | !$this->debugging ? : $this->Log->write ("status_update", _('Failed to update address status'), 0 ); 555 | } 556 | } 557 | 558 | /** 559 | * Update last check time for agent 560 | * 561 | * @access public 562 | * @param int $id 563 | * @param datetime $date 564 | * @return void 565 | */ 566 | public function ping_update_scanagent_checktime ($id, $date = false) { 567 | # set time 568 | if ($date === false) { $date = date("Y-m-d H:i:s"); } 569 | else { $date = $date; } 570 | # execute 571 | try { $this->Database->updateObject("scanAgents", array("id"=>$id, "last_access"=>date("Y-m-d H:i:s")), "id"); } 572 | catch (Exception $e) { 573 | } 574 | } 575 | 576 | /** 577 | * Updates last time that subnet was scanned 578 | * 579 | * @method update_subnet_scantime 580 | * @param int $subnet_id 581 | * @param false|datetime $datetime 582 | * @return void 583 | */ 584 | public function update_subnet_scantime ($subnet_id, $datetime = false) { 585 | // set date 586 | $datetime = $datetime===false ? date("Y-m-d H:i:s") : $datetime; 587 | // update 588 | try { $this->Database->updateObject("subnets", array("id"=>$subnet_id, "lastScan"=>$datetime), "id"); } 589 | catch (Exception $e) {} 590 | } 591 | 592 | /** 593 | * Updates last time discovery check was run on subnet 594 | * 595 | * @method update_subnet_discoverytime 596 | * @param int $subnet_id 597 | * @param false|datetime $datetime 598 | * @return void 599 | */ 600 | public function update_subnet_discoverytime ($subnet_id, $datetime = false) { 601 | // set date 602 | $datetime = $datetime===false ? date("Y-m-d H:i:s") : $datetime; 603 | // update 604 | try { $this->Database->updateObject("subnets", array("id"=>$subnet_id, "lastDiscovery"=>$datetime), "id"); } 605 | catch (Exception $e) {} 606 | } 607 | 608 | /** 609 | * Updates address tag if state changes 610 | * 611 | * @method update_address_tag 612 | * @param int$address_id 613 | * @param int $tag_id (default: 2) 614 | * @param int $old_tag_id (default: null) 615 | * @param dadtetime $last_seen_date (default: false) 616 | * @return bool 617 | */ 618 | public function update_address_tag ($address_id, $tag_id = 2, $old_tag_id = null, $last_seen_date = false) { 619 | if (is_numeric($address_id)) { 620 | // don't update statuses for never seen addresses ! 621 | if ($last_seen_date!==false && !is_null($last_seen_date) && strlen($last_seen_date)>2 && $last_seen_date!="0000-00-00 00:00:00" && $last_seen_date!="1970-01-01 00:00:01" && $last_seen_date!="1970-01-01 01:00:00") { 622 | // dont update reserved to offline 623 | if (!($tag_id==1 && $old_tag_id==3)) { 624 | try { $this->Database->updateObject("ipaddresses", array("id"=>$address_id, "state"=>$tag_id), "id"); } 625 | catch (Exception $e) { 626 | return false; 627 | } 628 | } 629 | else { 630 | return false; 631 | } 632 | } 633 | else { 634 | return false; 635 | } 636 | } 637 | else { 638 | return false; 639 | } 640 | // ok 641 | return true; 642 | } 643 | 644 | /** 645 | * Opens socket connection on specified TCP ports, if at least one is available host is alive 646 | * 647 | * @access public 648 | * @param mixed $address 649 | * @param mixed $port 650 | * @return void 651 | */ 652 | public function telnet_address ($address, $port) { 653 | # set all ports 654 | $ports = explode(",", str_replace(";",",",$port)); 655 | # default response is dead 656 | $retval = 1; 657 | //try each port untill one is alive 658 | foreach($ports as $p) { 659 | // open socket 660 | $conn = @fsockopen($address, $p, $errno, $errstr, $this->icmp_timeout); 661 | //failed 662 | if (!$conn) {} 663 | //success 664 | else { 665 | $retval = 0; //set return as port if alive 666 | fclose($conn); 667 | break; //end foreach if success 668 | } 669 | } 670 | # exit with result 671 | exit($retval); 672 | } 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | /** 683 | * @prepare addresses methods 684 | * -------------------------------- 685 | */ 686 | 687 | /** 688 | * Returns all addresses to be scanned or updated 689 | * 690 | * @access public 691 | * @param mixed $type //discovery, update 692 | * @param mixed $subnet 693 | * @param bool $type 694 | * @return void 695 | */ 696 | public function prepare_addresses_to_scan ($type, $subnet, $die = true) { 697 | # discover new addresses 698 | if($type=="discovery") { return is_numeric($subnet) ? $this->prepare_addresses_to_discover_subnetId ($subnet, $die) : $this->prepare_addresses_to_discover_subnet ($subnet, $die); } 699 | # update addresses statuses 700 | elseif($type=="update") { return $this->prepare_addresses_to_update ($subnet); } 701 | # fail 702 | else { die(json_encode(array("status"=>1, "error"=>"Invalid scan type provided"))); } 703 | } 704 | 705 | /** 706 | * Returns array of all addresses to be scanned inside subnet defined with subnetId 707 | * 708 | * @access public 709 | * @param mixed $subnetId 710 | * @return void 711 | */ 712 | public function prepare_addresses_to_discover_subnetId ($subnetId, $die) { 713 | # initialize classes 714 | $Subnets = new Subnets ($this->Database); 715 | 716 | //subnet ID is provided, fetch subnet 717 | $subnet = $Subnets->fetch_subnet(null, $subnetId); 718 | if($subnet===false) { 719 | if ($die) { die(json_encode(array("status"=>1, "error"=>"Invalid subnet ID provided"))); } 720 | else { return array(); } 721 | } 722 | 723 | // we should support only up to 4094 hosts! 724 | if($Subnets->max_hosts ($subnet)>4094 && php_sapi_name()!="cli") 725 | if ($die) { die(json_encode(array("status"=>1, "error"=>"Scanning from GUI is only available for subnets up to /20 or 4094 hosts!"))); } 726 | else { return array(); } 727 | 728 | # set array of addresses to scan, exclude existing! 729 | $ip = $this->get_all_possible_subnet_addresses ($subnet->subnet, $subnet->mask); 730 | 731 | # remove excluded 732 | $ip = $this->remove_excluded_subnet_addresses ($ip, $subnetId); 733 | 734 | # remove existing 735 | $ip = $this->remove_existing_subnet_addresses ($ip, $subnetId); 736 | 737 | //none to scan? 738 | if(sizeof($ip)==0) { 739 | if ($die) { die(json_encode(array("status"=>1, "error"=>"Didn't find any address to scan!"))); } 740 | else { return array(); } 741 | } 742 | 743 | //return 744 | return $ip; 745 | } 746 | 747 | /** 748 | * Fetches all possible subnet addresses 749 | * 750 | * @access private 751 | * @param $subnet //subnet in decimal format 752 | * @param int $mask //subnet mask 753 | * @return void //array of ip addresses in decimal format 754 | */ 755 | private function get_all_possible_subnet_addresses ($subnet, $mask) { 756 | # initialize classes 757 | $Subnets = new Subnets ($this->Database); 758 | # make sure we have proper subnet format 759 | $subnet = $Subnets->transform_address($subnet, "decimal"); 760 | //fetch start and stop addresses 761 | $boundaries = (object) $Subnets->get_network_boundaries ($subnet, $mask); 762 | //create array 763 | if($mask==32) { 764 | $ip[] = $Subnets->transform_to_decimal($boundaries->network); 765 | } 766 | elseif($mask==31) { 767 | $ip[] = $Subnets->transform_to_decimal($boundaries->network); 768 | $ip[] = $Subnets->transform_to_decimal($boundaries->broadcast); 769 | } 770 | else { 771 | //set loop limits 772 | $start = gmp_strval(gmp_add($Subnets->transform_to_decimal($boundaries->network),1)); 773 | $stop = gmp_strval($Subnets->transform_to_decimal($boundaries->broadcast)); 774 | //loop 775 | for($m=$start; $m<$stop; $m++) { 776 | $ip[] = $m; 777 | } 778 | } 779 | //return 780 | return $ip; 781 | } 782 | 783 | /** 784 | * Removes excluded addresses from 785 | * 786 | * @access private 787 | * @param mixed $ip //array of ip addresses in decimal format 788 | * @param mixed $subnetId //id of subnet 789 | * @return void 790 | */ 791 | private function remove_excluded_subnet_addresses ($ip, $subnetId) { 792 | # first fetch all addresses 793 | $Addresses = new Addresses ($this->Database); 794 | // get all existing IP addresses in subnet 795 | $addresses = $Addresses->fetch_subnet_addresses($subnetId); 796 | // if some exist remove them 797 | if(is_array($addresses) && is_array($ip) && sizeof($ip)>0) { 798 | foreach($addresses as $a) { 799 | if( $a->excludePing === 1) { 800 | $key = array_search($a->ip_addr, $ip); 801 | unset($ip[$key]); 802 | } 803 | } 804 | //reindex array for pinging 805 | $ip = array_values(@$ip); 806 | } 807 | //return 808 | return is_array(@$ip) ? $ip : array(); 809 | } 810 | 811 | /** 812 | * Removes existing addresses from 813 | * 814 | * @access private 815 | * @param mixed $ip //array of ip addresses in decimal format 816 | * @param mixed $subnetId //id of subnet 817 | * @return void 818 | */ 819 | private function remove_existing_subnet_addresses ($ip, $subnetId) { 820 | # first fetch all addresses 821 | $Addresses = new Addresses ($this->Database); 822 | // get all existing IP addresses in subnet 823 | $addresses = $Addresses->fetch_subnet_addresses($subnetId); 824 | // if some exist remove them 825 | if(is_array($addresses) && is_array($ip) && sizeof($ip)>0) { 826 | foreach($addresses as $a) { 827 | $key = array_search($a->ip_addr, $ip); 828 | if($key !== false) { 829 | unset($ip[$key]); 830 | } 831 | } 832 | //reindex array for pinging 833 | $ip = array_values(@$ip); 834 | } 835 | //return 836 | return is_array(@$ip) ? $ip : array(); 837 | } 838 | 839 | /** 840 | * Returns array of all addresses in subnet 841 | * 842 | * @access public 843 | * @param mixed $subnet 844 | * @return void 845 | */ 846 | public function prepare_addresses_to_discover_subnet ($subnet) { 847 | //set subnet / mask 848 | $subnet_parsed = explode("/", $subnet); 849 | # result 850 | $ip = $this->get_all_possible_subnet_addresses ($subnet_parsed[0], $subnet_parsed[1]); 851 | //none to scan? 852 | if(sizeof($ip)==0) { die(json_encode(array("status"=>1, "error"=>"Didn't find any address to scan!"))); } 853 | //result 854 | return $ip; 855 | } 856 | 857 | /** 858 | * Returns array of all addresses to be scanned 859 | * 860 | * @access public 861 | * @param mixed $subnetId 862 | * @return void 863 | */ 864 | public function prepare_addresses_to_update ($subnetId) { 865 | # first fetch all addresses 866 | $Addresses = new Addresses ($this->Database); 867 | // get all existing IP addresses in subnet 868 | $subnet_addresses = $Addresses->fetch_subnet_addresses($subnetId); 869 | //create array 870 | if(is_array($subnet_addresses) && sizeof($subnet_addresses)>0) { 871 | foreach($subnet_addresses as $a) { 872 | $scan_addresses[$a->id] = $a->ip_addr; 873 | } 874 | //reindex 875 | $scan_addresses = array_values(@$scan_addresses); 876 | //return 877 | return $scan_addresses; 878 | } 879 | else { 880 | return array(); 881 | } 882 | } 883 | } 884 | 885 | 886 | /** 887 | * @scan helper functions 888 | * ------------------------ 889 | */ 890 | 891 | /** 892 | * Ping address helper for CLI threading 893 | * 894 | * used for: 895 | * - icmp status update (web > ping, pear) 896 | * - icmp subnet discovery (web > ping, pear) 897 | * - icmp status update (cli) 898 | * - icmp discovery (cli) 899 | */ 900 | function ping_address ($address) { 901 | // $Database = new Database_PDO; 902 | // $Scan = new Scan ($Database); 903 | global $Scan; 904 | //scan 905 | return $Scan->ping_address ($address); 906 | } 907 | 908 | /** 909 | * Telnet address helper for CLI threading 910 | */ 911 | function telnet_address ($address, $port) { 912 | global $Scan; 913 | //scan 914 | return $Scan->telnet_address ($address, $port); 915 | } 916 | 917 | /** 918 | * fping subnet helper for fping threading, all methods 919 | */ 920 | function fping_subnet ($subnet_cidr, $return = true) { 921 | global $Scan; 922 | //scan 923 | return $Scan->ping_address_method_fping_subnet ($subnet_cidr, $return); 924 | } -------------------------------------------------------------------------------- /functions/classes/class.Thread.php: -------------------------------------------------------------------------------- 1 | 6 | * @version 1.0.0 - stable 7 | * @author Tudor Barbu 8 | * @copyright MIT 9 | */ 10 | class PingThread { 11 | const FUNCTION_NOT_CALLABLE = 10; 12 | const COULD_NOT_FORK = 15; 13 | 14 | /** 15 | * possible errors 16 | * 17 | * @var array 18 | */ 19 | private $errors = array( 20 | PingThread::FUNCTION_NOT_CALLABLE => 'You must specify a valid function name that can be called from the current scope.', 21 | PingThread::COULD_NOT_FORK => 'pcntl_fork() returned a status of -1. No new process was created', 22 | ); 23 | 24 | /** 25 | * callback for the function that should 26 | * run as a separate thread 27 | * 28 | * @var callback 29 | */ 30 | protected $runnable; 31 | 32 | /** 33 | * holds the current process id 34 | * 35 | * @var integer 36 | */ 37 | private $pid; 38 | 39 | /** 40 | * holds the exit code after the child dies 41 | */ 42 | private $exitCode = -1; 43 | 44 | /** 45 | * holds type - needed for fping 46 | */ 47 | public $stype = "ping"; 48 | 49 | /** 50 | * checks if threading is supported by the current 51 | * PHP configuration 52 | * 53 | * @return boolean 54 | */ 55 | public static function available(&$errmsg = null) { 56 | $required_extensions = ['posix','pcntl']; 57 | $required_functions = ['posix_getpid','posix_getppid','posix_mkfifo','posix_kill', 58 | 'pcntl_fork','pcntl_waitpid','pcntl_wifexited','pcntl_wexitstatus','pcntl_signal']; 59 | 60 | if ($errmsg = php_feature_missing($required_extensions, $required_functions)) 61 | return false; 62 | 63 | return true; 64 | } 65 | 66 | /** 67 | * class constructor - you can pass 68 | * the callback function as an argument 69 | * 70 | * @param callback $_runnable 71 | */ 72 | public function __construct( $_runnable = null ) { 73 | if( $_runnable !== null ) { 74 | $this->setRunnable( $_runnable ); 75 | } 76 | } 77 | 78 | /** 79 | * sets the callback 80 | * 81 | * @param callback $_runnable 82 | * @return callback 83 | */ 84 | public function setRunnable( $_runnable ) { 85 | if( self::runnableOk( $_runnable ) ) { 86 | $this->runnable = $_runnable; 87 | } 88 | else { 89 | throw new Exception( $this->getError( PingThread::FUNCTION_NOT_CALLABLE ), PingThread::FUNCTION_NOT_CALLABLE ); 90 | } 91 | } 92 | 93 | /** 94 | * gets the callback 95 | * 96 | * @return callback 97 | */ 98 | public function getRunnable() { 99 | return $this->runnable; 100 | } 101 | 102 | /** 103 | * checks if the callback is ok (the function/method 104 | * actually exists and is runnable from the current 105 | * context) 106 | * 107 | * can be called statically 108 | * 109 | * @param callback $_runnable 110 | * @return boolean 111 | */ 112 | public static function runnableOk( $_runnable ) { 113 | return ( function_exists( $_runnable ) && is_callable( $_runnable ) ); 114 | } 115 | 116 | /** 117 | * returns the process id (pid) of the simulated thread 118 | * 119 | * @return int 120 | */ 121 | public function getPid() { 122 | return $this->pid; 123 | } 124 | 125 | /** 126 | * checks if the child thread is alive 127 | * 128 | * @return boolean 129 | */ 130 | public function isAlive() { 131 | $pid = pcntl_waitpid( $this->pid, $status, WNOHANG ); 132 | 133 | if ($pid === 0) { // child is still alive 134 | return true; 135 | } else { 136 | if (pcntl_wifexited($status) && $this->exitCode == -1) { // normal exit 137 | $this->exitCode = pcntl_wexitstatus($status); 138 | } 139 | return false; 140 | } 141 | } 142 | 143 | /** 144 | * return exit code of child (-1 if child is still alive) 145 | * 146 | * @return int 147 | */ 148 | public function getExitCode() { 149 | $this->isAlive(); 150 | return $this->exitCode; 151 | } 152 | 153 | /** 154 | * starts the thread, all the parameters are 155 | * passed to the callback function 156 | * 157 | * @return void 158 | */ 159 | public function start() { 160 | $pid = @ pcntl_fork(); 161 | if( $pid == -1 ) { 162 | throw new Exception( $this->getError( PingThread::COULD_NOT_FORK ), PingThread::COULD_NOT_FORK ); 163 | } 164 | if( $pid ) { 165 | // parent 166 | $this->pid = $pid; 167 | } 168 | else { 169 | // child 170 | pcntl_signal( SIGTERM, array( $this, 'signalHandler' ) ); 171 | $arguments = func_get_args(); 172 | if ( !empty( $arguments ) ) { 173 | call_user_func_array( $this->runnable, $arguments ); 174 | } 175 | else { 176 | call_user_func( $this->runnable ); 177 | } 178 | 179 | exit( 0 ); 180 | } 181 | } 182 | 183 | /** 184 | * starts the thread, all the parameters are 185 | * passed to the callback function 186 | * 187 | * modification for fping threading for cron scanning 188 | * 189 | * @return void 190 | */ 191 | public function start_fping() { 192 | $status = 0; 193 | $results = null; 194 | $pid = pcntl_fork(); 195 | 196 | if( $pid == -1 ) { //error forking, no child is created 197 | throw new Exception( $this->getError( PingThread::COULD_NOT_FORK ), PingThread::COULD_NOT_FORK ); 198 | }else if ( $pid ) {// parent 199 | $this->pid = $pid; 200 | 201 | } else { // child 202 | $this->pid = posix_getpid();//pid (child) 203 | $this->ppid = posix_getppid();//pid (parent) 204 | 205 | pcntl_signal( SIGTERM, array( $this, 'signalHandler' ) ); 206 | $array_args = func_get_args(); 207 | if ( !empty( $array_args ) ) { 208 | $results = call_user_func_array( $this->runnable, $array_args ); 209 | }else{ 210 | $results = call_user_func( $this->runnable ); 211 | } 212 | 213 | $pipe = "/tmp/pipe_".$this->pid;//pid is known by parent 214 | 215 | if(!file_exists($pipe)) {//child talks to parent using this pipe 216 | umask(0); 217 | posix_mkfifo($pipe, 0600); 218 | } 219 | //we have to open the pipe and send the data serialized 220 | $pipe_descriptor = fopen($pipe, 'w'); 221 | fwrite($pipe_descriptor, serialize( $results ) ); 222 | 223 | //and kill the child using posix_kill ( exit(0) duplicates headers!! ) 224 | posix_kill( $this->pid , SIGKILL); 225 | exit(0); 226 | } 227 | } 228 | 229 | /** 230 | * attempts to stop the thread 231 | * returns true on success and false otherwise 232 | * 233 | * @param integer $_signal - SIGKILL/SIGTERM 234 | * @param boolean $_wait 235 | */ 236 | public function stop( $_signal = SIGKILL, $_wait = false ) { 237 | if( $this->isAlive() ) { 238 | posix_kill( $this->pid, $_signal ); 239 | if( $_wait ) { 240 | pcntl_waitpid( $this->pid, $status = 0 ); 241 | } 242 | } 243 | } 244 | 245 | /** 246 | * alias of stop(); 247 | * 248 | * @return boolean 249 | */ 250 | public function kill( $_signal = SIGKILL, $_wait = false ) { 251 | return $this->stop( $_signal, $_wait ); 252 | } 253 | 254 | /** 255 | * gets the error's message based on 256 | * its id 257 | * 258 | * @param integer $_code 259 | * @return string 260 | */ 261 | public function getError( $_code ) { 262 | if ( isset( $this->errors[$_code] ) ) { 263 | return $this->errors[$_code]; 264 | } 265 | else { 266 | return 'No such error code ' . $_code . '! Quit inventing errors!!!'; 267 | } 268 | } 269 | 270 | /** 271 | * signal handler 272 | * 273 | * @param integer $_signal 274 | */ 275 | protected function signalHandler( $_signal ) { 276 | switch( $_signal ) { 277 | case SIGTERM: 278 | exit( 0 ); 279 | break; 280 | } 281 | } 282 | } 283 | 284 | // EOF 285 | -------------------------------------------------------------------------------- /functions/classes/class.phpipamAgent.php: -------------------------------------------------------------------------------- 1 | Result = new Result (); 170 | // read config file 171 | $this->config = (object) Config::ValueOf('config'); 172 | // set time 173 | $this->set_now_time (); 174 | // set valid connection types 175 | $this->set_valid_conn_types (); 176 | // set valid scan types 177 | $this->set_valid_scan_types (); 178 | // set valid scan types 179 | $this->set_valid_ping_types (); 180 | // set conn type 181 | $this->set_conn_type (); 182 | // set ping type 183 | $this->set_ping_type (); 184 | // validate php build 185 | $this->validate_php_build (); 186 | // validate threading 187 | $this->validate_threading (); 188 | // validate ping path 189 | $this->validate_ping_path (); 190 | // save database 191 | $this->Database = $Database; 192 | } 193 | 194 | /** 195 | * Sets execution start date in time and date format 196 | * 197 | * @access private 198 | * @return void 199 | */ 200 | private function set_now_time () { 201 | $this->nowdate = date("Y-m-d H:i:s"); 202 | $this->now = strtotime($this->nowdate); 203 | } 204 | 205 | /** 206 | * Defines valid connection types to phpipam server 207 | * 208 | * @access private 209 | * @return void 210 | */ 211 | private function set_valid_conn_types () { 212 | // set valid types 213 | $this->conn_types = array( 214 | 'api', 215 | 'mysql' 216 | ); 217 | } 218 | 219 | /** 220 | * Sets valid scan types (update, discover) 221 | * 222 | * @access private 223 | * @return void 224 | */ 225 | private function set_valid_scan_types () { 226 | // set valid types 227 | $this->scan_types = array( 228 | 'update', 229 | 'discover' 230 | ); 231 | } 232 | 233 | /** 234 | * Sets valid ping types (ping, fping, pear) 235 | * 236 | * @access private 237 | * @return void 238 | */ 239 | private function set_valid_ping_types () { 240 | // set valid types 241 | $this->ping_types = array( 242 | 'ping', 243 | 'fping', 244 | 'pear' 245 | ); 246 | } 247 | 248 | /** 249 | * Validates connection type 250 | * 251 | * @access private 252 | * @return void 253 | */ 254 | private function validate_conn_type () { 255 | //validate 256 | if (!in_array($this->config->type, $this->conn_types)) { 257 | $this->Result->throw_exception (500, "Invalid connection type!"); 258 | } 259 | } 260 | 261 | /** 262 | * Sets connection type to database 263 | * 264 | * @access private 265 | * @return void 266 | */ 267 | private function set_conn_type () { 268 | // validate 269 | $this->validate_conn_type (); 270 | // save 271 | $this->conn_type = $this->config->type; 272 | } 273 | 274 | /** 275 | * Sets type of scan to be executed 276 | * 277 | * @access public 278 | * @param mixed $type 279 | * @return void 280 | */ 281 | public function set_scan_type ($type) { 282 | //validate 283 | if (!in_array($type,$this->scan_types)) { 284 | $this->Result->throw_exception (500, "Invalid scan type - $type! Valid options are ".implode(", ", $this->scan_types)); 285 | } 286 | // ok, save 287 | $this->scan_type = $type; 288 | } 289 | 290 | /** 291 | * Sets type of ping to be executed 292 | * 293 | * @access public 294 | * @return void 295 | */ 296 | public function set_ping_type () { 297 | //validate 298 | if (!in_array($this->config->method, $this->ping_types)) { 299 | $this->Result->throw_exception (500, "Invalid ping method - \$config['method'] = \"".escape_input($this->config->method)."\""); 300 | } 301 | // ok, save 302 | $this->ping_type = $this->config->method; 303 | } 304 | 305 | /** 306 | * Validates php build 307 | * 308 | * @access private 309 | * @return void 310 | */ 311 | private function validate_php_build () { 312 | // set required extensions 313 | $required_ext = $this->set_required_extensions (); 314 | // get available ext 315 | $available_ext = get_loaded_extensions(); 316 | // loop and check 317 | foreach ($required_ext as $e) { 318 | if (!in_array($e, $available_ext)) { 319 | $missing_ext[] = $e; 320 | } 321 | } 322 | 323 | // die if missing 324 | if (isset($missing_ext)) { 325 | $error[] = "The following required PHP extensions are missing for phpipam-agent:"; 326 | foreach ($missing_ext as $missing) { 327 | $error[] =" - ". $missing; 328 | } 329 | $error[] = "Please recompile PHP to include missing extensions."; 330 | 331 | // die 332 | $this->Result->throw_exception (500, $error); 333 | } 334 | } 335 | 336 | /** 337 | * Validates threading support 338 | * 339 | * @access private 340 | * @return void 341 | */ 342 | private function validate_threading () { 343 | // only for threaded 344 | if($this->config->nonthreaded !== true) { 345 | // test to see if threading is available 346 | if(!PingThread::available($errmsg)) { 347 | $this->Result->throw_exception (500, "Threading is required for scanning subnets - Error: $errmsg\n"); 348 | } 349 | } 350 | } 351 | 352 | /** 353 | * Validate path for ping file 354 | * 355 | * @access private 356 | * @return void 357 | */ 358 | private function validate_ping_path () { 359 | if(!file_exists($this->config->pingpath)) { 360 | $this->Result->throw_exception (500, "ping executable does not exist - \$config['pingpath'] = \"".escape_input($this->config->pingpath)."\""); 361 | } 362 | } 363 | 364 | /** 365 | * Sets required extensions for agent to run 366 | * 367 | * @access private 368 | * @return void 369 | */ 370 | private function set_required_extensions () { 371 | // general 372 | $required_ext = array("gmp", "json", "pcntl"); 373 | 374 | // if mysql selected 375 | if ($this->config->type=="mysql") { 376 | $required_ext = array_merge($required_ext, array("PDO", "pdo_mysql")); 377 | } 378 | // if non-threaded permitted remove pcntl requirement 379 | if ($this->config->nonthreaded === true) { 380 | unset($required_ext[2]); 381 | } 382 | // if api selected 383 | 384 | // result 385 | return $required_ext; 386 | } 387 | 388 | /** 389 | * Resolves hostname 390 | * 391 | * @access public 392 | * @param object $address 393 | * @param boolean $override override DNS resolving flag 394 | * @return array 395 | */ 396 | public function resolve_address ($address, $override=false) { 397 | # make sure it is dotted format 398 | $address->ip = $this->transform_address ($address->ip_addr, "dotted"); 399 | # if hostname is set try to check 400 | if(empty($address->hostname) || is_null($address->hostname)) { 401 | # if permitted in settings 402 | if($this->settings->enableDNSresolving == 1 || $override) { 403 | # resolve 404 | $resolved = gethostbyaddr($address->ip); 405 | if($resolved==$address->ip) $resolved=""; //resolve fails 406 | 407 | return array("class"=>"resolved", "name"=>$resolved); 408 | } 409 | else { 410 | return array("class"=>"", "name"=>""); 411 | } 412 | } 413 | else { 414 | return array("class"=>"", "name"=>$address->hostname); 415 | } 416 | } 417 | 418 | /** 419 | * Prints success 420 | * 421 | * @access private 422 | * @param string|array $text 423 | * @return void 424 | */ 425 | private function print_success ($text) { 426 | // array ? 427 | if (is_array($text)) { 428 | foreach ($text as $t) { 429 | $success[] = $t; 430 | } 431 | $success[] = ""; 432 | } else { 433 | $success[] = $text."\n"; 434 | } 435 | // print 436 | print implode("\n",$success); 437 | } 438 | 439 | /** 440 | * Executes scan / discover. 441 | * 442 | * @access public 443 | * @return void 444 | */ 445 | public function execute () { 446 | // initialize proper function 447 | $init = 'initialize_'.$this->conn_type; 448 | // init 449 | return $this->{$init} (); 450 | } 451 | 452 | 453 | /** 454 | * Sets scan object 455 | * 456 | * @access private 457 | * @return void 458 | */ 459 | private function scan_set_object () { 460 | # initialize objects 461 | $this->Scan = new Scan ($this->Database); 462 | // set ping statuses 463 | $statuses = explode(";", $this->settings->pingStatus); 464 | } 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | /** 475 | * @api functions 476 | * --------------------------------- 477 | */ 478 | 479 | /** 480 | * Initialize API 481 | * 482 | * @access private 483 | * @return void 484 | */ 485 | private function initialize_api () { 486 | $this->Result->throw_exception (500, "API agent type not yet supported!"); 487 | } 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | /** 498 | * @mysql functions 499 | * --------------------------------- 500 | */ 501 | 502 | /** 503 | * Initialized mysql connection 504 | * 505 | * @access private 506 | * @return void 507 | */ 508 | private function initialize_mysql () { 509 | // test connection 510 | $this->mysql_test_connection (); 511 | 512 | // validate key and fetch agent 513 | $this->mysql_validate_key (); 514 | 515 | // fetch settings 516 | $this->mysql_fetch_settings (); 517 | 518 | // initialize scan object 519 | $this->scan_set_object (); 520 | 521 | // we have subnets, now check 522 | return $this->scan_type == "update" ? $this->mysql_scan_update_host_statuses () : $this->mysql_scan_discover_hosts (); 523 | } 524 | 525 | /** 526 | * Test connection to database server. 527 | * 528 | * Will throw exception if failure 529 | * 530 | * @access private 531 | * @return void 532 | */ 533 | private function mysql_test_connection () { 534 | $this->Database->connect(); 535 | } 536 | 537 | /** 538 | * Validates key for phpipam agent and fetches id 539 | * 540 | * @access private 541 | * @return void 542 | */ 543 | private function mysql_validate_key () { 544 | // fetch details 545 | try { $agent = $this->Database->getObjectQuery("select * from `scanAgents` where `code` = ? and `type` = 'mysql' limit 1;", array($this->config->key)); } 546 | catch (Exception $e) { 547 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 548 | } 549 | // invalid 550 | if (is_null($agent)) { 551 | $this->Result->throw_exception (500, "Error: Invalid agent code"); 552 | } 553 | // save agent details 554 | else { 555 | $this->agent_details = $agent; 556 | } 557 | } 558 | 559 | /** 560 | * Update last check for agent 561 | * 562 | * @access public 563 | * @return void 564 | */ 565 | public function update_agent_scantime () { 566 | // update access time 567 | try { $agent = $this->Database->runQuery("update `scanAgents` set `last_access` = ? where `id` = ? limit 1;", array($this->nowdate, $this->agent_details->id)); } 568 | catch (Exception $e) { 569 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 570 | } 571 | } 572 | 573 | /** 574 | * Fetches settings from master database 575 | * 576 | * @access private 577 | * @return void 578 | */ 579 | private function mysql_fetch_settings () { 580 | # fetch 581 | try { $settings = $this->Database->getObject("settings", 1); } 582 | catch (Exception $e) { 583 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 584 | } 585 | # save 586 | $this->settings = $settings; 587 | } 588 | 589 | 590 | /** 591 | * Check for last statuses of hosts 592 | * 593 | * @access public 594 | * @return void 595 | */ 596 | public function mysql_scan_update_host_statuses () { 597 | // prepare addresses 598 | $subnets = $this->mysql_fetch_subnets ($this->agent_details->id); 599 | // fetch addresses 600 | $addresses = $this->mysql_fetch_addresses ($subnets, "update"); 601 | // save existing and reindexed 602 | $addresses_tmp = $addresses[0]; 603 | $addresses = (array) $addresses[1]; 604 | 605 | // non-threaded? 606 | if ($this->config->nonthreaded === true) { 607 | // execute 608 | if ($this->ping_type=="fping") { $subnets = $this->mysql_scan_discover_hosts_fping_nonthreaded ($subnets, $addresses_tmp); } 609 | else { $subnets = $this->mysql_scan_discover_hosts_ping_nonthreaded ($subnets, $addresses); } 610 | } 611 | else { 612 | // execute 613 | if ($this->ping_type=="fping") { $subnets = $this->mysql_scan_discover_hosts_fping ($subnets, $addresses_tmp); } 614 | else { $subnets = $this->mysql_scan_discover_hosts_ping ($subnets, $addresses); } 615 | } 616 | 617 | // update database and send mail if requested 618 | $this->mysql_scan_update_write_to_db ($subnets); 619 | // reset dhcp 620 | if($this->config->remove_inactive_dhcp===true) { 621 | $this->reset_dhcp_addresses(); 622 | } 623 | // reset autodiscovered DHCP addresses 624 | if($this->config->reset_autodiscover_addresses===true) { 625 | $this->reset_autodiscover_addresses(); 626 | } 627 | 628 | // updatelast scantime 629 | foreach ($subnets as $s) { 630 | $this->update_subnet_status_scantime ($s->id); 631 | } 632 | } 633 | 634 | /** 635 | * Update last scan date 636 | * 637 | * @method update_subnet_status_scantime 638 | * @param int $subnet_id 639 | * @return void 640 | */ 641 | private function update_subnet_status_scantime ($subnet_id) { 642 | try { $this->Database->updateObject("subnets", array("id"=>$subnet_id, "lastScan"=>$this->nowdate), "id"); } 643 | catch (Exception $e) {} 644 | } 645 | 646 | /** 647 | * Scan for new hosts in selected networks 648 | * 649 | * @access public 650 | * @return void 651 | */ 652 | public function mysql_scan_discover_hosts () { 653 | // prepare addresses 654 | $subnets = $this->mysql_fetch_subnets ($this->agent_details->id); 655 | // fetch addresses 656 | $addresses = $this->mysql_fetch_addresses ($subnets, "discovery"); 657 | // save existing and reindexed 658 | $addresses_tmp = $addresses[0]; 659 | $addresses = $addresses[1]; 660 | 661 | // execute 662 | if ($this->ping_type=="fping") { $subnets = $this->mysql_scan_discover_hosts_fping ($subnets, $addresses_tmp); } 663 | else { $subnets = $this->mysql_scan_discover_hosts_ping ($subnets, $addresses); } 664 | 665 | // update database and send mail if requested 666 | $this->mysql_scan_discovered_write_to_db ($subnets); 667 | // updatelast scantime 668 | foreach ($subnets as $s) { 669 | $this->update_subnet_discovery_scantime ($s->id); 670 | } 671 | } 672 | 673 | /** 674 | * Update last discovery date 675 | * 676 | * @method update_subnet_discovery_scantime 677 | * @param int $subnet_id 678 | * @return void 679 | */ 680 | private function update_subnet_discovery_scantime ($subnet_id) { 681 | try { $this->Database->updateObject("subnets", array("id"=>$subnet_id, "lastDiscovery"=>$this->nowdate), "id"); } 682 | catch (Exception $e) {} 683 | } 684 | 685 | /** 686 | * This function fetches id, subnet and mask for all subnets 687 | * 688 | * @access private 689 | * @param int $agentId (default:null) 690 | * @return void 691 | */ 692 | private function mysql_fetch_subnets ($agentId = null ) { 693 | # null 694 | if (is_null($agentId) || !is_numeric($agentId)) { return false; } 695 | # get type 696 | $type = $this->get_scan_type_field (); 697 | # fetch 698 | try { $subnets = $this->Database->getObjectsQuery("SELECT `id`,`subnet`,`sectionId`,`mask`,`resolveDNS`,`nameserverId` FROM `subnets` WHERE `scanAgent` = ? AND `$type` = 1 AND `isFolder` = 0 AND `mask` > 0;", array($agentId)); } 699 | catch (Exception $e) { 700 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 701 | } 702 | # die if nothing to scan 703 | if (sizeof($subnets)==0) { die(); } 704 | // if subnet has slaves dont check it 705 | foreach ($subnets as $k=>$s) { 706 | try { $count = $this->Database->numObjectsFilter("subnets", "masterSubnetId", $s->id); } 707 | catch (Exception $e) { 708 | $this->Result->show("danger", _("Error: ").$e->getMessage()); 709 | return false; 710 | } 711 | if ($count>0) { 712 | unset($subnets[$k]); 713 | } 714 | } 715 | # die if nothing to scan 716 | if (!isset($subnets)) { die(); } 717 | # result 718 | return $subnets; 719 | } 720 | 721 | /** 722 | * Fetches addresses to scan 723 | * 724 | * @access private 725 | * @param array $subnets 726 | * @param string $type (discovery, update) 727 | * @return void 728 | */ 729 | private function mysql_fetch_addresses ($subnets, $type) { 730 | // array check 731 | if(!is_array($subnets)) { die(); } 732 | // loop through subnets and save addresses to scan 733 | foreach($subnets as $s) { 734 | // if subnet has slaves dont check it 735 | if ($this->mysql_check_slaves ($s->id) === false) { 736 | $addresses_tmp[$s->id] = $this->Scan->prepare_addresses_to_scan ($type, $s->id); 737 | } 738 | } 739 | // if false exit 740 | if(!isset($addresses_tmp)) { die(); } 741 | 742 | // reindex 743 | if (isset($addresses_tmp)) { 744 | foreach($addresses_tmp as $s_id=>$a) { 745 | foreach($a as $ip) { 746 | $addresses[] = array("subnetId"=>$s_id, "ip_addr"=>$ip); 747 | } 748 | } 749 | } 750 | else { 751 | $addresses_tmp = array(); 752 | $addresses = array(); 753 | } 754 | // return result - $addresses_tmp = existing, $addresses = reindexed 755 | return array($addresses_tmp, $addresses); 756 | } 757 | 758 | /** 759 | * Check if subnet has slaves 760 | * 761 | * @access private 762 | * @param int $subnetId 763 | * @return void 764 | */ 765 | private function mysql_check_slaves ($subnetId) { 766 | // int check 767 | if(!is_numeric($subnetId)) { return false; } 768 | // check 769 | try { $count = $this->Database->numObjectsFilter("subnets", "masterSubnetId", $subnetId); } 770 | catch (Exception $e) { 771 | $this->Result->show("danger", _("Error: ").$e->getMessage()); 772 | return false; 773 | } 774 | # result 775 | return $count>0 ? true : false; 776 | } 777 | 778 | /** 779 | * Discover new host with fping 780 | * 781 | * @access private 782 | * @param array $subnets 783 | * @param array $addresses_tmp 784 | * @return void 785 | */ 786 | private function mysql_scan_discover_hosts_fping ($subnets, $addresses_tmp) { 787 | $z = 0; //addresses array index 788 | 789 | //run per MAX_THREADS 790 | for ($m=0; $mconfig->threads) { 791 | // create threads 792 | $threads = array(); 793 | //fork processes 794 | for ($i = 0; $i < $this->config->threads; $i++) { 795 | //only if index exists! 796 | if(isset($subnets[$z])) { 797 | //start new thread 798 | $threads[$z] = new PingThread( 'fping_subnet' ); 799 | $threads[$z]->start_fping( $this->transform_to_dotted($subnets[$z]->subnet)."/".$subnets[$z]->mask ); 800 | } 801 | $z++; //next index 802 | } 803 | // wait for all the threads to finish 804 | while( !empty( $threads ) ) { 805 | foreach($threads as $index => $thread) { 806 | $child_pipe = "/tmp/pipe_".$thread->getPid(); 807 | 808 | if (file_exists($child_pipe)) { 809 | $file_descriptor = fopen( $child_pipe, "r"); 810 | $child_response = ""; 811 | while (!feof($file_descriptor)) { 812 | $child_response .= fread($file_descriptor, 8192); 813 | } 814 | //we have the child data in the parent, but serialized: 815 | $child_response = unserialize( $child_response ); 816 | //store 817 | $subnets[$index]->discovered = $child_response; 818 | //now, child is dead, and parent close the pipe 819 | unlink( $child_pipe ); 820 | unset($threads[$index]); 821 | } 822 | } 823 | } 824 | } 825 | 826 | //fping finds all subnet addresses, we must remove existing ones ! 827 | foreach($subnets as $sk=>$s) { 828 | if (is_array($s->discovered)) { 829 | foreach($s->discovered as $rk=>$result) { 830 | if(!in_array($this->transform_to_decimal($result), $addresses_tmp[$s->id])) { 831 | unset($subnets[$sk]->discovered[$rk]); 832 | } 833 | } 834 | //rekey 835 | $subnets[$sk]->discovered = array_values($subnets[$sk]->discovered); 836 | } 837 | } 838 | 839 | // return result 840 | return $subnets; 841 | } 842 | 843 | /** 844 | * Discover new hosts with ping or pear 845 | * 846 | * @access private 847 | * @param mixed $subnets 848 | * @param mixed $addresses 849 | * @return void 850 | */ 851 | private function mysql_scan_discover_hosts_ping ($subnets, $addresses) { 852 | $z = 0; //addresses array index 853 | 854 | //run per MAX_THREADS 855 | $num_of_addresses = sizeof($addresses); 856 | for ($m=0; $m<$num_of_addresses; $m += $this->config->threads) { 857 | 858 | // create threads 859 | $threads = array(); 860 | 861 | //fork processes 862 | for ($i = 0; $i < $this->config->threads; $i++) { 863 | //only if index exists! 864 | if(isset($addresses[$z])) { 865 | //start new thread 866 | $threads[$z] = new PingThread( 'ping_address' ); 867 | $threads[$z]->start( $this->transform_to_dotted( $addresses[$z]['ip_addr']) ); 868 | } 869 | $z++; //next index 870 | } 871 | 872 | // wait for all the threads to finish 873 | while( !empty( $threads ) ) { 874 | foreach( $threads as $index => $thread ) { 875 | if( !$thread->isAlive() ) { 876 | //unset dead hosts 877 | if($thread->getExitCode() != 0) { 878 | unset($addresses[$index]); 879 | } 880 | //remove thread 881 | unset($threads[$index]); 882 | } 883 | } 884 | } 885 | } 886 | 887 | //ok, we have all available addresses, rekey them 888 | if (sizeof($addresses)>0) { 889 | foreach($addresses as $a) { 890 | $add_tmp[$a['subnetId']][] = $this->transform_to_dotted($a['ip_addr']); 891 | } 892 | //add to scan_subnets as result 893 | foreach($subnets as $sk=>$s) { 894 | if(isset($add_tmp[$s->id])) { 895 | $subnets[$sk]->discovered = $add_tmp[$s->id]; 896 | } 897 | } 898 | } 899 | 900 | // return result 901 | return $subnets; 902 | } 903 | 904 | 905 | /** 906 | * Discover new host with fping - nonthreaded 907 | * 908 | * @access private 909 | * @param mixed $subnets 910 | * @return void 911 | */ 912 | private function mysql_scan_discover_hosts_fping_nonthreaded ($subnets, $addresses_tmp) { 913 | /* 914 | 915 | // run separately for each host 916 | foreach ($address as $a) { 917 | // ping 918 | $ping = fping_subnet ($this->transform_to_dotted($subnets[$z]->subnet)."/".$subnets[$z]->mask ); 919 | // check result 920 | var_dump($ping); 921 | } 922 | 923 | //run per MAX_THREADS 924 | for ($m=0; $m<=sizeof($subnets); $m += $this->config->threads) { 925 | // create threads 926 | $threads = array(); 927 | //fork processes 928 | for ($i = 0; $i <= $this->config->threads && $i <= sizeof($subnets); $i++) { 929 | //only if index exists! 930 | if(isset($subnets[$z])) { 931 | //start new thread 932 | $threads[$z] = new PingThread( 'fping_subnet' ); 933 | $threads[$z]->start_fping( $this->transform_to_dotted($subnets[$z]->subnet)."/".$subnets[$z]->mask ); 934 | $z++; //next index 935 | } 936 | } 937 | // wait for all the threads to finish 938 | while( !empty( $threads ) ) { 939 | foreach($threads as $index => $thread) { 940 | $child_pipe = "/tmp/pipe_".$thread->getPid(); 941 | 942 | if (file_exists($child_pipe)) { 943 | $file_descriptor = fopen( $child_pipe, "r"); 944 | $child_response = ""; 945 | while (!feof($file_descriptor)) { 946 | $child_response .= fread($file_descriptor, 8192); 947 | } 948 | //we have the child data in the parent, but serialized: 949 | $child_response = unserialize( $child_response ); 950 | //store 951 | $subnets[$index]->discovered = $child_response; 952 | //now, child is dead, and parent close the pipe 953 | unlink( $child_pipe ); 954 | unset($threads[$index]); 955 | } 956 | } 957 | usleep(200000); 958 | } 959 | } 960 | 961 | //fping finds all subnet addresses, we must remove existing ones ! 962 | foreach($subnets as $sk=>$s) { 963 | if (is_array($s->discovered)) { 964 | foreach($s->discovered as $rk=>$result) { 965 | if(!in_array($this->transform_to_decimal($result), $addresses_tmp[$s->id])) { 966 | unset($subnets[$sk]->discovered[$rk]); 967 | } 968 | } 969 | //rekey 970 | $subnets[$sk]->discovered = array_values($subnets[$sk]->discovered); 971 | } 972 | } 973 | */ 974 | 975 | // return result 976 | return $subnets; 977 | } 978 | 979 | /** 980 | * Discover new hosts with ping or pear - nonthreaded! 981 | * 982 | * @access private 983 | * @param mixed $subnets 984 | * @param mixed $addresses 985 | * @return $subnets 986 | */ 987 | private function mysql_scan_discover_hosts_ping_nonthreaded ($subnets, $addresses) { 988 | 989 | for ($i = 0; $i <= sizeof($addresses); $i++) { 990 | ping_address( $this->transform_to_dotted( $addresses[$i]['ip_addr']) ); 991 | } 992 | 993 | //ok, we have all available addresses, rekey them 994 | if (sizeof($addresses)>0) { 995 | foreach($addresses as $a) { 996 | $add_tmp[$a['subnetId']][] = $this->transform_to_dotted($a['ip_addr']); 997 | } 998 | //add to scan_subnets as result 999 | foreach($subnets as $sk=>$s) { 1000 | if(isset($add_tmp[$s->id])) { 1001 | $subnets[$sk]->discovered = $add_tmp[$s->id]; 1002 | } 1003 | } 1004 | } 1005 | 1006 | // return result 1007 | return $subnets; 1008 | } 1009 | 1010 | /** 1011 | * Write discovered hosts to database 1012 | * 1013 | * @access private 1014 | * @param mixed $subnets 1015 | * @return void 1016 | */ 1017 | private function mysql_scan_discovered_write_to_db ($subnets) { 1018 | # insert to database 1019 | $discovered = 0; //for mailing 1020 | 1021 | # reset db connection for ping / pear 1022 | if ($this->scan_type!=="fping") { 1023 | unset($this->Database); 1024 | $this->Database = new Database_PDO (); 1025 | } 1026 | // loop 1027 | foreach($subnets as $s) { 1028 | if(is_array($s->discovered)) { 1029 | foreach($s->discovered as $ip) { 1030 | // try to resolve hostname 1031 | $tmp = new stdClass(); 1032 | $tmp->ip_addr = $ip; 1033 | $hostname = $this->resolve_address($tmp, true); 1034 | //set update query 1035 | $values = array("subnetId"=>$s->id, 1036 | "ip_addr"=>$this->transform_address($ip, "decimal"), 1037 | "hostname"=>$hostname['name'], 1038 | "description"=>"-- autodiscovered --", 1039 | "note"=>"This host was autodiscovered on ".$this->nowdate. " by agent ".$this->agent_details->name, 1040 | "lastSeen"=>$this->nowdate, 1041 | "state"=>"2" 1042 | ); 1043 | //insert 1044 | $this->mysql_insert_address($values); 1045 | 1046 | //set discovered 1047 | $discovered++; 1048 | } 1049 | } 1050 | } 1051 | 1052 | // mail ? 1053 | if($discovered>0 && $this->config->sendmail===true) { 1054 | $this->scan_discovery_send_mail (); 1055 | } 1056 | 1057 | // ok 1058 | return true; 1059 | } 1060 | 1061 | /** 1062 | * Inserts new address to database 1063 | * 1064 | * @access private 1065 | * @param mixed $insert 1066 | * @return void 1067 | */ 1068 | private function mysql_insert_address ($insert) { 1069 | # execute 1070 | try { $this->Database->insertObject("ipaddresses", $insert); } 1071 | catch (Exception $e) { 1072 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 1073 | return false; 1074 | } 1075 | # ok 1076 | return true; 1077 | } 1078 | 1079 | /** 1080 | * Update statuses for alive hosts 1081 | * 1082 | * @access private 1083 | * @param mixed $subnets 1084 | * @return voi 1085 | */ 1086 | private function mysql_scan_update_write_to_db ($subnets) { 1087 | # reset db connection for ping / pear 1088 | if ($this->scan_type!=="fping") { 1089 | unset($this->Database); 1090 | $this->Database = new Database_PDO (); 1091 | } 1092 | // loop 1093 | foreach ($subnets as $s) { 1094 | if (is_array($s->discovered)) { 1095 | foreach ($s->discovered as $ip) { 1096 | # execute 1097 | $query = "update `ipaddresses` set `lastSeen` = ? where `subnetId` = ? and `ip_addr` = ? limit 1;"; 1098 | $vars = array($this->nowdate, $s->id, $this->transform_address($ip, "decimal")); 1099 | 1100 | try { $this->Database->runQuery($query, $vars); } 1101 | catch (Exception $e) { 1102 | $this->Result->throw_exception(500, "Error: ".$e->getMessage()); 1103 | } 1104 | } 1105 | } 1106 | } 1107 | 1108 | } 1109 | 1110 | /** 1111 | * Resets DHCP Adresses 1112 | * 1113 | * @access private 1114 | * @return void 1115 | */ 1116 | private function reset_dhcp_addresses () { 1117 | 1118 | # reset db connection 1119 | unset($this->Database); 1120 | $this->Database = new Database_PDO (); 1121 | 1122 | # Get all used DHCP addresses 1123 | $query = "SELECT `ip_addr`, `subnetId`, `lastSeen` FROM `ipaddresses` WHERE `state` = ? AND NOT `lastSeen` = ?;"; 1124 | $vars = array("4", "0000-00-00 00:00:00"); 1125 | 1126 | // fetch details 1127 | try { $DHCPAddresses = $this->Database->getObjectsQuery($query, $vars); } 1128 | catch (Exception $e) { 1129 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 1130 | } 1131 | 1132 | # Get Warning and Offline time 1133 | $query = "select `pingStatus` from `settings`;"; 1134 | 1135 | // fetch details 1136 | try { $statuses = $this->Database->getObjectsQuery($query); } 1137 | catch (Exception $e) { 1138 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 1139 | } 1140 | 1141 | # Convert stdClass Objects to arrays 1142 | $statuses = json_decode(json_encode($statuses), True); 1143 | $DHCPAddresses = json_decode(json_encode($DHCPAddresses), True); 1144 | 1145 | $statuses = explode(";", $statuses['0']['pingStatus']); 1146 | 1147 | foreach ($DHCPAddresses as $addr) { 1148 | $tDiff = time() - strtotime($addr['lastSeen']); 1149 | 1150 | if ( $tDiff > $statuses['1']) 1151 | { 1152 | $query = "UPDATE `ipaddresses` SET `lastSeen` = ?, hostname = '' WHERE `subnetId` = ? AND `ip_addr` = ? limit 1;"; 1153 | $vars = array("0000-00-00 00:00:00", $addr['subnetId'], $addr['ip_addr']); 1154 | 1155 | try { $this->Database->runQuery($query, $vars); } 1156 | catch (Exception $e) { 1157 | $this->Result->throw_exception(500, "Error: ".$e->getMessage()); 1158 | } 1159 | } 1160 | } 1161 | } 1162 | 1163 | 1164 | /** 1165 | * Resets autodiscovered Adresses 1166 | * 1167 | * @access private 1168 | * @return void 1169 | */ 1170 | private function reset_autodiscover_addresses () { 1171 | 1172 | # reset db connection 1173 | unset($this->Database); 1174 | $this->Database = new Database_PDO (); 1175 | 1176 | # Get all autodiscoverd IPs 1177 | $query = "SELECT `ip_addr`, `subnetId`, `lastSeen` FROM `ipaddresses` WHERE `description` = ? AND NOT `lastSeen` = ?;"; 1178 | $vars = array("-- autodiscovered --", "0000-00-00 00:00:00"); 1179 | 1180 | // fetch details 1181 | try { $AutoDiscAddresses = $this->Database->getObjectsQuery($query, $vars); } 1182 | catch (Exception $e) { 1183 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 1184 | } 1185 | 1186 | # Get Warning and Offline time 1187 | $query = "select `pingStatus` from `settings`;"; 1188 | 1189 | // fetch details 1190 | try { $statuses = $this->Database->getObjectsQuery($query); } 1191 | catch (Exception $e) { 1192 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 1193 | } 1194 | 1195 | # Convert stdClass Objects to arrays 1196 | $statuses = json_decode(json_encode($statuses), True); 1197 | $AutoDiscAddresses = json_decode(json_encode($AutoDiscAddresses), True); 1198 | 1199 | $statuses = explode(";", $statuses['0']['pingStatus']); 1200 | 1201 | foreach ($AutoDiscAddresses as $addr) { 1202 | $tDiff = time() - strtotime($addr['lastSeen']); 1203 | 1204 | if ( $tDiff > $statuses['1']) { 1205 | ## Delete IP 1206 | $field = "subnetId"; $value = $addr['subnetId']; 1207 | $field2 = "ip_addr"; $value2 = $addr['ip_addr']; 1208 | 1209 | try { $this->Database->deleteRow("ipaddresses", $field, $value, $field2, $value2); } 1210 | catch (Exception $e) { 1211 | $this->Result->throw_exception (500, "Error: ".$e->getMessage()); 1212 | } 1213 | } 1214 | } 1215 | } 1216 | 1217 | 1218 | /** 1219 | * @common functions for both methods 1220 | * --------------------------------- 1221 | */ 1222 | 1223 | /** 1224 | * Sets mysql field name from scan type 1225 | * 1226 | * @access private 1227 | * @return void 1228 | */ 1229 | private function get_scan_type_field () { 1230 | if ($this->scan_type == "update") { return "pingSubnet"; } 1231 | elseif ($this->scan_type == "discover") { return "discoverSubnet"; } 1232 | else { $this->Result->throw_exception (500, "Invalid scan type!"); } 1233 | } 1234 | 1235 | 1236 | private function scan_discovery_send_mail () { 1237 | 1238 | } 1239 | 1240 | } 1241 | -------------------------------------------------------------------------------- /functions/functions.php: -------------------------------------------------------------------------------- 1 | 'tcp://'.Config::ValueOf('proxy_server').':'.Config::ValueOf('proxy_port'), 15 | 'request_fulluri' => true]; 16 | 17 | if (Config::ValueOf('proxy_use_auth') == true) { 18 | $proxy_auth = base64_encode(Config::ValueOf('proxy_user').':'.Config::ValueOf('proxy_pass')); 19 | $proxy_settings['header'] = "Proxy-Authorization: Basic ".$proxy_auth; 20 | } 21 | stream_context_set_default (['http' => $proxy_settings]); 22 | 23 | /* for debugging proxy config uncomment next line */ 24 | // var_dump(stream_context_get_options(stream_context_get_default())); 25 | } 26 | 27 | /* global and missing functions */ 28 | require('global_functions.php'); 29 | 30 | /* @http only cookies ------------------- */ 31 | if(php_sapi_name()!="cli") 32 | ini_set('session.cookie_httponly', 1); 33 | 34 | /* @debugging functions ------------------- */ 35 | if(Config::ValueOf('debugging')==true) { 36 | ini_set('display_errors', 1); 37 | ini_set('display_startup_errors', 1); 38 | error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT); 39 | } 40 | else { 41 | ini_set('display_errors', 0); 42 | ini_set('display_startup_errors', 0); 43 | error_reporting(E_ERROR ^ E_WARNING); 44 | } 45 | 46 | // Fix JSON_UNESCAPED_UNICODE for PHP 5.3 47 | defined('JSON_UNESCAPED_UNICODE') or define('JSON_UNESCAPED_UNICODE', 256); 48 | 49 | /* @classes ---------------------- */ 50 | 51 | require( dirname(__FILE__) . '/classes/class.Common.php' ); 52 | require( dirname(__FILE__) . '/classes/class.PDO.php' ); 53 | require( dirname(__FILE__) . '/classes/class.Log.php' ); 54 | require( dirname(__FILE__) . '/classes/class.Addresses.php' ); 55 | require( dirname(__FILE__) . '/classes/class.Subnets.php' ); 56 | require( dirname(__FILE__) . '/classes/class.Result.php' ); 57 | require( dirname(__FILE__) . '/classes/class.Mail.php' ); 58 | require( dirname(__FILE__) . '/classes/class.Scan.php' ); 59 | require( dirname(__FILE__) . '/classes/class.Thread.php' ); 60 | require( dirname(__FILE__) . '/classes/class.phpipamAgent.php' ); 61 | 62 | /* get version */ 63 | include('version.php'); 64 | -------------------------------------------------------------------------------- /functions/global_functions.php: -------------------------------------------------------------------------------- 1 | = 5.6.0 13 | * A timing safe equals comparison more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html. 14 | */ 15 | if(!function_exists('hash_equals')) { 16 | function hash_equals($safeString, $userString) { 17 | $safeLen = strlen($safeString); 18 | $userLen = strlen($userString); 19 | 20 | if ($userLen != $safeLen) { return false; } 21 | 22 | $result = 0; 23 | for ($i = 0; $i < $userLen; ++$i) { 24 | $result |= (ord($safeString[$i]) ^ ord($userString[$i])); 25 | } 26 | // They are only identical strings if $result is exactly 0... 27 | return $result === 0; 28 | } 29 | } 30 | 31 | /** 32 | * Supported in PHP 5 >= 5.5.0 33 | * For older php versions make sure that function "json_last_error_msg" exist and create it if not 34 | */ 35 | if (!function_exists('json_last_error_msg')) { 36 | function json_last_error_msg() { 37 | static $ERRORS = [ 38 | JSON_ERROR_NONE => 'No error', 39 | JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', 40 | JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', 41 | JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', 42 | JSON_ERROR_SYNTAX => 'Syntax error', 43 | JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded' 44 | ]; 45 | 46 | $error = json_last_error(); 47 | return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error'; 48 | } 49 | } 50 | 51 | /** 52 | * create links function 53 | * 54 | * if rewrite is enabled in settings use rewrite, otherwise ugly links 55 | * 56 | * levels: $el 57 | */ 58 | function create_link ($l0 = null, $l1 = null, $l2 = null, $l3 = null, $l4 = null, $l5 = null, $l6 = null ) { 59 | # get settings 60 | global $User; 61 | 62 | $parts = []; 63 | for($i=0; $i<=6; $i++) { 64 | if (is_null(${"l$i"})) continue; 65 | 66 | foreach(explode('/', ${"l$i"}) as $p) { 67 | // url encode all 68 | $parts[] = urlencode($p); 69 | } 70 | } 71 | 72 | if (empty($parts)) 73 | return BASE; 74 | 75 | # Pretty Links 76 | if($User->settings->prettyLinks=="Yes") { 77 | $link = BASE.implode('/', $parts); 78 | 79 | # IP search fix 80 | if (!is_null($parts[6]) || ($parts[0]=="tools" && $parts[1]=="search" && isset($parts[2]))) 81 | return $link; 82 | 83 | return $link.'/'; 84 | } 85 | 86 | # Normal links 87 | $el = array("page", "section", "subnetId", "sPage", "ipaddrid", "tab"); 88 | // override for search 89 | if ($l0=="tools" && $l1=="search") 90 | $el = array("page", "section", "ip", "addresses", "subnets", "vlans", "ip"); 91 | 92 | foreach($parts as $i=>$p) { 93 | $parts[$i] = "$el[$i]=$p"; 94 | } 95 | 96 | return BASE."index.php?".implode('&', $parts); 97 | } 98 | 99 | /** 100 | * Escape HTML and quotes in user provided input 101 | * @param mixed $data 102 | * @return string 103 | */ 104 | function escape_input($data) { 105 | return (!isset($data) || strlen($data)==0) ? '' : htmlentities($data, ENT_QUOTES); 106 | } 107 | 108 | /** 109 | * Check if required php features are missing 110 | * @param mixed $required_extensions 111 | * @param mixed $required_functions 112 | * @return string|bool 113 | */ 114 | function php_feature_missing($required_extensions = null, $required_functions = null) { 115 | 116 | if (is_array($required_extensions)) { 117 | foreach ($required_extensions as $ext) { 118 | if (extension_loaded($ext)) 119 | continue; 120 | 121 | return _('Required PHP extension not installed: ').$ext; 122 | } 123 | } 124 | 125 | if (is_array($required_functions)) { 126 | foreach ($required_functions as $function) { 127 | if (function_exists($function)) 128 | continue; 129 | 130 | $ini_path = trim( php_ini_loaded_file() ); 131 | $disabled_functions = ini_get('disable_functions'); 132 | if (is_string($disabled_functions) && in_array($function, explode(';',$disabled_functions))) 133 | return _('Required function disabled')." : $ini_path, disable_functions=$function"; 134 | 135 | return _('Required function not found: ').$function.'()'; 136 | } 137 | } 138 | 139 | return false; 140 | } -------------------------------------------------------------------------------- /functions/version.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | connect (); 28 | // new scan object 29 | $Scan = new Scan ($Database); 30 | $Scan->ping_set_exit(true); 31 | $Scan->set_debugging( Config::ValueOf('debugging', false) ); 32 | } 33 | else { 34 | // scan without DB connection 35 | $Database = false; 36 | } 37 | 38 | // initialize and make default checks 39 | $phpipam_agent = new phpipamAgent ($Database); 40 | 41 | // set scan type - status update (update) or discover, must be provided via argv[1] 42 | $phpipam_agent->set_scan_type ($argv[1]); 43 | 44 | // execute 45 | $phpipam_agent->execute (); 46 | 47 | // update scan time 48 | $phpipam_agent->update_agent_scantime (); 49 | } 50 | //catch any exceptions and report the problem 51 | catch ( Exception $e ) { 52 | print "--------------------\n"; 53 | print $e->getMessage()."\n"; 54 | print "--------------------\n"; 55 | // die 56 | die(); 57 | } 58 | ?> 59 | -------------------------------------------------------------------------------- /misc/CHANGELOG: -------------------------------------------------------------------------------- 1 | == 0.02 2 | 3 | Changes 4 | ---------------------------- 5 | PHPMailer moved to git submodule. After updating to 0.02 run "git submodule update --init --recursive" 6 | 7 | Bugfixes: 8 | ---------------------------- 9 | + Synchronized bugfixed library code with https://github.com/phpipam/phpipam 10 | + php7.x compatibility fixes. 11 | 12 | == 0.01.004 13 | 14 | Bugfixes: 15 | ---------------------------- 16 | + update last access time; 17 | + fixed scan path ignored; 18 | + fixed /20 limit for scanning; 19 | 20 | == 0.01.001 21 | 22 | Initial release: 23 | ------------ 24 | + mysql agent only; -------------------------------------------------------------------------------- /misc/gpl-3.0.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . --------------------------------------------------------------------------------