#!/usr/bin/perl 
use Socket;
use Getopt::Std;

#########################################################################
# MAC address lookup translation tool MAC -> name,			#	
# collect MAC addresses on the LAN,					#
# reverse translations range of IP addresse				#
#									#
# Author: Pavel Mracek mrak(at)mrak(dot)cz                              #
# Version: 0.5                                                          #
#########################################################################
#
#  example 1.: lookup mac to name
#	root@nika root]# arp -a |hwa
#	10.1.255.10 at 00:40:a1:3c:15:1f(holy_i626_1) [ether] on eth2
#	10.1.2.2 at 00:07:a6:ac:ae:12(blek) [ether] on eth0
#	213.191.108.97 at 00:50:ac:a3:54:a3(unknown) [ether] on eth1
#
#  example 2.: lookup range of ip adresses
#	[mrak@cat ~]$ hwa -r 213.192.29.1-6
#	213.192.29.1 - gw2.praslavice.net
#	213.192.29.2 - mail.praslavice.net
#	213.192.29.4 - ip4.tucnak.org
#	213.192.29.5 - lancia.tucnak.org
#	213.192.29.6 - ip6.tucnak.org
#
#  example 3.: grab local lan MACs:IPs
#	[mrak@cat ~]$ hwa -g 172.21.21.1-30
#	0023cdb1b18f:172.21.21.1
#	0090e8193bc3:172.21.21.3
#	000b6bc95fc1:172.21.21.24
#	00094563bc5c:172.21.21.30
#
#  	OR in DNS zone format
#	[mrak@cat ~]$  bin/hwa -z -g 172.21.21.1-10
#	0023cdb1b18f    IN      TXT     172.21.21.1
#	0090e8193bc3    IN      TXT     172.21.21.3
#
# 
#  
#  example 4.: lookup single MAC to NAME with  vendor identification
#	[mrak@cat ~]$ hwa -V 00:02:72:6b:ea:91
#	roman-ap-wa220 - CC&C_Technologies,_Inc.
#
#
#
#  example 5.: DNS zone file with MAC to NAME records
#
#	$ORIGIN hw.mrak.cz.
#	$TTL 259200     ; 3 days
#	@              IN SOA  nika.mrak.cz. mrak.nika.mrak.cz. (
#				20040419; serial
#				3600       ; refresh (1 hour)
#				9000       ; retry (2 hours 30 minutes)
#				1209600    ; expire (2 weeks)
#				432        ; minimum (7 minutes 12 seconds)
#			)
#	$TTL 432        ; 7 minutes 12 seconds
#		    IN          NS      nika.mrak.cz.
#		    IN          NS      holy.mrak.cz.
#
#	$TTL 259200     ; 3 days
#	;;;;;;;;;;;;;; mac2name records ;;;;;;;;;;;;;;;;
#
#	0008c7bbbf51    IN      TXT     blek
#	00e908a9fa1c    IN      TXT     slayer
#	0008089daa60    IN      TXT     bohus
#
# 
#########################################################################
# This file may be distributed under the terms of the GNU General   	#
# Public License.                           				#
#########################################################################


## text konfiguration file (kompatibile with iptraf)
$conf_file="~/.ethernet.desc";

## command for searching in DNS records
$dns_cmd="host -t txt";
## default "hw" domain and optional DNS server
#$dns_parm="hw.mrak.cz 10.1.1.1";
$dns_parm="hw.mrak.cz";

# waiting for ping in grab_arp function
$ping_wait="1";

$arp="/sbin/arp";
###################################################
########## END of user pref. variables ############
###################################################
my (%addr);
my (%ven);
$ver="0.5";

#######
## conf

sub c_load{
   my ($file)=@_;
   my @conf;
   my $l;

   open(CF,"<$file") || return(0);
   @conf=<CF>;
   close CF;

   foreach $l (@conf){
     if($l =~ /^(.+):(.*)$/){
       $addr{$1}=$2;
     } 
   }
}

sub c_print(){
  foreach $k (keys(%addr)){
   print &expa($k)." - $addr{$k}\n";
  }
}



###############
## text func 

# substitute MAC address
sub subs(){
  open(IN,"<@_") || die "cannot open file @_ \n$!";  
  my ($linen,$line,$pre,$hw);
  while($line=<IN>){
       while(($pre,$hw)=$line =~ /(.*?)(..?$deli..?$deli..?$deli..?$deli..?$deli..?)/){
         $line = $';		# store postmatch
         $hw = zeroexpand(split /$deli/,$hw);
	 $hw=lc("$hw");
	 $name=resolve($hw);
	 $hw=&expa($hw)."($name)";
         $linen .= $pre.$hw;	# store prematch + name
       } # line cykle
       print $linen.$line;	# print linen and end of $line where doesn't match MAC addres
       $linen='';
  }				# file while
  close IN;
}

sub expa{
  my ($ex)=@_;
  $ex =~ /(..)(..)(..)(..)(..)(..)/;
  return("$1:$2:$3:$4:$5:$6");
}
sub zeroexpand{
 my ($k,$res);
   foreach $k (@_){
       $k = length($k) > 1 ? $k : "0".$k;
       $res.="$k";
   }
return $res;
}
###################
## arp-dns graber/resolver
# resolve wrapper     parm:(ff11ff11ff11)
sub resolve{
  my ($hw)=@_;
  my ($name,$vendor,$hwpr);
   #  prelozeni adresy na jmeno
   if($addr{$hw}){
       $name=$addr{$hw};	
   }else{
       #  bere zaznam z DNS jinak vraci unknown
       $name=&resolve_dns($hw);
       #  ulozi do mistni cache
       $addr{$hw}=$name;
   }

   # prelozeni kodu vyrobce
   if($opt_V){
     $hw =~ /^([\da-f]{6})/;
     $hwpr=$1;	

     if($ven{$hwpr}){
       $vendor=$ven{$hwpr};
     }else{
       $vendor=&resolve_ven($hwpr);
       #  ulozi do mistni cache
       $ven{$hwpr}=$vendor;
     } 
     $name = $name." - ".$vendor;
   } 
 return($name);
}

# resolve mac from DNS TXT RR
sub resolve_dns{
  my ($hw)=@_;
  my ($res,@res);
  my ($cmd)=$dns_cmd.' '.$hw.'.'.$dns_parm;
  open(DNS,"$cmd|") || die("DNS txt resolve failed :(\n $!");
  @res=<DNS>;
  close DNS;
  $res=pop(@res);
  if($res=~/^.*?text\s\"(.*)\"/){
    return $1;
  }else{
    return('unknown');
  }
}

# resolve vendor code from DNS TXT RR
sub resolve_ven(){
  my ($hwpr)=@_;
  my ($res,@res);
    my ($cmd)=$dns_cmd.' '.$hwpr.'.'.$dns_parm;
    open(DNS,"$cmd|") || die("DNS txt resolve failed :(\n $!");
    @res=<DNS>;
    close DNS;
    $res=pop(@res);
    if($res=~/^.*?text\s\"(.*)\"/){
      return $1;
    }else{
      return('unknown');
    } # pharse result
}



##
# grab arp adres from range
sub get_arp{
my ($ipaddr) = @_;
my (@hosts,$h,$line);
  @hosts=&make_array($ipaddr);
  foreach $h (@hosts){
    print "pinging $h\n" if($opt_v);
    system("ping -q -c 1 -w $ping_wait $h &>/dev/null");
    open(ARP,"$arp $h|") || die "exec ARP $h failed \n";
    <ARP>;$line=<ARP>;
    close ARP;
    if(($name,$hw)=$line=~/^(.+?)\s+.+\s+(..:..:..:..:..:..)/){
        $hw =~ s/://g;
	$hw=lc($hw);
        if($opt_z){
	   print $hw."\tIN\tTXT\t".$name."\n";
	}else{
	   print $hw.':'.$name."\n";
  	}
    }
  }
}

sub resolv_r(){
my ($ipaddr) = @_;
my (@hosts,$h,$name);
  @hosts=&make_array($ipaddr);

  foreach $h (@hosts){
    print "resolving $h\n" if($opt_v);
    if(($name) = gethostbyaddr(inet_aton($h), AF_INET)){
        print "$h - $name\n";
    }
  }
return 1;
}

sub make_array{
my ($line) = $_[0];
my (@hosts,@tmp,@bip,@eip,$num);

    if ($line !~ /#/){
	if ($line =~ /-/){
	    @tmp = split /-/, $line;
	    @bip = split /\./,$tmp[0];
	    @eip = split /\./,$tmp[1];
	    }
	    else {
	    @bip = split /\./, $line;
	    @eip = split /\./, $line;
	    }
	    $a1 = $bip[0];
	    $b1 = $bip[1];
	    $c1 = $bip[2];
	    $d1 = $bip[3];
	    $num = @eip;
	    if ($num == 1){
		$a2 = $bip[0];
		$b2 = $bip[1];
	        $c2 = $bip[2];
	        $d2 = $eip[0];
		} elsif ($num == 2){
		$a2 = $bip[0];
		$b2 = $bip[1];
	        $c2 = $eip[0];
	        $d2 = $eip[1];
		} elsif ($num == 3){
		$a2 = $bip[0];
		$b2 = $eip[0];
	        $c2 = $eip[1];
	        $d2 = $eip[2];
		} elsif ($num == 4){
		$a2 = $eip[0];
		$b2 = $eip[1];
	        $c2 = $eip[2];
	        $d2 = $eip[3];
		}
		
		check_end();
		$aend = $a2;
		while ($a1 <= $aend){
		    while ($b1 <= $bend){
			while ($c1 <= $cend){
			    while ($d1 <= $dend){
				push (@hosts, "$a1.$b1.$c1.$d1");
				$d1+=1;
				check_end();
			    }
			$c1+=1;
			$d1=0;
			}
		    $b1+=1;
		    $c1=0;
		    }
		$a1+=1;
		$b1=0;
		}
	}
return @hosts;
}
    
sub check_end {
	
    if(($a1==$a2) && ($b1==$b2) && ($c1==$c2)) {
	$dend=$d2;
    } else {
	$dend=255;
    }
    if(($a1==$a2) && ($b1==$b2)){
	$cend=$c2;
    } else {
	$cend=255;
    }
    if($a1==$a2){
	$bend=$b2;
    } else {
	$bend=255;
    }
}

sub test_ip{
my ($ip)=@_;
 if($ip !~ /\d+[\.\-\d]*/){
      die("Usage: $0 192.168.0.10-38\n");
 }
return 1;
}

###############################
########## MAIN ###############
&getopts("hvr:d:g:cf:V:z");

$deli = ($opt_d ? $opt_d : ':');

if($opt_r && &test_ip($opt_r)){
   &resolv_r($opt_r);	
}elsif($opt_g && &test_ip($opt_g)){
   &get_arp($opt_g);
}elsif($opt_c){
   &c_load($conf_file);
   &c_print();
}elsif($opt_f){
    &c_load($conf_file);
    &subs("$opt_f");
}elsif($ARGV[0]){
      &c_load($conf_file);
      
      $hw=lc("$ARGV[0]");
###   $hw =~ s/://g; ## replaced by zeroexpand func.
      $hw = zeroexpand(split /:/,$hw);
      print resolve($hw)."\n";	
   
}elsif($opt_h){
   print "hwa -  hardware address resolver     version $ver
arp -a |hwa		- Stdin->stdout substitution
hwa 00..d4 || 00:..:F9	- Resolve MAC  	    file then dns
hwa -V 			- Resolve vendor code
hwa -d -		- set delimiter up to \"-\" (default \":\" )
hwa -f file		- Read file, resolv it and print on stdout
hwa -r 192.168.0.0-255	- Resolve IP range from PTR
hwa -z 			- DNS zone like output, (-g opt. is required)
hwa -g 192.168.0.0-255	- Grab MAC:name from IP range
hwa -c 			- Show pharsed config $conf_file
hwa -h 			- Show this text
hwa -v 			- Verbose output
"
}else{
    &c_load($conf_file);
    &subs("/dev/stdin");
}





1;

