#!/usr/bin/env php 1) { if (CACTI_CONNECTION == 'online') { db_force_remote_cnn(); } elseif (debounce_run_notification('db_offline')) { cacti_log(sprintf('WARNING: Main Cacti database %s offline or in recovery. Can not run automation', $rdatabase_hostname), true, 'AUTOM8'); admin_email(__('Cacti System Warning'), __('WARNING: Main Cacti database %s offline or in recovery', $rdatabase_hostname)); exit(1); } } /** * sig_handler - provides a generic means to catch exceptions to the Cacti log. * * @param int $signo - the signal that was thrown by the interface. * * @return void */ function sig_handler($signo) : void { global $network_id, $thread, $master, $poller_id; switch ($signo) { case SIGTERM: case SIGINT: if ($thread > 0) { clearTask($network_id, (int) getmypid()); exit(0); } if ($thread == 0 && !$master) { $pids = array_rekey(db_fetch_assoc_prepared("SELECT pid FROM automation_processes WHERE network_id = ? AND task!='tmaster'", [$network_id]), 'pid', 'pid'); if (cacti_sizeof($pids)) { foreach ($pids as $pid) { cacti_process_kill((int) $pid, SIGTERM, 'AUTOM8'); } } clearTask($network_id, (int) getmypid()); sleep(5); db_execute_prepared('DELETE FROM automation_ips WHERE network_id = ?', [$network_id]); } else { $pids = array_rekey(db_fetch_assoc_prepared("SELECT pid FROM automation_processes WHERE poller_id = ? AND task='tmaster'", [$poller_id]), 'pid', 'pid'); if (cacti_sizeof($pids)) { foreach ($pids as $pid) { cacti_process_kill((int) $pid, SIGTERM, 'AUTOM8'); } } clearTask($network_id, (int) getmypid()); } exit(0); default: // ignore all other signals } } // take time and log performance data $start = microtime(true); // Unix Timestamp for Database $startTime = time(); // let PHP run just as long as it has to ini_set('max_execution_time', '0'); $dir = __DIR__; chdir($dir); // process calling arguments $parms = $_SERVER['argv']; array_shift($parms); $debug = false; $force = false; $dryrun = false; $network_id = 0; $poller_id = POLLER_ID; $thread = 0; $master = false; $serial_scans = read_config_option('automation_serial_scans') == 'on' ? true : false; global $debug, $poller_id, $network_id, $thread, $master, $dryrun; if (cacti_sizeof($parms)) { foreach ($parms as $parameter) { if (str_contains($parameter, '=')) { [$arg, $value] = explode('=', $parameter, 2); } else { $arg = $parameter; $value = ''; } switch ($arg) { case '-d': case '--debug': $debug = true; break; case '--dryrun': $dryrun = true; break; case '-M': case '--master': $master = true; break; case '--poller': $poller_id = $value; break; case '-f': case '--force': $force = true; break; case '--network': $network_id = $value; break; case '--thread': $thread = $value; break; case '-v': case '--version': display_version(); exit(0); case '-h': case '--help': display_help(); exit(0); default: print 'ERROR: Invalid Parameter ' . $parameter . "\n\n"; display_help(); exit(1); } } } // install signal handlers for UNIX only if (function_exists('pcntl_signal')) { pcntl_signal(SIGTERM, 'sig_handler'); pcntl_signal(SIGINT, 'sig_handler'); } // Let's ensure that we were called correctly if (!$master && !$network_id) { print "FATAL: You must specify -M to Start the Master Control Process, or the Network ID using --network\n"; exit(1); } // Simple check for a disabled network if (!$master && $thread == 0) { $status = db_fetch_cell_prepared('SELECT enabled FROM automation_networks WHERE id = ? AND poller_id = ?', [$network_id, $poller_id]); if ($status != 'on' && !$force) { cacti_log(automation_get_pid() . " WARNING: The Network ID: $network_id is disabled. You must use the 'force' option to force its execution.", true, 'AUTOM8'); exit(1); } } if ($master) { if (!db_table_exists('automation_networks')) { exit(0); } $networks = db_fetch_assoc_prepared('SELECT * FROM automation_networks WHERE poller_id = ?', [$poller_id]); $launched = 0; if (cacti_sizeof($networks)) { foreach ($networks as $network) { if ($network['snmp_id'] == 0) { cacti_log("ERROR: Automation can not run for Network '" . $network['name'] . "' since the SNMP ID is not set.", true, 'AUTOM8'); continue; } if ($serial_scans && countCurrentTasks() > 0) { automation_debug('Serial automation enabled and an existing automation scan is in process.'); exit(0); } if (api_scheduler_is_time_to_start($network) || $force) { automation_debug("Launching Network Master for '" . $network['name'] . "'\n"); exec_background(read_config_option('path_php_binary'), '-q ' . read_config_option('path_webroot') . '/poller_automation.php --poller=' . $poller_id . ' --network=' . $network['id'] . ($force ? ' --force' : '') . ($debug ? ' --debug' : '')); $launched++; if ($serial_scans) { automation_debug('Serial automation enabled. No other discoveries started this pass.'); break; } } else { automation_debug("Not time to Run Discovery for '" . $network['name'] . "'\n"); } } } exit(0); } // Check for Network Master if ($master === false && $thread == 0) { automation_debug("Thread master about to launch collector threads\n"); // Remove any stale entries $pids = array_rekey( db_fetch_assoc_prepared('SELECT pid FROM automation_processes WHERE network_id = ?', [$network_id]), 'pid', 'pid' ); automation_debug("Killing any prior running threads\n"); if (cacti_sizeof($pids)) { foreach ($pids as $pid) { if (isProcessRunning($pid)) { killProcess($pid); cacti_log("WARNING: Automation Process $pid is still running for Network ID: $network_id", true, 'AUTOM8'); } else { cacti_log("WARNING: Process $pid claims to be running but not found for Network ID: $network_id", true, 'AUTOM8'); } } } automation_debug("Removing any orphan entries\n"); db_execute_prepared('DELETE FROM automation_ips WHERE network_id = ?', [$network_id]); db_execute_prepared('DELETE FROM automation_processes WHERE network_id = ?', [$network_id]); registerTask($network_id, getmypid(), $poller_id, 'tmaster'); cacti_log(automation_get_pid() . " Network Discover is now running for Subnet Range '$network_id'", true, 'AUTOM8'); $preexisting_devices = getNetworkDevices($network_id); automation_primeIPAddressTable($network_id); $threads = db_fetch_cell_prepared('SELECT threads FROM automation_networks WHERE id = ?', [$network_id]); if ($threads <= 0) { $threads = 1; } automation_debug("Automation will use $threads Threads\n"); db_execute_prepared('UPDATE automation_networks SET last_started = ? WHERE id = ?', [date('Y-m-d H:i:s', $startTime), $network_id]); $curthread = 1; while ($curthread <= $threads) { automation_debug("Launching Thread $curthread\n"); exec_background(read_config_option('path_php_binary'), '-q ' . read_config_option('path_webroot') . '/poller_automation.php --poller=' . $poller_id . " --thread=$curthread --network=$network_id" . ($force ? ' --force' : '') . ($debug ? ' --debug' : '')); $curthread++; } sleep(5); automation_debug("Checking for Running Threads\n"); $failcount = 0; while (true) { $command = db_fetch_cell_prepared('SELECT command FROM automation_processes WHERE network_id = ? AND task="tmaster"', [$network_id]); if ($command == 'cancel') { /* Terminating ourselves, so exit rather than depend on the signal arriving: the sibling cancel path below already does, and without it a refused or lost signal leaves this while (true) re-querying for ever and the cancelled discovery never stops. */ killProcess(getmypid()); exit(0); } $running = db_fetch_cell_prepared('SELECT count(*) FROM automation_processes WHERE network_id = ? AND task!="tmaster" AND status="running"', [$network_id]); automation_debug("Found $running Threads"); // Are there no more running tasks? Wait up to 15 seconds to // allow processes to start before checking for failures if (($running == 0 && $failcount > 3) || $command == 'cancel') { db_execute_prepared('DELETE FROM automation_ips WHERE network_id = ?', [$network_id]); $totals = db_fetch_row_prepared('SELECT SUM(up_hosts) AS up, SUM(snmp_hosts) AS snmp FROM automation_processes WHERE network_id = ?', [$network_id]); // take time and log performance data $end = microtime(true); db_execute_prepared('UPDATE automation_networks SET up_hosts = ?, snmp_hosts = ?, last_started = ?, last_runtime = ? WHERE id = ?', [$totals['up'], $totals['snmp'], date('Y-m-d H:i:s', $startTime), ($end - $start), $network_id]); clearAllTasks($network_id); reportNetworkStatus($network_id, $preexisting_devices); exit(0); } else { $failcount++; } sleep(5); } } else { registerTask($network_id, getmypid(), $poller_id); discoverDevices($network_id, $thread); endTask($network_id, getmypid()); } exit(0); function discoverDevices(int $network_id, int $thread) : bool { global $dryrun; $network = db_fetch_row_prepared('SELECT * FROM automation_networks WHERE id = ?', [$network_id]); $dns = trim($network['dns_servers']); // Let's do some stats! $stats = []; $stats['scanned'] = 0; $stats['ping'] = 0; $stats['snmp'] = 0; $stats['added'] = 0; $count_graph = 0; $count = 0; while (true) { // Check for cancel $command = db_fetch_cell_prepared('SELECT command FROM automation_processes WHERE network_id = ? AND task = "tmaster"', [$network_id]); if ($command == 'cancel' || empty($command)) { removeMyProcess(getmypid(), $network_id); killProcess(getmypid()); exit(0); } // set and ip to be scanned db_execute_prepared('UPDATE automation_ips SET pid = ?, thread = ? WHERE network_id = ? AND status = 0 AND pid = 0 LIMIT 1', [getmypid(), $thread, $network_id]); $device = db_fetch_row_prepared('SELECT * FROM automation_ips WHERE pid = ? AND thread = ? AND status=0', [getmypid(), $thread]); if (cacti_sizeof($device) && isset($device['ip_address'])) { $count++; cacti_log(automation_get_pid() . ' NOTE: Found device IP address \'' . $device['ip_address'] . '\' to check', false, 'AUTOM8', POLLER_VERBOSITY_MEDIUM); if (!filter_var($device['ip_address'], FILTER_VALIDATE_IP)) { cacti_log(automation_get_pid() . ' WARNING: IP address \'' . $device['ip_address'] . '\' is not a valid IP address.', false, 'AUTOM8'); markIPDone($device['ip_address'], $network_id); continue; } if ($dns != '') { $dnsname = automation_get_dns_from_ip($device['ip_address'], $dns, 1000); if ($dnsname != $device['ip_address'] && $dnsname != 'timed_out' && $dnsname != 'ERROR') { automation_debug('Device: ' . $device['ip_address'] . ", Checking DNS: Found '" . $dnsname . "'"); db_execute_prepared('UPDATE automation_ips SET hostname = ? WHERE ip_address = ?', [$dnsname, $device['ip_address']]); $device['hostname'] = $dnsname; $device['dnsname'] = $dnsname; $device['dnsname_short'] = explode('.', cacti_strtolower($dnsname))[0]; } elseif ($network['enable_netbios'] == 'on') { automation_debug('Device: ' . $device['ip_address'] . ', Checking DNS: Not found, Checking NetBIOS:'); $netbios = ping_netbios_name($device['ip_address']); if ($netbios === false) { automation_debug(' Not found'); $device['hostname'] = $device['ip_address']; $device['dnsname'] = ''; $device['dnsname_short'] = ''; } else { automation_debug(" Found: '" . $netbios . "'"); db_execute_prepared('UPDATE automation_ips SET hostname = ? WHERE ip_address = ?', [$device['hostname'], $device['ip_address']]); $device['dnsname'] = $netbios; $device['dnsname_short'] = $netbios; } } else { automation_debug('Device: ' . $device['ip_address'] . ', Checking DNS: Not found'); $device['hostname'] = $device['ip_address']; $device['dnsname'] = ''; $device['dnsname_short'] = ''; } } else { $dnsname = gethostbyaddr($device['ip_address']); $device['hostname'] = $dnsname; if ($dnsname != $device['ip_address']) { automation_debug('Device: ' . $device['ip_address'] . ", Checking DNS: Found '" . $dnsname . "'"); db_execute_prepared('UPDATE automation_ips SET hostname = ? WHERE ip_address = ?', [$dnsname, $device['ip_address']]); $device['dnsname'] = $dnsname; $device['dnsname_short'] = explode('.', cacti_strtolower((string) $dnsname))[0]; } elseif ($network['enable_netbios'] == 'on') { automation_debug('Device: ' . $device['ip_address'] . ', Checking DNS: Not found, Checking NetBIOS:'); $netbios = ping_netbios_name($device['ip_address']); if ($netbios === false) { automation_debug(' Not found'); $device['hostname'] = $device['ip_address']; $device['dnsname'] = ''; $device['dnsname_short'] = ''; } else { automation_debug(" Found: '" . $netbios . "'"); db_execute_prepared('UPDATE automation_ips SET hostname = ? WHERE ip_address = ?', [$device['hostname'], $device['ip_address']]); $device['dnsname'] = $netbios; $device['dnsname_short'] = $netbios; } } else { automation_debug('Device: ' . $device['ip_address'] . ', Checking DNS: Not found'); $device['hostname'] = $device['ip_address']; $device['dnsname'] = ''; $device['dnsname_short'] = ''; } } $exists = db_fetch_row_prepared('SELECT id, snmp_version, status, deleted FROM host WHERE hostname IN (?,?)', [$device['ip_address'], $device['hostname']]); if (!cacti_sizeof($exists)) { automation_debug(', Status: Not in Cacti'); if (substr($device['ip_address'], -3) < 255) { automation_debug(', Ping: '); // Set status to running markIPRunning($device['ip_address'], $network_id); $stats['scanned']++; $device['snmp_status'] = 0; $device['ping_status'] = 0; $device['snmp_id'] = $network['snmp_id']; $device['poller_id'] = $network['poller_id']; $device['site_id'] = $network['site_id']; $device['snmp_version'] = '2'; $device['snmp_port'] = ''; $device['snmp_community'] = ''; $device['snmp_username'] = ''; $device['snmp_password'] = ''; $device['snmp_auth_protocol'] = ''; $device['snmp_auth_passphrase'] = ''; $device['snmp_auth_protocol'] = ''; $device['snmp_context'] = ''; $device['snmp_port'] = ''; $device['snmp_timeout'] = ''; $device['snmp_sysDescr'] = ''; $device['snmp_sysObjectID'] = ''; $device['snmp_sysUptime'] = 0; $device['snmp_sysName'] = ''; $device['snmp_sysName_short'] = ''; $device['snmp_sysLocation'] = ''; $device['snmp_sysContact'] = ''; $device['os'] = ''; $device['snmp_priv_passphrase'] = ''; $device['snmp_priv_protocol'] = ''; $device['max_oids'] = '10'; $device['bulk_walk_size'] = '-1'; $device['snmp_options'] = '0'; $device['snmp_retries'] = '3'; // create new ping socket for host pinging $ping = new Net_Ping; $ping->host['hostname'] = $device['ip_address']; $ping->retries = $network['ping_retries']; $ping->port = $network['ping_port']; // perform the appropriate ping check of the host $bypass_ping = false; $result = false; if ($network['ping_method'] == PING_SNMP) { $bypass_ping = true; } if ($bypass_ping == false) { $result = $ping->ping(AVAIL_PING, $network['ping_method'], $network['ping_timeout'], 1); if (!$result) { automation_debug(' No response'); updateDownDevice($network_id, $device['ip_address']); } else { automation_debug(' Responded'); $stats['ping']++; addUpDevice($network_id, (int) getmypid()); } } if (($result || $bypass_ping) && automation_valid_snmp_device($device)) { $snmp_sysName = trim($device['snmp_sysName']); $snmp_sysName_short = ''; if (!is_ipaddress($snmp_sysName)) { $parts = explode('.', $snmp_sysName); foreach ($parts as $part) { if (is_numeric($part)) { $snmp_sysName_short = $snmp_sysName; break; } } if ($snmp_sysName_short == '') { $snmp_sysName_short = $parts[0]; } } else { $snmp_sysName_short = $snmp_sysName; } $exists = db_fetch_row_prepared('SELECT id, status, snmp_version, deleted FROM host WHERE hostname IN (?,?)', [$snmp_sysName_short, $snmp_sysName]); if (cacti_sizeof($exists)) { if ($exists['deleted'] != 'on') { if ($exists['status'] == 3 || $exists['status'] == 2) { addUpDevice($network_id, (int) getmypid()); if ($exists['snmp_version'] > 0) { addSNMPDevice($network_id, (int) getmypid()); } // Rerun data queries if specified rerunDataQueries($exists['id'], $network); } automation_debug(' Device is in Cacti!'); } else { automation_debug(' Device is in Cacti but marked as deleted!'); } markIPDone($device['ip_address'], $network_id); } else { $host_id = 0; if ($snmp_sysName != '') { $hostname = gethostbyaddr($device['ip_address']); if ($hostname != $device['ip_address']) { if (str_contains((string) $hostname, '.')) { $hostname = substr((string) $hostname, 0, strpos((string) $hostname, '.') - 1); } } $isCactiSysName = db_fetch_cell_prepared('SELECT COUNT(*) FROM host WHERE snmp_sysName = ? AND (hostname = ? OR hostname LIKE ?)', [$snmp_sysName, $device['ip_address'], db_like_escape($hostname) . '%']); if ($isCactiSysName) { automation_debug(", Skipping sysName '" . $snmp_sysName . "' already in Cacti!\n"); markIPDone($device['ip_address'], $network_id); continue; } if ($network['same_sysname'] == '') { $isDuplicateSysNameDiscovery = db_fetch_cell_prepared('SELECT COUNT(*) FROM automation_devices WHERE network_id = ? AND sysName != "" AND ip != ? AND sysName = ?', [$network_id, $device['ip_address'], $snmp_sysName]); $isDuplicateSysNameCacti = db_fetch_cell_prepared('SELECT COUNT(*) FROM host WHERE snmp_sysName = ? AND hostname != ?', [$snmp_sysName, $device['ip_address']]); if ($isDuplicateSysNameDiscovery || $isDuplicateSysNameCacti) { automation_debug(", Skipping sysName '" . $snmp_sysName . "' already Discovered!\n"); markIPDone($device['ip_address'], $network_id); continue; } } $stats['snmp']++; addSNMPDevice($network_id, (int) getmypid()); automation_debug(' Responded'); $fos = automation_find_os($device['snmp_sysDescr'], $device['snmp_sysObjectID'], $device['snmp_sysName']); if (is_array($fos) && $network['add_to_cacti'] == 'on' && $dryrun == false) { automation_debug(', Template: ' . $fos['name'] . "\n"); $device['os'] = $fos['name']; $device['host_template'] = $fos['host_template']; $device['availability_method'] = $fos['availability_method']; if ($fos['populate_location'] == 'on') { $device['location'] = $device['snmp_sysLocation']; } if ($fos['description_pattern'] != '') { $sysName = $device['snmp_sysName']; $ip_address = $device['ip_address']; $dnsname = $device['dnsname']; $shortname = $device['dnsname_short']; $sysLocation = $device['snmp_sysLocation']; if ($sysName != '') { $pattern = str_replace('|sysName|', $sysName, $fos['description_pattern']); } else { $pattern = $fos['description_pattern']; } if ($ip_address != '') { $pattern = str_replace('|ipAddress|', $ip_address, $pattern); } if ($dnsname != '') { $pattern = str_replace('|dnsName|', $dnsname, $pattern); } if ($shortname != '') { $pattern = str_replace('|dnsShortName|', $shortname, $pattern); } if ($sysLocation != '') { $pattern = str_replace('|sysLocation|', $sysLocation, $pattern); } $description = db_fetch_cell_prepared('SELECT ?', [$pattern]); if ($description != '') { $device['description'] = $description; } } $host_id = automation_add_device($device); if (!empty($host_id)) { if (isset($device['snmp_sysDescr']) && $device['snmp_sysDescr'] != '') { db_execute_prepared('UPDATE host SET snmp_sysDescr = ? WHERE id = ?', [$device['snmp_sysDescr'], $host_id]); } if (isset($device['snmp_sysObjectID']) && $device['snmp_sysObjectID'] != '') { db_execute_prepared('UPDATE host SET snmp_sysObjectID = ? WHERE id = ?', [$device['snmp_sysObjectID'], $host_id]); } if (isset($device['snmp_sysUptime']) && $device['snmp_sysUptime'] != '') { db_execute_prepared('UPDATE host SET snmp_sysUptimeInstance = ? WHERE id = ?', [$device['snmp_sysUptime'], $host_id]); } if (isset($device['snmp_sysContact']) && $device['snmp_sysContact'] != '') { db_execute_prepared('UPDATE host SET snmp_sysContact = ? WHERE id = ?', [$device['snmp_sysContact'], $host_id]); } if (isset($device['snmp_sysName']) && $device['snmp_sysName'] != '') { db_execute_prepared('UPDATE host SET snmp_sysName = ? WHERE id = ?', [$device['snmp_sysName'], $host_id]); } if (isset($device['snmp_sysLocation']) && $device['snmp_sysLocation'] != '') { db_execute_prepared('UPDATE host SET snmp_sysLocation = ? WHERE id = ?', [$device['snmp_sysLocation'], $host_id]); } automation_update_device($host_id); } $stats['added']++; } elseif ($fos == false) { automation_debug(", Template: Not found, Not adding to Cacti\n"); } elseif ($dryrun) { automation_debug(", Not adding to Cacti - Dryrun Mode\n"); } else { automation_debug(', Template: ' . $fos['name']); $device['os'] = $fos['name']; automation_debug(", Skipped: Add to Cacti disabled\n"); } } if ($host_id > 0) { db_execute_prepared('DELETE FROM automation_devices WHERE host_id = ?', [$host_id]); } // if the devices template is not discovered, add to found table db_execute('REPLACE INTO automation_devices (network_id, host_id, hostname, ip, snmp_community, snmp_version, snmp_port, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, sysName, sysLocation, sysContact, sysDescr, sysUptime, os, snmp, up, time) VALUES (' . $network_id . ', ' . $host_id . ', ' . db_qstr($device['dnsname']) . ', ' . db_qstr($device['ip_address']) . ', ' . db_qstr($device['snmp_community']) . ', ' . db_qstr($device['snmp_version']) . ', ' . db_qstr($device['snmp_port']) . ', ' . db_qstr($device['snmp_username']) . ', ' . db_qstr($device['snmp_password']) . ', ' . db_qstr($device['snmp_auth_protocol']) . ', ' . db_qstr($device['snmp_priv_passphrase']) . ', ' . db_qstr($device['snmp_priv_protocol']) . ', ' . db_qstr($device['snmp_context']) . ', ' . db_qstr($device['snmp_sysName']) . ', ' . db_qstr($device['snmp_sysLocation']) . ', ' . db_qstr($device['snmp_sysContact']) . ', ' . db_qstr($device['snmp_sysDescr']) . ', ' . db_qstr($device['snmp_sysUptime']) . ', ' . db_qstr($device['os']) . ', ' . '1, 1,' . time() . ')'); markIPDone($device['ip_address'], $network_id); } } elseif ($result) { db_execute('REPLACE INTO automation_devices (network_id, hostname, ip, snmp_community, snmp_version, snmp_port, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, sysName, sysLocation, sysContact, sysDescr, sysUptime, os, snmp, up, time) VALUES (' . $network_id . ', ' . db_qstr($device['dnsname']) . ', ' . db_qstr($device['ip_address']) . ', ' . db_qstr($device['snmp_community']) . ', ' . db_qstr($device['snmp_version']) . ', ' . db_qstr($device['snmp_port']) . ', ' . db_qstr($device['snmp_username']) . ', ' . db_qstr($device['snmp_password']) . ', ' . db_qstr($device['snmp_auth_protocol']) . ', ' . db_qstr($device['snmp_priv_passphrase']) . ', ' . db_qstr($device['snmp_priv_protocol']) . ', ' . db_qstr($device['snmp_context']) . ', ' . db_qstr($device['snmp_sysName']) . ', ' . db_qstr($device['snmp_sysLocation']) . ', ' . db_qstr($device['snmp_sysContact']) . ', ' . db_qstr($device['snmp_sysDescr']) . ', ' . db_qstr($device['snmp_sysUptime']) . ', ' . '"", 0, 1,' . time() . ')'); automation_debug(', Alive no SNMP!'); markIPDone($device['ip_address'], $network_id); } else { markIPDone($device['ip_address'], $network_id); } automation_debug("\n"); } else { automation_debug(", Status: Ignoring Address (PHP Bug does not allow us to ping .255 as it thinks its a broadcast IP)!\n"); markIPDone($device['ip_address'], $network_id); } } else { if ($exists['deleted'] != 'on') { if ($exists['status'] == 3 || $exists['status'] == 2) { addUpDevice($network_id, (int) getmypid()); if ($exists['snmp_version'] > 0) { addSNMPDevice($network_id, (int) getmypid()); } // Rerun data queries if specified rerunDataQueries($exists['id'], $network); } automation_debug(", Status: Already in Cacti\n"); } else { automation_debug(", Status: Already in Cacti but marked as deleted\n"); } markIPDone($device['ip_address'], $network_id); } } else { // no more ips to scan break; } } cacti_log(automation_get_pid() . ' Network ' . $network['name'] . " Thread $thread Finished, " . $stats['scanned'] . ' IPs Scanned, ' . $stats['ping'] . ' IPs Responded to Ping, ' . $stats['snmp'] . ' Responded to SNMP, ' . $stats['added'] . ' Device Added, ' . $count_graph . ' Graphs Added to Cacti', true, 'AUTOM8'); return true; } /** * display_version - displays version information * * @return void */ function display_version() : void { $version = get_cacti_cli_version(); print "Cacti Network Discovery Scanner, Version $version, " . COPYRIGHT_YEARS . "\n"; } /** * display_help - displays the usage of the function * * @return void */ function display_help() : void { display_version(); print "\nusage: poller_automation.php -M [--poller=ID] | --network=network_id [-T=thread_id]\n"; print " [--debug] [--force]\n\n"; print "Cacti's automation poller. This poller has two operating modes, master and worker.\n"; print "The master process tracks and launches all workers based upon Cacti's automation\n"; print "settings. If you only want to force a network to be collected, you only need to\n"; print "specify the Network ID and the force options.\n\n"; print "Master Process:\n"; print " -M | --master - Master poller for all Automation\n"; print " --poller=ID - Master Poller ID, Defaults to 0 or WebServer\n\n"; print "Network Masters and Workers:\n"; print " --network=n - Network ID to discover\n"; print " --thread=n - Thread ID, Defaults to 0 or Network Master\n\n"; print "General Options:\n"; print " --force - Force the execution of a discovery process\n"; print " --debug - Display verbose output during execution\n\n"; } function isProcessRunning(int $pid) : bool { return cacti_process_pid_exists($pid); } function killProcess(int $pid) : bool { return cacti_process_kill((int) $pid, SIGTERM, 'AUTOM8'); } function removeMyProcess(int $pid, int $network_id) : void { db_execute_prepared('DELETE FROM automation_processes WHERE pid = ? AND network_id = ?', [$pid, $network_id]); db_execute_prepared('DELETE FROM automation_ips WHERE pid = ? AND network_id = ?', [$pid, $network_id]); } function rerunDataQueries(int $host_id, array &$network) : void { if ($network['rerun_data_queries'] == 'on') { $snmp_queries = db_fetch_assoc_prepared('SELECT snmp_query_id FROM host_snmp_query WHERE host_id = ?', [$host_id]); if (cacti_sizeof($snmp_queries)) { foreach ($snmp_queries as $query) { run_data_query($host_id, $query['snmp_query_id']); } } } } function registerTask(int $network_id, int $pid, int $poller_id, string $task = 'collector') : void { db_execute_prepared("REPLACE INTO automation_processes (pid, poller_id, network_id, task, status, heartbeat, command) VALUES (?, ?, ?, ?, 'running', NOW(), 'start')", [$pid, $poller_id, $network_id, $task]); } function endTask(int $network_id, int $pid) : void { db_execute_prepared("UPDATE automation_processes SET status='done', heartbeat=NOW() WHERE pid = ? AND network_id = ?", [$pid, $network_id]); } function countCurrentTasks() : mixed { return db_fetch_cell('SELECT COUNT(*) FROM automation_processes'); } function addUpDevice(int $network_id, int $pid) : void { db_execute_prepared('UPDATE automation_processes SET up_hosts=up_hosts+1, heartbeat=NOW() WHERE pid = ? AND network_id = ?', [$pid, $network_id]); } function addSNMPDevice(int $network_id, int $pid) : void { db_execute_prepared('UPDATE automation_processes SET snmp_hosts=snmp_hosts+1, heartbeat=NOW() WHERE pid = ? AND network_id = ?', [$pid, $network_id]); } function reportNetworkStatus(int $network_id, array $old_devices) : bool { $details = db_fetch_row_prepared('SELECT notification_enabled, notification_email, notification_fromname, notification_fromemail FROM automation_networks WHERE id = ?', [$network_id]); if (cacti_sizeof($details)) { if ($details['notification_enabled'] == 'on') { if ($details['notification_fromname'] == '') { $fromname = read_config_option('automation_fromname'); if ($fromname == '') { $fromname = read_config_option('settings_from_name'); if ($fromname == '') { $fromname = __('Cacti Primary Admin'); } } } else { $fromname = $details['notification_fromname']; } if ($details['notification_fromemail'] == '') { $fromemail = read_config_option('automation_fromemail'); if ($fromemail == '') { $fromemail = read_config_option('settings_from_email'); if ($fromemail == '') { $fromemail = 'root@cacti.net'; } } } else { $fromemail = $details['notification_fromemail']; } $from = $fromname . ' <' . $fromemail . '>'; if ($details['notification_email'] != '') { $email = $details['notification_email']; } else { $email = read_config_option('automation_email'); if ($email == '') { $admin_user = read_config_option('admin_user'); if ($admin_user == '') { cacti_log('WARNING: Unable to send Automation Notification Email. No Primary Admin User Account specified.', false, 'POLLER'); return false; } $details = db_fetch_cell_prepared('SELECT email_address AS notification_email, full_name FROM user_auth WHERE id = ?', [$admin_user]); if (!cacti_sizeof($details)) { cacti_log('WARNING: Unable to send Automation Notification Email. The Primary Admin User Account does not exist.', false, 'POLLER'); return false; } if ($details['notification_email'] == '') { cacti_log('WARNING: Unable to send Automation Notification Email. The Primary Admin User Account does not have an Email Address.', false, 'POLLER'); return false; } $email = ($details['full_name'] != '' ? $details['full_name'] : __('Cacti Primary Admin')) . ' <' . $details['notification_email'] . '>'; } } $new_devices = getNetworkDevices($network_id); $ids = []; populateDeviceIndex($ids, false, $old_devices); populateDeviceIndex($ids, true, $new_devices); $table_head_style = 'style="border-bottom: 1px solid black"'; $table_head = '' . "Hostname" . "IP Address" . "SNMP Name" . "Has SNMP?" . "Responding?" . ''; $table_exist = ''; $table_new = ''; $count_exist = 0; $count_new = 0; $font_up = 'up'; $font_down = 'down'; foreach ($new_devices as $device) { $id = $device['ip']; $html_line = '' . $device['hostname'] . '' . $device['ip'] . '' . (empty($device['sysName']) ? 'None' : $device['sysName']) . '' . ($device['snmp'] ? $font_up : $font_down) . '' . ($device['up'] ? $font_up : $font_down) . ''; if ($ids[$id]['old'] != '') { $table_exist .= $html_line; $count_exist++; } else { $table_new .= $html_line; $count_new++; } } if (strlen($table_exist) > 0) { $table_exist .= ' '; } if (strlen($table_new) > 0) { $table_new .= ' '; } $v = CACTI_VERSION; $headers['User-Agent'] = 'Cacti-Automation-v' . $v; $status = ($count_new + $count_exist) . ' devices discovered'; if ($count_new > 0) { $status .= ', ' . $count_new . ' new!'; } $network = db_fetch_row_prepared('SELECT id, name, subnet_range, last_started, last_runtime FROM automation_networks WHERE id = ?', [$network_id]); $subject = 'Discovery of ' . $network['name'] . ' (' . $network['subnet_range'] . ') - ' . $status; $output = '

Discovery of ' . $network['name'] . '



' . '

Summary

' . '' . '' . '' . '' . '' . '
Network:' . $network['subnet_range'] . '
Started:' . $network['last_started'] . '
Duration:' . intval($network['last_runtime']) . '
Existing:' . $count_exist . ' devices
New:' . $count_new . ' devices


'; if ($count_new > 0 || $count_exist > 0) { $output .= ''; if ($count_new > 0) { $output .= '' . $table_head . $table_new; } if ($count_exist > 0) { $output .= '' . $table_head . $table_exist; } $output .= '

New Devices

Existing Devices

'; } $error = mailer( $from, $email, '', '', '', $subject, $output, __('Cacti Automation Report requires an html based Email client'), '', $headers ); if (strlen($error)) { cacti_log("WARNING: Automation had problems sending to '$email' for $status. The error was '$error'", false, 'AUTOM8'); } else { cacti_log("NOTICE: Email Notification Sent to '$email' for $status.", false, 'AUTOM8'); } } } return true; } function populateDeviceIndex(array &$ids, bool $is_new, array $devices) : void { $field = ($is_new ? 'new' : 'old'); foreach ($devices as $index => $device) { $id = $device['ip']; if (!isset($ids[$id])) { $ids[$id] = ['old' => '', 'new' => '']; } $ids[$id][$field] = $id; } } function clearTask(int $network_id, int $pid) : void { db_execute_prepared('DELETE FROM automation_processes WHERE pid = ? AND network_id = ?', [$pid, $network_id]); db_execute_prepared('DELETE FROM automation_ips WHERE network_id = ?', [$network_id]); } function clearAllTasks(int $network_id) : void { db_execute_prepared('DELETE FROM automation_processes WHERE network_id = ?', [$network_id]); } function markIPRunning(string $ip_address, int $network_id) : void { db_execute_prepared('UPDATE automation_ips SET status=1 WHERE ip_address = ? AND network_id = ?', [$ip_address, $network_id]); } function markIPDone(string $ip_address, int $network_id) : void { db_execute_prepared('UPDATE automation_ips SET status=2 WHERE ip_address = ? AND network_id = ?', [$ip_address, $network_id]); } function getNetworkDevices(int $network_id) : mixed { return db_fetch_assoc_prepared('SELECT id, hostname, ip, sysName, snmp, up, time FROM automation_devices WHERE network_id = ? ORDER BY hostname', [$network_id]); } function updateDownDevice(int $network_id, string $ip) : void { $exists = db_fetch_cell_prepared('SELECT COUNT(*) FROM automation_devices WHERE ip = ? AND network_id = ?', [$ip, $network_id]); if ($exists) { db_execute_prepared("UPDATE automation_devices SET up='0' WHERE ip = ? AND network_id = ?", [$ip, $network_id]); } }