#!/usr/bin/perl -w
#
# __copy1__
# __copy2__
#
use strict;
use warnings;

my $CMD		= "ku-netdiscover";
my $CMDVER	= "1.8";
my $CMDSIG	= "$CMD v.$CMDVER (2023-08025)";

my $myname	= `uname -n`; chomp($myname);
my $domain	= $myname; $domain =~ s/^[^.][^.]*.//;
my $device	= "";

my $macfile	= "/usr/share/arp-scan/custom-mac-vendors.txt";
my $defmacfile	= "/usr/share/arp-scan/mac-vendor.txt";
my $scancmd	= "arp-scan -g";
my $retry	= 8;

my $dhcpleases	= `which dhcp-lease-list 2>/dev/null`; chomp( $dhcpleases );

my $count	= 0;
my $IN;
my %host;
my $ipmaxlen	= 0;
my $hostmaxlen	= 0;
my @tmp;
my $hline	= '-' x 80;

my $f_html	= 0;
my $f_wiki	= 0;

my $VERBOSE	= 1;
my $DEBUG	= 0;

while (scalar(@ARGV)) {
    $_	= shift(@ARGV);
    CASE: {
	if ($_ eq '-v' || $_ eq '--verbose')	{ $VERBOSE = 1; last CASE; }
	if ($_ eq '-q' || $_ eq '--quiet')	{ $VERBOSE = 0; last CASE; }
	if ($_ eq '-D' || $_ eq '--debug')	{ $DEBUG = 1; pdebug( "DEBUG=1" ); last CASE; }
	if ($_ eq '--html')			{ $f_html = 1; last CASE; }
	if ($_ eq '--wiki')			{ $f_wiki = 1; last CASE; }
	if ($_ =~ /^-D[0-9]/ || $_ =~ /^--debug=[0-9]/)	{
		s/-D//;
		s/--debug=//;
		$DEBUG = $_;
		last CASE;
	}
	if ($_ eq '-h')				{ usage(); }
	if ($_ eq '--help')			{ usage(); }

	if ($_ eq '-r' || $_ eq '--retry') {
		usage( "'--retry' requires a numeric argument" )	if (!scalar(@ARGV));
		$_ = shift(@ARGV);
		usage( "'--retry' requires a numeric argument" )	if ($_ !~ /^[0-9]+$/);
		$retry = $_;
		last CASE;
	}
	if ($_ =~ /^[a-z]/) {
		$device	= $_;
		pdebug( "device=$device" );
		last CASE;
	}
	usage();
    }
}

# safety checks
#
if (`which arp-scan` eq "") {
	die( "command 'arp-scan' not found, abort\n" );
}
if (chomp($_=`id -u`),$_ ne '0') {
	die( "you need to be root to run this command\n" );
}

if ($f_html && $f_wiki) {
	usage( "you can't use both --html and --wiki" );
}

if (! -f $macfile) {
	$macfile = $defmacfile;
}
if ($domain eq "") {
	print( STDERR "warning, domain not set\n" );
}


# search (first or command line) interface and get my mac
#
my $tempdev = "";
my $myaddr;
my $mymac;
pdebug( "searching infos, device=$device" );
open( IN, "/sbin/ifconfig $device |" )	or die( "error spawning /sbin/ifconfig: $!\n" );
while( <IN> ) {
	chomp();
	pdebug( "ifconfig: $_" );
	if ($_ =~ /^[a-z]/) {
		$tempdev = $_;
		$tempdev =~ s/:* +.*//;
	}
	if ($_ =~ /HWaddr/ || $_ =~ /ether /) {
		s/.*HWaddr *//;
		s/.*ether //;
		s/ .*//;
		$mymac	= $_;
		pdebug( "ifconfig: mymac='$mymac'" );
	}
	if ($_ =~ /inet /) {
		s/.*inet addr: *//;
		s/.*inet *//;
		s/ .*//;
		$myaddr	= $_;
		pdebug( "ifconfig: myaddr='$myaddr'" );
	}
	last	if (defined $myaddr && defined $mymac);
}
#close( IN )	or die( "error running /sbin/ifconfig $device: $!\n" );
close( IN );
$device	= $tempdev	if ($device eq "");

if (!defined $myaddr || !defined $mymac) {
	printf( STDERR "warning: cannot add myself, no address or MAC found\n" );
} else {
	addhost( $myaddr, $mymac, "(myself)" );
}

$scancmd = "$scancmd -r $retry -I $device --macfile=$macfile --localnet";
pdebug( "scancmd: $scancmd" );

vprint( "running '$scancmd'\n" );

open( IN, "$scancmd |" )	or die( "$!" );

while( <IN> ) {
	next	if ($_ !~ /^[0-9][0-9]*\.[0-9]/);
	s/[ \t]+/ /g;

	chomp();
	@tmp		= split( / / );
	my $ip		= shift(@tmp);
	my $mac		= shift(@tmp);
	my $vendor	= join( " ", @tmp );

	$vendor	=~ s/Unknown/unknown-vendor/g;
	$vendor	=~ s/locally administered/local/g;

	addhost( $ip, $mac, $vendor );
}


close( IN )	or die( "$!" );


my $hformat = "\n%s\n\n";
my $pformat = "%-${ipmaxlen}s  %-17s  %-${hostmaxlen}s  %-24.24s\n";
my $cformat = "%-${ipmaxlen}.${ipmaxlen}s  %-17.17s  %-${hostmaxlen}.${hostmaxlen}s  %-24.24s\n";
my $tformat = "\n%s\n";

if ($f_html) {
	$hformat = "<h2>%s</h2>\n";
	$pformat = " <tr> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> </tr>\n";
	$cformat = " <tr> <th>%s</th> <th>%s</th> <th>%s</th> <th>%s</th> </tr>\n";
	$tformat = "<p>%s\n";
}



printf( "<verbatim>\n" )	if ($f_wiki);

@tmp = localtime( time() );
printf( $hformat, sprintf( "ARP-SCAN ON $device  ==  %02d-%02d-%02d %02d:%02d ==  $CMDSIG", 
	$tmp[5]+1900, $tmp[4]+1, $tmp[3], $tmp[2], $tmp[1] ) );

printf( "<table>\n" )					if ($f_html);
printf( $cformat, 'IP', 'MAC', 'HOST', 'VENDOR' );
printf( $cformat, $hline, $hline, $hline, $hline )	if (!$f_html);

foreach $_ (sort keys %host) {
	my ($ip,$mac,$hostname,$vendor)	= split( /\|/, $host{$_});
	printf( $pformat, $ip, $mac, $hostname, $vendor );
}

printf( "</table>\n" )					if ($f_html);
printf( $cformat, $hline, $hline, $hline, $hline )	if (!$f_html);
printf( $tformat, "TOTAL HOSTS: " . scalar(keys %host) );
printf( "</verbatim>\n" )				if ($f_wiki);
printf( "\n" );

exit( 0 );



sub usage
{
	print STDERR "
$CMDSIG

usage: ku-netdiscovery [options] [device]

options:
  --html		produces html (table) output
  --wiki		produces wiki (verbatim) output
  -r|--retry N		set retry number (default: $retry)

  -q|--quiet		be quiet
  -v|--verbose		be verbose
  -D[n]|--debug[=n]	activate debug, with optional level 'n'

";
	print STDERR "\nerror: ", @_, "\n"	if (scalar(@_));
	die( "\n" );
}



sub addhost
{
	my ($ip,$mac,$vendor)	= @_;
	my @tmp		= split( '\.', $ip );
	my $sortip	= sprintf( "%03d%03d%03d%03d", $tmp[0], $tmp[1], $tmp[2], $tmp[3] );
	my $hostname	= '';
	my $color	= '';
	
	if ($domain ne '') {
		$hostname	= `host $ip 2>/dev/null | fgrep $domain | head -1`;
	}
	if ($hostname eq '') {
		$hostname	= `host $ip 2>/dev/null | head -1`;
	}
	if ($hostname =~ /NXDOMAIN/ && $dhcpleases ne '') {
		$hostname	= get_dhcp_name( $ip );
		if ($hostname ne '') {
			$hostname	= " (dhcp:$hostname)";
		} else {
			$hostname	= "NXDOMAIN";
		}
	}
	chomp($hostname);

	$hostname	= " (no-hostname)"			if ($hostname =~ /NXDOMAIN/);
	$hostname	=~ s/.*pointer //;
	$hostname	=~ s/\.$//;

	if (length($ip) > $ipmaxlen) {
		$ipmaxlen = length($ip);
	}
	if (length($hostname) > $hostmaxlen) {
		$hostmaxlen = length($hostname);
	}

	# html? just some fancy colors on hostname
	if ($f_html) {
		CASE: {
			if ($hostname =~ /FREE/)	{ $color = 'red'; last CASE; }
			if ($hostname =~ /unknown/)	{ $color = 'red'; last CASE; }
			if ($hostname =~ /reserved/)	{ $color = 'red'; last CASE; }
			if ($hostname =~ /dhcp/)	{ $color = 'blue'; last CASE; }
	    	}
	    	if ($color ne '') {
			$hostname = sprintf( '<font color="%s">%s</font>', $color, $hostname );
		}
	}

	pdebug( "addhost( $ip,$mac,$hostname,$vendor )" );
	$host{$sortip}	= "$ip|$mac|$hostname|$vendor";

	return 1;
}


sub vprint
{
	if ($VERBOSE) { print( STDERR @_ ); }
}

sub pdebug
{
	if ($DEBUG) { print( STDERR "D> ", @_, "\n"); }
}


sub get_dhcp_name
{
	my ($ip) = @_;
	my $name = `$dhcpleases 2>/dev/null | grep " $ip "`;
	return ""	if ($name eq '');

	my @tmp = split( /\s+/, $name );
	$name = $tmp[2];
	$name = ''	if ($name eq '-NA-');
	return $name;
}
