1<?php
2
3/**
4 * This script does the following:
5 * 		- fetches all subnets that are marked for discovering new hosts
6 * 		- Scans each subnet for new hosts
7 * 		- If new host is discovered it will be added to database
8 *
9 *	Scan type be used as defined under administration:
10 *		- ping
11 *		- pear ping
12 *		- fping
13 *
14 *	Fping is new since version 1.2, it will work faster because it has built-in threading
15 *	so we are only forking separate subnets
16 *
17 *	Script must be run from cron, here is a crontab example, 1x/day should be enough:
18 * 		0 1 * * *  /usr/local/bin/php /<sitepath>/functions/scripts/pingCheck.php > /dev/null 2>&1
19 *
20 *
21 *	In case of problems set reset_debugging to true
22 *
23 */
24
25# include required scripts
26require_once( dirname(__FILE__) . '/../functions.php' );
27require( dirname(__FILE__) . '/../../functions/classes/class.Thread.php');
28
29# initialize objects
30$Database 	= new Database_PDO;
31$Subnets	= new Subnets ($Database);
32$Addresses	= new Addresses ($Database);
33$Tools		= new Tools ($Database);
34$Scan		= new Scan ($Database);
35$DNS		= new DNS ($Database);
36$Result		= new Result();
37
38// set exit flag to true
39$Scan->ping_set_exit(true);
40// set debugging
41$Scan->set_debugging(false);
42// change scan type?
43if(@$config['discovery_check_method'])
44$Scan->reset_scan_method ($config['discovery_check_method']);
45// set ping statuses
46$statuses = explode(";", $Scan->settings->pingStatus);
47// set mail override flag
48if(!isset($config['discovery_check_send_mail'])) {
49	$config['discovery_check_send_mail'] = true;
50}
51
52// set now for whole script
53$now     = time();
54$nowdate = date ("Y-m-d H:i:s");
55
56// response for mailing
57$address_change = array();			// Array with differences, can be used to email to admins
58$hostnames      = array();			// Array with detected hostnames
59
60
61// script can only be run from cli
62if(php_sapi_name()!="cli") 						{ die("This script can only be run from cli!"); }
63// test to see if threading is available
64if(!PingThread::available($errmsg)) 			{ die("Threading is required for scanning subnets - Error: $errmsg\n"); }
65// verify ping path
66if ($Scan->icmp_type=="ping") {
67if(!file_exists($Scan->settings->scanPingPath)) { die("Invalid ping path!"); }
68}
69// verify fping path
70if ($Scan->icmp_type=="fping") {
71if(!file_exists($Scan->settings->scanFPingPath)){ die("Invalid fping path!"); }
72}
73
74
75//first fetch all subnets to be scanned
76$scan_subnets = $Subnets->fetch_all_subnets_for_discoveryCheck (1);
77//set addresses
78if ($scan_subnets!==false) {
79    // initial array
80    $addresses_tmp = array();
81    // loop
82    foreach($scan_subnets as $i => $s) {
83    	// if subnet has slaves dont check it
84    	if ($Subnets->has_slaves ($s->id) === false) {
85    		$addresses_tmp[$s->id] = $Scan-> prepare_addresses_to_scan ("discovery", $s->id, false);
86			// save discovery time
87			$Scan->update_subnet_discoverytime ($s->id, $nowdate);
88        } else {
89            unset( $scan_subnets[$i] );
90    	}
91    }
92
93    //reindex
94    if(sizeof($addresses_tmp)>0) {
95        foreach($addresses_tmp as $s_id=>$a) {
96        	foreach($a as $ip) {
97        		$addresses[] = array("subnetId"=>$s_id, "ip_addr"=>$ip);
98        	}
99        }
100    }
101}
102
103
104if($Scan->get_debugging()==true)				{ print_r($scan_subnets); }
105if($scan_subnets===false || !count($scan_subnets)) { die("No subnets are marked for new hosts checking\n"); }
106
107
108//scan
109if($Scan->get_debugging()==true)				{ print "Using $Scan->icmp_type\n--------------------\n\n"; }
110
111
112$z = 0;			//addresses array index
113
114// let's just reindex the subnets array to save future issues
115$scan_subnets   = array_values($scan_subnets);
116$size_subnets   = count($scan_subnets);
117$size_addresses = max(array_keys($addresses));
118
119//different scan for fping
120if($Scan->icmp_type=="fping") {
121	//run per MAX_THREADS
122	for ($m=0; $m<=$size_subnets; $m += $Scan->settings->scanMaxThreads) {
123	    // create threads
124	    $threads = array();
125	    //fork processes
126	    for ($i = 0; $i <= $Scan->settings->scanMaxThreads && $i <= $size_subnets; $i++) {
127	    	//only if index exists!
128	    	if(isset($scan_subnets[$z])) {
129				//start new thread
130	            $threads[$z] = new PingThread( 'fping_subnet' );
131				$threads[$z]->start_fping( $Subnets->transform_to_dotted($scan_subnets[$z]->subnet)."/".$scan_subnets[$z]->mask );
132	            $z++;				//next index
133			}
134	    }
135	    // wait for all the threads to finish
136	    while( !empty( $threads ) ) {
137			foreach($threads as $index => $thread) {
138				$child_pipe = "/tmp/pipe_".$thread->getPid();
139
140				if (file_exists($child_pipe)) {
141					$file_descriptor = fopen( $child_pipe, "r");
142					$child_response = "";
143					while (!feof($file_descriptor)) {
144						$child_response .= fread($file_descriptor, 8192);
145					}
146					//we have the child data in the parent, but serialized:
147					$child_response = unserialize( $child_response );
148					//store
149					$scan_subnets[$index]->discovered = $child_response;
150					//now, child is dead, and parent close the pipe
151					unlink( $child_pipe );
152					unset($threads[$index]);
153				}
154			}
155	        usleep(200000);
156	    }
157	}
158
159	//fping finds all subnet addresses, we must remove existing ones !
160	foreach($scan_subnets as $sk=>$s) {
161    	if(isset($s->discovered)) {
162    		foreach($s->discovered as $rk=>$result) {
163    			if(!in_array($Subnets->transform_to_decimal($result), $addresses_tmp[$s->id])) {
164    				unset($scan_subnets[$sk]->discovered[$rk]);
165    			}
166    		}
167            //rekey
168            $scan_subnets[$sk]->discovered = array_values($scan_subnets[$sk]->discovered);
169		}
170	}
171}
172//ping, pear
173else {
174	//run per MAX_THREADS
175    for ($m=0; $m<=$size_addresses; $m += $Scan->settings->scanMaxThreads) {
176        // create threads
177        $threads = array();
178
179        //fork processes
180        for ($i = 0; $i <= $Scan->settings->scanMaxThreads && $i <= $size_addresses; $i++) {
181        	//only if index exists!
182        	if(isset($addresses[$z])) {
183				//start new thread
184	            $threads[$z] = new PingThread( 'ping_address' );
185	            $threads[$z]->start( $Subnets->transform_to_dotted($addresses[$z]['ip_addr']) );
186				$z++;			//next index
187			}
188        }
189
190        // wait for all the threads to finish
191        while( !empty( $threads ) ) {
192            foreach( $threads as $index => $thread ) {
193                if( ! $thread->isAlive() ) {
194					//unset dead hosts
195					if($thread->getExitCode() != 0) {
196						unset($addresses[$index]);
197					}
198                    //remove thread
199                    unset( $threads[$index]);
200                }
201            }
202            usleep(200000);
203        }
204	}
205
206	//ok, we have all available addresses, rekey them
207	foreach($addresses as $a) {
208		$add_tmp[$a['subnetId']][] = $Subnets->transform_to_dotted($a['ip_addr']);
209	}
210	//add to scan_subnets as result
211	foreach($scan_subnets as $sk=>$s) {
212		if(isset($add_tmp[$s->id])) {
213			$scan_subnets[$sk]->discovered = $add_tmp[$s->id];
214		}
215	}
216}
217
218
219# print change
220if($Scan->get_debugging()==true)				{ "\nDiscovered addresses:\n----------\n"; print_r($scan_subnets); }
221
222
223
224# reinitialize objects
225$Database 	= new Database_PDO;
226$Admin		= new Admin ($Database, false);
227$Addresses	= new Addresses ($Database);
228$Subnets	= new Subnets ($Database);
229$DNS		= new DNS ($Database);
230$Scan		= new Scan ($Database);
231$Result		= new Result();
232
233# insert to database
234$discovered = 0;				//for mailing
235
236foreach($scan_subnets as $s) {
237	if(isset($s->discovered)) {
238		foreach($s->discovered as $ip) {
239			// fetch subnet
240			$subnet = $Subnets->fetch_subnet ("id", $s->id);
241			$nsid = $subnet===false ? false : $subnet->nameserverId;
242			// try to resolve hostname
243			$hostname = $DNS->resolve_address ($ip, false, true, $nsid);
244			// save to hostnames
245			$hostnames[$ip] = $hostname['name']==$ip ? "" : $hostname['name'];
246
247			//set update query
248			$values = array(
249							"subnetId"    =>$s->id,
250							"ip_addr"     =>$ip,
251							"hostname"    =>$hostname['name'],
252							"description" =>"-- autodiscovered --",
253							"note"        =>"This host was autodiscovered on ".$nowdate,
254							"lastSeen"    =>$nowdate,
255							"state"       =>"2",
256							"action"      =>"add"
257							);
258			//insert
259			$Addresses->modify_address($values);
260
261			//set discovered
262			$discovered++;
263		}
264	}
265}
266
267# update scan time
268$Scan->ping_update_scanagent_checktime (1, $nowdate);
269
270
271
272# send mail
273if($discovered>0 && $config['discovery_check_send_mail']) {
274
275	# check for recipients
276	foreach($Admin->fetch_multiple_objects ("users", "role", "Administrator") as $admin) {
277		if($admin->mailNotify=="Yes") {
278			$recepients[] = array("name"=>$admin->real_name, "email"=>$admin->email);
279		}
280	}
281	# none?
282	if(!isset($recepients))	{ die(); }
283
284	# fake user object, needed for create_link
285	$User = new StdClass();
286	@$User->settings->prettyLinks = $Scan->settings->prettyLinks;
287
288	# try to send
289	try {
290		# fetch mailer settings
291		$mail_settings = $Admin->fetch_object("settingsMail", "id", 1);
292		# initialize mailer
293		$phpipam_mail = new phpipam_mail($Scan->settings, $mail_settings);
294
295		// set subject
296		$subject	= "phpIPAM new addresses detected ".date("Y-m-d H:i:s");
297
298		//html
299		$content[] = "<h3>phpIPAM found $discovered new hosts</h3>";
300		$content[] = "<table style='margin-left:10px;margin-top:5px;width:auto;padding:0px;border-collapse:collapse;border:1px solid gray;'>";
301		$content[] = "<tr>";
302		$content[] = "	<th style='padding:3px 8px;border:1px solid silver;border-bottom:2px solid gray;'>IP</th>";
303		$content[] = "	<th style='padding:3px 8px;border:1px solid silver;border-bottom:2px solid gray;'>Hostname</th>";
304		$content[] = "	<th style='padding:3px 8px;border:1px solid silver;border-bottom:2px solid gray;'>Subnet</th>";
305		$content[] = "	<th style='padding:3px 8px;border:1px solid silver;border-bottom:2px solid gray;'>Section</th>";
306		$content[] = "</tr>";
307		//plain
308		$content_plain[] = "phpIPAM found $discovered new hosts\r\n------------------------------";
309		//Changes
310		foreach($scan_subnets as $s) {
311			if(is_array($s->discovered)) {
312				foreach($s->discovered as $ip) {
313					//set subnet
314					$subnet 	 = $Subnets->fetch_subnet(null, $s->id);
315					//set section
316					$section 	 = $Admin->fetch_object("sections", "id", $s->sectionId);
317
318					$content[] = "<tr>";
319					$content[] = "	<td style='padding:3px 8px;border:1px solid silver;'>$ip</td>";
320					$content[] = "	<td style='padding:3px 8px;border:1px solid silver;'>".$hostnames[$ip]."</td>";
321					$content[] = "	<td style='padding:3px 8px;border:1px solid silver;'><a href='".rtrim($Scan->settings->siteURL, "/")."".create_link("subnets",$section->id,$subnet->id)."'>".$Subnets->transform_to_dotted($subnet->subnet)."/".$subnet->mask." - ".$subnet->description."</a></td>";
322					$content[] = "	<td style='padding:3px 8px;border:1px solid silver;'><a href='".rtrim($Scan->settings->siteURL, "/")."".create_link("subnets",$section->id)."'>$section->name $section->description</a></td>";
323					$content[] = "</tr>";
324
325					//plain content
326					$content_plain[] = "\t * $ip (".$Subnets->transform_to_dotted($subnet->subnet)."/".$subnet->mask.")";
327				}
328			}
329		}
330		$content[] = "</table>";
331
332
333		# set content
334		$content 		= $phpipam_mail->generate_message (implode("\r\n", $content));
335		$content_plain 	= implode("\r\n",$content_plain);
336
337		$phpipam_mail->Php_mailer->setFrom($mail_settings->mAdminMail, $mail_settings->mAdminName);
338		//add all admins to CC
339		foreach($recepients as $admin) {
340			$phpipam_mail->Php_mailer->addAddress(addslashes($admin['email']), addslashes($admin['name']));
341		}
342		$phpipam_mail->Php_mailer->Subject = $subject;
343		$phpipam_mail->Php_mailer->msgHTML($content);
344		$phpipam_mail->Php_mailer->AltBody = $content_plain;
345		//send
346		$phpipam_mail->Php_mailer->send();
347	} catch (phpmailerException $e) {
348		$Result->show_cli("Mailer Error: ".$e->errorMessage(), true);
349	} catch (Exception $e) {
350		$Result->show_cli("Mailer Error: ".$e->getMessage(), true);
351	}
352}