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

## 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.4";

#######
## 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");

$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, (-r 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;


