Import the IP history plugin from the demo server.
[racktables-contribs] / fingtab / fingtab.php
1 <?php
2 /**
3 * Fing network scan plugin
4 * version 0.2
5 *
6 * Written by Florian Pfaff
7 *
8 * Inspired by livePTR and rc_pingreply
9 *
10 * The purpose of this plugin is to scan networks which cannot be reached directly.
11 * It uses SSH to connect to a host which has overlook fing installed and uses it to scan the defined network
12 *
13 * Requirements:
14 * - SSH client on racktables server
15 * - Overlook Fing http://www.overlooksoft.com/download installed on a host in the network you want to scan
16 * - the host with overlook fing has to be reachable via SSH
17 * - key SSH authentication needs to be configured
18 * - $FING_ssh_binary and $FING_settings need to be configured in the secret.php
19 *
20 */
21
22 // register fing tab
23 $tab['ipv4net']['fing'] = 'Fing Scan';
24 $tabhandler['ipv4net']['fing'] = 'FingTab';
25 $ophandler['ipv4net']['fing']['importFingData'] = 'importFingData';
26
27
28
29 /**
30 * Execute a command return exit code
31 * provide STDOUT and STDERR
32 */
33 function my_shell_exec($cmd, &$stdout=null, &$stderr=null) {
34 $proc = proc_open($cmd, array(
35 1 => array('pipe','w'),
36 2 => array('pipe','w'),
37 ),$pipes);
38 $stdout = stream_get_contents($pipes[1]);
39 fclose($pipes[1]);
40 $stderr = stream_get_contents($pipes[2]);
41 fclose($pipes[2]);
42 return proc_close($proc);
43 }
44
45
46 /**
47 * Parse a csv returned by fing and return an array with the parsed information
48 */
49 function parse_fing_csv($csv){
50 $known_ips = array();
51 foreach (preg_split("/((\r?\n)|(\r\n?))/", $csv) as $line) {
52 $fields= explode(";", $line);
53
54 if ($fields[0]) {
55 $tmp_array = array(
56 "ip" => $fields[0],
57 "state" => $fields[2],
58 "timestamp" => $fields[3],
59 "hostname" => $fields[4],
60 "mac" => $fields[5],
61 "vendor" => array_key_exists(6,$fields) ? $fields[6] : ""
62 );
63 $known_ips[$fields[0]] = $tmp_array;
64 }
65 }
66 return $known_ips;
67 }
68
69
70 /**
71 * get hostname, state and mac/vendor information for a given ip
72 */
73 function get_fing_info($ip,$known_ips){
74 $hostname = "";
75 $state = "down";
76 $mac_vendor ="";
77
78 if (array_key_exists($ip, $known_ips))
79 {
80 $hostname = $known_ips[$ip]["hostname"];
81 $state = strtoupper($known_ips[$ip]["state"]);
82 $mac_vendor = $known_ips[$ip]["mac"];
83 if ($known_ips[$ip]["vendor"])
84 $mac_vendor = $mac_vendor." (".$known_ips[$ip]["vendor"].")";
85 }
86 $result = array($hostname, $state, $mac_vendor);
87 return $result;
88 }
89
90
91 /**
92 * Return the amount of hosts which are up
93 */
94 function get_fing_up_count($known_ips)
95 {
96 $count = 0;
97 foreach ($known_ips as $cip)
98 {
99 if (strtoupper($cip["state"]) == "UP")
100 $count++;
101 }
102 return $count;
103 }
104
105 /**
106 * render error message
107 */
108 function render_fing_error($title,$msg)
109 {
110 echo "<div class='msg_error'>";
111 echo "<h2>${title}</h2>";
112 echo $msg;
113 echo "</div>";
114 }
115
116 /**
117 * Class FingException
118 */
119 class FingException extends Exception {}
120
121
122 /**
123 * get fing settings array for a specific subnet
124 */
125 function get_fing_settings($ip,$mask)
126 {
127 global $FING_settings;
128 if (!$FING_settings)
129 throw new FingException("Fing configuration not found. Please make sure \$FING_settings is configured in the secret.php");
130
131 $net = $ip."/".$mask;
132 if (!array_key_exists($net,$FING_settings))
133 throw new FingException("No matching Fing configuration found for network ${net}");
134
135 return $FING_settings[$net];
136 }
137
138
139 /**
140 * scan a subnet using fing
141 */
142 function get_fing_scan($ip,$mask)
143 {
144 global $FING_ssh_binary;
145
146 if (!$FING_ssh_binary)
147 throw new FingException("\$FING_ssh_binary is not defined in secret.php");
148
149 $settings = get_fing_settings($ip,$mask);
150
151 $fing_gw = $settings["gateway"];
152 $user = $settings["user"];
153 $sudo = $settings["gateway"] ? "sudo" : "";
154 $id_file = $settings["id_file"];
155 $cmd = "$FING_ssh_binary $user@$fing_gw -v -i $id_file -o 'PasswordAuthentication no' $sudo fing -r 1 -n $ip/$mask -o table,csv,console --silent";
156
157 $ret_val = my_shell_exec("$cmd", $std_out, $std_err);
158 if ($ret_val>0)
159 throw new FingException("Error executing fing:<br><pre>Command: $cmd\n\n exit code: $ret_val\n\n STDERR: $std_err\n\n STDOUT: $std_out\n</pre>");
160
161 $known_ips = parse_fing_csv($std_out);
162
163 return $known_ips;
164 }
165
166 /**
167 *
168 * address allocation setting (copied from interface.php)
169 *
170 */
171
172
173
174
175 /**
176 *
177 * Fing tab handler
178 *
179 */
180 function FingTab($id)
181 {
182 $can_import = permitted (NULL, NULL, 'importFingData');
183
184
185 //
186 // allocation settings
187 //
188 // address allocation code, IPv4 networks view
189 $aac_left = array
190 (
191 'regular' => '',
192 'virtual' => '<span class="aac-left" title="Loopback">L:</span>',
193 'shared' => '<span class="aac-left" title="Shared">S:</span>',
194 'router' => '<span class="aac-left" title="Router">R:</span>',
195 'point2point' => '<span class="aac-left" title="Point-to-point">P:</span>',
196 );
197
198 //
199 // header
200 //
201 global $pageno, $tabno;
202
203 $maxperpage = getConfigVar('IPV4_ADDRS_PER_PAGE');
204 $range = spotEntity('ipv4net', $id);
205 loadIPAddrList($range);
206 echo "<center><h1>${range['ip']}/${range['mask']}</h1><h2>${range['name']}</h2></center>\n";
207
208
209 //
210 // execute fing
211 //
212 try {
213 $known_ips = get_fing_scan($range['ip'], $range['mask']);
214 $fing_cfg = get_fing_settings($range['ip'], $range['mask']);
215 $fing_gw = $fing_cfg["gateway"];
216 } catch (FingException $e) {
217 render_fing_error("Could not get network scan via fing:",$e->getMessage());
218 return FALSE;
219 }
220
221 echo "<table class=objview border=0 width='100%'><tr><td class=pcleft>";
222 startPortlet ('overlook fing (via: '.$fing_gw.')');
223
224 //
225 // pagination
226 //
227 if (isset($_REQUEST['pg']))
228 $page = $_REQUEST['pg'];
229 else
230 $page = 0;
231 $startip = ip4_bin2int ($range['ip_bin']);
232 $endip = ip4_bin2int (ip_last ($range));
233 $numpages = 0;
234 if ($endip - $startip > $maxperpage)
235 {
236 $numpages = ($endip - $startip) / $maxperpage;
237 $startip = $startip + $page * $maxperpage;
238 $endip = $startip + $maxperpage - 1;
239 }
240 echo "<center>";
241 if ($numpages)
242 echo '<h3>' . ip4_format (ip4_int2bin ($startip)) . ' ~ ' . ip4_format (ip4_int2bin ($endip)) . '</h3>';
243 for ($i=0; $i<$numpages; $i++)
244 if ($i == $page)
245 echo "<b>$i</b> ";
246 else
247 echo "<a href='".makeHref(array('page'=>$pageno, 'tab'=>$tabno, 'id'=>$id, 'pg'=>$i))."'>$i</a> ";
248 echo "</center>";
249
250 if ($can_import)
251 {
252 printOpFormIntro ('importFingData', array ('addrcount' => ($endip - $startip + 1)));
253 $box_counter = 1;
254 }
255
256
257 echo "<table class='widetable' border=0 cellspacing=0 cellpadding=5 align='center'>\n";
258 echo "<tr><th class='tdleft'>address</th><th class='tdleft'>state</th><th class='tdleft'>current name</th><th class='tdleft'>DNS name</th><th class='tdleft'>MAC</th><th class='tdleft'>Allocation</th>";
259 if ($can_import)
260 echo '<th>import</th>';
261 echo "</tr>\n";
262
263
264 //
265 // Loop through all IPs
266 //
267 $cnt_match = $cnt_missing = $cnt_mismatch = $cnt_total = 0;
268 for ($ip = $startip; $ip <= $endip; $ip++)
269 {
270 $cnt_total++;
271 $print_cbox = FALSE;
272 $ip_bin = ip4_int2bin($ip);
273 $addr = isset ($range['addrlist'][$ip_bin]) ? $range['addrlist'][$ip_bin] : array ('name' => '', 'reserved' => 'no');
274 $straddr = ip4_format ($ip_bin);
275
276 list($fing_hostname, $fing_state, $fing_mac_vendor) = get_fing_info($straddr, $known_ips);
277 $ip_is_up = strtoupper($fing_state) == "UP" ? TRUE : FALSE;
278
279 if ($can_import)
280 {
281 echo "<input type=hidden name=addr_${cnt_total} value=${straddr}>\n";
282 echo "<input type=hidden name=descr_${cnt_total} value=${fing_hostname}>\n";
283 echo "<input type=hidden name=rsvd_${cnt_total} value=${addr['reserved']}>\n";
284 }
285
286 $skip_dns_check = FALSE;
287 echo "<tr";
288 // Ignore network and broadcast addresses
289 if (($ip == $startip && $addr['name'] == 'network') || ($ip == $endip && $addr['name'] == 'broadcast'))
290 {
291 echo " class='trbusy'";
292 $skip_dns_check = TRUE;
293 }
294 elseif (!$ip_is_up)
295 echo " class='trnull'";
296 // set line color depending if we have the name already in the DB
297 if (!$skip_dns_check) {
298 if ($addr['name'] == $fing_hostname) {
299 if (strlen($fing_hostname)) {
300 echo ' class=trok';
301 $cnt_match++;
302 }
303 } elseif (!strlen($addr['name']) or !strlen($fing_hostname)) {
304 echo ' class=trwarning';
305 $print_cbox = TRUE;
306 $cnt_missing++;
307 } else {
308 echo ' class=trerror';
309 $print_cbox = TRUE;
310 $cnt_mismatch++;
311 }
312 }
313
314 //IP
315 echo "><td class='tdleft";
316 if (isset ($range['addrlist'][$ip_bin]['class']) and strlen ($range['addrlist'][$ip_bin]['class']))
317 echo ' ' . $range['addrlist'][$ip_bin]['class'];
318 echo "'><a href='".makeHref(array('page'=>'ipaddress', 'ip'=>$straddr))."'>${straddr}</a></td>";
319
320 //other columns
321 if ($skip_dns_check)
322 echo "<td class='tdleft'>&nbsp;</td>";
323 else {
324 if (!$ip_is_up)
325 echo "<td class='tdleft'>" . $fing_state . "</td>";
326 else
327 echo "<td class='tdleft'><div class='strong'>" . $fing_state . "</div></td>";
328 }
329 echo "<td class=tdleft>${addr['name']}</td>";
330 echo "<td class='tdleft'>".$fing_hostname."</td>";
331 echo "<td class='tdleft'>".$fing_mac_vendor."</td>";
332
333
334 //allocation
335 echo "<td>";
336 $delim = '';
337 if ( $addr['reserved'] == 'yes')
338 {
339 echo "<strong>RESERVED</strong> ";
340 $delim = '; ';
341 }
342 foreach ($addr['allocs'] as $ref)
343 {
344 echo $delim . $aac_left[$ref['type']];
345 echo makeIPAllocLink ($ip_bin, $ref, TRUE);
346 $delim = '; ';
347 }
348 if ($delim != '')
349 $delim = '<br>';
350 foreach ($addr['vslist'] as $vs_id)
351 {
352 $vs = spotEntity ('ipv4vs', $vs_id);
353 echo $delim . mkA ("${vs['name']}:${vs['vport']}/${vs['proto']}", 'ipv4vs', $vs['id']) . '&rarr;';
354 $delim = '<br>';
355 }
356 foreach ($addr['vsglist'] as $vs_id)
357 {
358 $vs = spotEntity ('ipvs', $vs_id);
359 echo $delim . mkA ($vs['name'], 'ipvs', $vs['id']) . '&rarr;';
360 $delim = '<br>';
361 }
362 foreach ($addr['rsplist'] as $rsp_id)
363 {
364 $rsp = spotEntity ('ipv4rspool', $rsp_id);
365 echo "${delim}&rarr;" . mkA ($rsp['name'], 'ipv4rspool', $rsp['id']);
366 $delim = '<br>';
367 }
368 echo "</td>";
369
370 // import column
371 if ($can_import)
372 {
373 echo '<td>';
374 if ($print_cbox)
375 echo "<input type=checkbox name=import_${cnt_total} id=atom_1_" . $box_counter++ . "_1>";
376 else
377 echo '&nbsp;';
378 echo '</td>';
379 }
380 echo "</tr>";
381 }
382
383 if ($can_import && $box_counter > 1)
384 {
385 echo '<tr><td colspan=4 align=center><input type=submit value="Import selected records"></td><td colspan=2 align=right>';
386 addJS ('js/racktables.js');
387 echo --$box_counter ? "<a href='javascript:;' onclick=\"toggleColumnOfAtoms(1, 1, ${box_counter})\">(toggle selection)</a>" : '&nbsp;';
388 echo '</td></tr>';
389 }
390
391 echo "</table>";
392 if ($can_import)
393 echo '</form>';
394 finishPortlet();
395
396 echo "</td><td class=pcright>";
397
398 //
399 // PING Statistics
400 //
401 startPortlet ('ping stats');
402 $cnt_ping_up = get_fing_up_count($known_ips);
403 echo "<table border=0 width='100%' cellspacing=0 cellpadding=2>";
404 echo "<tr class=trok><th class=tdright>Replied to Ping</th><td class=tdleft>${cnt_ping_up}</td></tr>\n";
405 echo "<tr class=trwarning><th class=tdright>No Response</th><td class=tdleft>".($cnt_total-$cnt_ping_up)."</td></tr>\n";
406 echo "</table>\n";
407 finishPortlet();
408
409 //
410 // DNS Statistics
411 //
412 startPortlet ('dns stats');
413 echo "<table border=0 width='100%' cellspacing=0 cellpadding=2>";
414 echo "<tr class=trok><th class=tdright>Exact matches:</th><td class=tdleft>${cnt_match}</td></tr>\n";
415 echo "<tr class=trwarning><th class=tdright>Missing from DB/DNS:</th><td class=tdleft>${cnt_missing}</td></tr>\n";
416 if ($cnt_mismatch)
417 echo "<tr class=trerror><th class=tdright>Mismatches:</th><td class=tdleft>${cnt_mismatch}</td></tr>\n";
418 echo "</table>\n";
419 finishPortlet();
420 }
421
422
423 $msgcode['importFingData']['OK'] = 26;
424 $msgcode['importFingData']['ERR'] = 141;
425 function importFingData ()
426 {
427 $net = spotEntity ('ipv4net', getBypassValue());
428 assertUIntArg ('addrcount');
429 $nbad = $ngood = 0;
430 for ($i = 1; $i <= $_REQUEST['addrcount']; $i++)
431 {
432 $inputname = "import_${i}";
433 if (! isCheckSet ($inputname))
434 continue;
435 $ip_bin = assertIPv4Arg ("addr_${i}");
436 assertStringArg ("descr_${i}", TRUE);
437 assertStringArg ("rsvd_${i}");
438 // Non-existent addresses will not have this argument set in request.
439 $rsvd = 'no';
440 if ($_REQUEST["rsvd_${i}"] == 'yes')
441 $rsvd = 'yes';
442 try
443 {
444 if (! ip_in_range ($ip_bin, $net))
445 throw new InvalidArgException ('ip_bin', $ip_bin);
446 updateAddress ($ip_bin, $_REQUEST["descr_${i}"], $rsvd);
447 $ngood++;
448 }
449 catch (RackTablesError $e)
450 {
451 $nbad++;
452 }
453 }
454 if (!$nbad)
455 showFuncMessage (__FUNCTION__, 'OK', array ($ngood));
456 else
457 showFuncMessage (__FUNCTION__, 'ERR', array ($nbad, $ngood));
458 }
459
460
461
462 ?>