#!/usr/bin/perl -w
# TODO
my $source = new Nmap::Parser;   #newer scan
my $reference = new Nmap::Parser; #older scan
my $XMLout="";
my $tmpXMLout="";
my $YAMLout="";
my $tmpYAMLout="";
my $Xout="";
my $argpos;
my $YFile;
my $XFile;
my %options;
my %renamedList;
my %renamedListReverse;

sub usage {
	print "Usage: ndiff.pl [OPTION]... FILES\n\n";
	print "Processing:\n";
	print "  -t: Attempt to track a host when it changes IP's but not the services it hosts.\n";
	print "  -xh: Exclude hostnames from output.\n";
	print "Output:\n";
	print "  -oX <filename>: Output difference in a XML format, use '-' to output to stdout.\n";
	print "  -oN <filename>: Output difference in a text format, use '-' to output to stdout.\n";
	print "  '-oN -' is assumed if nothing is specified.\n";
	print "\n";
	print "Examples:\n";
	die "  ndiff.pl -oX - firstweek.xml secondweek.xml thirdweek.xml\n";
}
sub stringXOR {
	my ($strOne, $strTwo) = @_;
	unless(defined $strOne || defined $strTwo ){
		return 0;
	}
	if(defined $strOne && defined $strTwo){
		if($strOne eq $strTwo){
			return 0;
		}else{
			return 1; 
		}
	}else{
		return 1;
	}
}
sub isDifferent {
	#return 0 if they are the same, 1 if they are different
	my ($hostA, $hostB) = @_;
	if($hostA->tcp_port_count == $hostB->tcp_port_count && $hostB->udp_port_count == $hostA->udp_port_count){
		for $ports ($hostA->tcp_ports){
			unless(stringXOR($hostA->tcp_port_state($ports), $hostB->tcp_port_state($ports))){
				$hostAServ = $hostA->tcp_service($ports);
				$hostBServ = $hostB->tcp_service($ports);
				if( stringXOR($hostAServ->name, $hostBServ->name) && stringXOR($hostAServ->product, $hostBServ->product) &&
					stringXOR($hostAServ->version, $hostBServ->version) ) {
					return 1;
				}
			}else{
				return 1;
			}
		}
		for $ports ($hostA->udp_ports){
			unless(stringXOR($hostA->udp_port_state($ports), $hostB->udp_port_state($ports))){
				$hostAServ = $hostA->udp_service($ports);
				$hostBServ = $hostB->udp_service($ports);
				if( stringXOR($hostAServ->name, $hostBServ->name) && stringXOR($hostAServ->product, $hostBServ->product) &&
					stringXOR($hostAServ->version, $hostBServ->version) ) {
					return 1;
				}
			}else{
				return 1;
			}
		}
		return 0;
	}else{
		return 1;
	}
}
sub comparator {
	#this is SOOO unoptimized it isnt even funny
	for $host ($source->all_hosts) {
REF:		for $reHost ($reference->all_hosts) {
			unless ((defined $renamedListReverse{$reHost->addr}) || ($host->addr eq $reHost->addr)){
				unless (isDifferent($host,$reHost)){
					$renamedList{$host->addr}=$reHost->addr;
					$renamedListReverse{$reHost->addr}=$host->addr;
					last REF;
				}
			}
		}
	}
}
sub addPort {
	my ($host, $port, $portType) = @_;
	my ($state,$hostServ);
	eval('$state = $host->'.$portType.'_port_state($port);');
	$tmpYAMLout=$tmpYAMLout."\tPort ".$port."/".$portType." is ".$state." ";
	$tmpXMLout=$tmpXMLout."\t\t<addport protocol=\"".$portType."\" portid=\"".$port."\"> <addstate state=\"".$state."\" />\n";
	eval('$hostServ = $host->'.$portType.'_service($port);');
	if(defined $hostServ->name){
		$tmpYAMLout=$tmpYAMLout."(".$hostServ->name.")\n";
		if(defined $hostServ->product || defined $hostServ->version){
			$tmpYAMLout=$tmpYAMLout."\tPort ".$port."/".$portType." is listening with ";
			if(defined $hostServ->product){
				$tmpYAMLout=$tmpYAMLout.$hostServ->product;
				$tmpXMLout=$tmpXMLout."product=\"".$hostServ->product."\" ";
			}
			if(defined $hostServ->version){
				$tmpXMLout=$tmpXMLout."version=\"".$hostServ->version."\" ";
				$tmpYAMLout=$tmpYAMLout." version ".$hostServ->version;
			}
			$tmpYAMLout=$tmpYAMLout."\n";
		}
		$tmpXMLout=$tmpXMLout."\t\t\t<addservice name=\"".$hostServ->name."\" ";
		$tmpXMLout=$tmpXMLout."method=\"".$hostServ->method."\" conf=\"".$hostServ->confidence."\" /> \n";
	}else{
		$tmpYAMLout=$tmpYAMLout."\n"
	}
	$tmpXMLout=$tmpXMLout."\t\t</addport>\n";
}
sub chgPort {
	my ($newhost, $oldhost, $port, $portType) = @_;
	my ($oldstate,$oldhostServ);
	my ($newstate,$newhostServ);
	eval('$newhostServ = $newhost->'.$portType.'_service($port);');
	eval('$oldhostServ = $oldhost->'.$portType.'_service($port);');
	eval('$oldstate = $oldhost->'.$portType.'_port_state($port);');
	eval('$newstate = $newhost->'.$portType.'_port_state($port);');
	my $statediff=0;
	my $servdiff=0;
	if(stringXOR($newstate, $oldstate)){
		$statediff=1;
	}
	if(stringXOR($newhostServ->name, $oldhostServ->name) || stringXOR($newhostServ->product, $oldhostServ->product) ||
	   stringXOR($newhostServ->version, $oldhostServ->version)) {
		#have the services changed at all? if so, then show the diff
		$servdiff=1;
	}
	if($statediff==1 || $servdiff==1){
		$tmpXMLout=$tmpXMLout."\t\t<chgport protocol=\"".$portType."\" portid=\"".$port."\" >";
		if($statediff==1){
			$tmpXMLout=$tmpXMLout."\t\t<addstate state=\"".$newstate."\" />\n";
			$tmpXMLout=$tmpXMLout."\t\t<delstate state=\"".$oldstate."\" />\n";
			$tmpYAMLout=$tmpYAMLout."\tPort ".$port."/".$portType." is ".$newstate." was ". $oldstate ." ";
		}
		if($servdiff==1){
			if(defined $newhostServ->name){
				$tmpYAMLout=$tmpYAMLout."(".$newhostServ->name.")\n";
				$tmpXMLout=$tmpXMLout."\t\t<addservice name=\"".$newhostServ->name."\" ";
				if(defined $newhostServ->product || defined $newhostServ->version){
					$tmpYAMLout=$tmpYAMLout."\tPort ".$port."/".$portType." is listening with";
					if(defined $newhostServ->product){
						$tmpYAMLout=$tmpYAMLout." ".$newhostServ->product;
						$tmpXMLout=$tmpXMLout."product=\"".$newhostServ->product."\" ";
					}
					if(defined $newhostServ->version){
						$tmpXMLout=$tmpXMLout."version=\"".$newhostServ->version."\" ";
						$tmpYAMLout=$tmpYAMLout." version ".$newhostServ->version;
					}
				}
				$tmpXMLout=$tmpXMLout."method=\"".$newhostServ->method."\" conf=\"".$newhostServ->confidence."\" />";
				$tmpYAMLout=$tmpYAMLout."\n";
			}else {
				$tmpYAMLout=$tmpYAMLout."\n";
			}
			if(defined $oldhostServ->name){
				$tmpYAMLout=$tmpYAMLout."\tPort ".$port."/".$portType." was listening with a ".$oldhostServ->name." server";
				$tmpXMLout=$tmpXMLout."\t\t<delservice name=\"".$oldhostServ->name."\" ";
				if(defined $oldhostServ->product){
					$tmpYAMLout=$tmpYAMLout." - ".$oldhostServ->product;
					$tmpXMLout=$tmpXMLout."product=\"".$oldhostServ->product."\" ";
				}
				if(defined $oldhostServ->version){
					$tmpXMLout=$tmpXMLout."product=\"".$oldhostServ->version."\" ";
					$tmpYAMLout=$tmpYAMLout." version ".$oldhostServ->version;
				}
				$tmpXMLout=$tmpXMLout."method=\"".$oldhostServ->method."\" conf=\"".$oldhostServ->confidence."\" />";
				$tmpYAMLout=$tmpYAMLout."\n";
			}
		}
		$tmpXMLout=$tmpXMLout."</chgport>";
		return 1;
	}
}
sub delPort {
	my($host, $port, $portType) = @_;
	my ($state,$hostServ);
	eval('$state = $host->'.$portType.'_port_state($port)');
	eval('$hostServ = $host->'.$portType.'_service($port);');
	$tmpYAMLout=$tmpYAMLout."\tPort ".$port."/".$portType." was ".$state." ";
	$tmpXMLout=$tmpXMLout."\t\t<delport protocol=\"".$portType."\" portid=\"".$port."\"> <delstate state=\"".$state."\" />\n";
	if(defined $hostServ->name){
		$tmpYAMLout=$tmpYAMLout."(".$hostServ->name.")\n";
		$tmpXMLout=$tmpXMLout."\t\t\t<delservice name=\"".$hostServ->name."\" ";
		if(defined $hostServ->product || defined $hostServ->version){
			$tmpYAMLout=$tmpYAMLout."\tPort ".$port."/".$portType." was listening with ";
			if(defined $hostServ->product){
				$tmpYAMLout=$tmpYAMLout.$hostServ->product;
				$tmpXMLout=$tmpXMLout."product=\"".$hostServ->product."\" ";
			}
			if(defined $hostServ->version){
				$tmpXMLout=$tmpXMLout." version=\"".$hostServ->version."\" ";
				$tmpYAMLout=$tmpYAMLout." version ".$hostServ->version;
			}
		}
		$tmpYAMLout=$tmpYAMLout."\n";
		$tmpXMLout=$tmpXMLout."method=\"".$hostServ->method."\" conf=\"".$hostServ->confidence."\" /> \n";
	}else{
		$tmpYAMLout=$tmpYAMLout."\n";
	}
	$tmpXMLout=$tmpXMLout."\t\t</delport>\n";
}
sub addHost {
	my ($NewHost) = shift;
	my $hostServ;
	$tmpYAMLout=$tmpYAMLout.$NewHost->addr;
	if(stringXOR($NewHost->hostname(),"0")){
		unless(defined $options{"xh"}){
		$tmpYAMLout=$tmpYAMLout." (".$NewHost->hostname().")";
		}
	}
	$tmpYAMLout=$tmpYAMLout.":\n\tHost has come online\n";
	$tmpXMLout=$tmpXMLout."\t<addhost><address addr=\"".$NewHost->addr."\" addrtype=\"".$NewHost->addrtype."\" />\n";
	for $port ($NewHost->tcp_ports) {
		addPort($NewHost,$port,"tcp");
	}
	for $port ($NewHost->udp_ports) {
		addPort($NewHost,$port,"udp");
	}
	$tmpXMLout=$tmpXMLout."\t</addhost>\n";
	$XMLout=$XMLout.$tmpXMLout;
	$tmpXMLout="";
	$YAMLout=$YAMLout.$tmpYAMLout;
	$tmpYAMLout="";
}
sub delHost {
	my ($OldHost)  = shift;
	$tmpYAMLout=$tmpYAMLout.$OldHost->addr;
	if(stringXOR($OldHost->hostname(),"0") ){
		unless(defined $options{"xh"}){
		$tmpYAMLout=$tmpYAMLout." (".$OldHost->hostname().")";
		}
	}
	$tmpYAMLout=$tmpYAMLout.":\n\tHost has gone offline\n";
	$tmpXMLout=$tmpXMLout."\t<delhost><address addr=\"".$OldHost->addr."\" addrtype=\"".$OldHost->addrtype."\" />\n";
	for $port ($OldHost->tcp_ports) {
		delPort($OldHost,$port,"tcp");
	}
	for $port ($OldHost->udp_ports) {
		delPort($OldHost,$port,"udp");
	}
	$YAMLout=$YAMLout.$tmpYAMLout;
	$tmpYAMLout="";
	$tmpXMLout=$tmpXMLout."\t</delhost>\n";
	$XMLout=$XMLout.$tmpXMLout;
	$tmpXMLout="";
}
sub chgHost {
	my($newHost, $oldHost) = @_;
	my $hostServ;

	if(isDifferent($newHost,$oldHost)){
		$tmpYAMLout=$tmpYAMLout.$newHost->addr." ";
		unless(defined $options{"xh"}){
		if(stringXOR($oldHost->hostname(),"0") && stringXOR($newHost->hostname(),"0")){
			if($oldHost->hostname() eq $newHost->hostname()){
				$tmpYAMLout=$tmpYAMLout."(".$oldHost->hostname().")";
			}else{
				$tmpYAMLout=$tmpYAMLout."(".$oldHost->hostname()." -> ".$oldHost->hostname().")";
			}
		}elsif(stringXOR($oldHost->hostname(),"0")){
			$tmpYAMLout=$tmpYAMLout."(".$oldHost->hostname()." -> nil)";
		}elsif(stringXOR($newHost->hostname(),"0")){
			$tmpYAMLout=$tmpYAMLout."(nil -> ".$newHost->hostname().")";
		}else{
			$tmpYAMLout=$tmpYAMLout.":\n";
		}
		}
		$tmpXMLout=$tmpXMLout."\t<chghost> <address addr=\"". $newHost->addr ."\" addrtype=\"". $newHost->addrtype."\" />\n";
		for $port ($newHost->tcp_ports) {
			if(defined $oldHost->tcp_port_state($port)){
				chgPort($newHost, $oldHost, $port, "tcp");
			}else{
				addPort($newHost,$port,"tcp");
			}
		}
		for $port ($oldHost->tcp_ports) {
			unless(defined $newHost->tcp_port_state($port)){
				delPort($oldHost,$port,"tcp");
			}
		}
		for $port ($newHost->udp_ports) {
			if(defined $oldHost->udp_port_state($port)){
				chgPort($newHost, $oldHost, $port, "udp");
				
			}else{
				addPort($newHost,$port,"udp");
			}
		}
		for $port ($oldHost->udp_ports) {
			unless(defined $newHost->udp_port_state($port)){
				delPort($oldHost,$port,"udp");
			}
		}
		$tmpXMLout="";
		$tmpXMLout="";
		$YAMLout=$YAMLout.$tmpYAMLout;
		$tmpYAMLout="";
	}
}
sub movehost {
	my($Host) = shift;
	$YAMLout=$YAMLout.$Host->addr." ";
	unless(defined $options{"xh"}){
	if(stringXOR($Host->hostname(),"0")){
		$YAMLout=$YAMLout."(".$Host->hostname().")";
	}
	}
	$YAMLout=$YAMLout.":\n\tHost has been renamed from ".$renamedList{$Host->addr}."\n";
	$XMLout=$XMLout."\t<chghost>"."<address addr=\"".$Host->addr."\" addrtype=\"".$Host->addrtype." oldaddr=\"".$renamedList{$Host->addr}."\" />\n\t</chghost>\n";
}
sub main {
	if(defined $options{"track"}){
		comparator();	 #check for changed hosts
	}
	my %listedHosts; #a list of hosts we have already displayed the diff of
	$YAMLout=$YAMLout."---\n";
	$YAMLout=$YAMLout."Date: ".$reference->get_session->time_str." -> ".$source->get_session->time_str."\n";
	$XMLout=$XMLout."<scandiff startdate=\"".$source->get_session->time_str."\" enddate=\"".$reference->get_session->time_str."\" >\n";
	for $host ($source->all_hosts()) {
		if(defined $renamedList{$host->addr}) {
			movehost($host);
		}elsif(defined $renamedListReverse{$host->addr}){
		}else {
			unless (defined $listedHosts{$host->addr}) {
				$listedHosts{$host->addr}=1;
				my $tmpRefHost = $reference->get_host($host->addr);
				if(defined $tmpRefHost) {
					if($tmpRefHost->status eq "down" && $host->status eq "up"){
						addHost($host);
					}elsif($tmpRefHost->status eq "up" && $host->status eq "down"){
						delHost($tmpRefHost);
					}elsif($tmpRefHost->status eq "up" && $host->status eq "up"){
						chgHost($host, $tmpRefHost);
					}
				} else {
					addHost($host);
				}
			}
		}
	}
	for $host ($reference->all_hosts()) {
		unless (defined  $listedHosts{$host->addr} || defined $renamedListReverse{$host->addr}) {
			$listedHosts{$host->addr}=1;
			my $tmpRefHost = $source->get_host($host->addr);
			if(defined $tmpRefHost) {
				if($tmpRefHost->status eq "down" && $host->status eq "up"){
					delHost($host);
				}
			} else {
				delHost($host);
			}
		}
	}
	$XMLout=$XMLout."</scandiff>\n";
}

unless (defined $ARGV[1]){
	usage();
}

for($argpos=0; $argpos <= $#ARGV; $argpos++) {
	if($ARGV[$argpos] eq "-oN"){
		$argpos++;
		$options{"YF"}=1;
		$YFile=$ARGV[$argpos];
	}elsif($ARGV[$argpos] eq "-oX"){
		$argpos++;
		$options{"XF"}=1;
		$XFile=$ARGV[$argpos];
	}elsif($ARGV[$argpos] eq "-t"){
		$options{"track"}=1;
	}elsif($ARGV[$argpos] eq "-xh"){
		$options{"xh"}=1;
	}else{
		if($argpos==0){
			$options{"YF"}=1;
			$YFile="-";
		}
		last;
	}
}

#this next line should be imporved
if(defined $options{"YF"}){
	open(yOUTFILE, ">".$YFile);
}
if(defined $options{"XF"}){
	open(xOUTFILE, ">".$XFile);
	print xOUTFILE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE nmapdiff SYSTEM \"nmap.dtd\">\n<nmapdiff>\n";
}
for(; $argpos < $#ARGV; $argpos++) {
	$YAMLout="";
	$XMLout="";
	$reference->parsefile($ARGV[$argpos]);
	$source->parsefile($ARGV[$argpos+1]);
	main();
	$source->purge;
	$reference->purge;
	#todo, I may want to escape more things
	$XMLout =~ s/\&/&amp;/g; 
	if(defined $options{"YF"}){
		print yOUTFILE $YAMLout;
	}
	if(defined $options{"XF"}){
		print xOUTFILE $XMLout;
	}
}

if(defined $options{"XF"}){
       print xOUTFILE "</nmapdiff>\n";
       close(xOUTFILE);
}
if(defined $options{"YF"}){
	print yOUTFILE "...\n";
	close(yOUTFILE);
}


#########################################################
### All code beneath this line is Nmap::Parser<http://nmapparser.wordpress.com>
### Copyright (C) 2003-2008 Anthony G Persaud L<http://www.anthonypersaud.com>
#########################################################
### MIT License
###
### Permission is hereby granted, free of charge, to any person obtaining a copy
### of this software and associated documentation files (the "Software"), to deal
### in the Software without restriction, including without limitation the rights
### to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
### copies of the Software, and to permit persons to whom the Software is
### furnished to do so, subject to the following conditions:
###
### The above copyright notice and this permission notice shall be included in
### all copies or substantial portions of the Software.
###
### THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
### IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
### FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
### AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
### LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
### OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
### THE SOFTWARE.
###
#########################################################

package Nmap::Parser;


use strict;
use XML::Twig;
use Storable qw(dclone);
use vars qw($VERSION %D);


$VERSION = 1.05;


#----------------------------------------
#		NEW			-
#----------------------------------------

sub new {

my ($class,$self) = shift;
    $class        = ref($class) || $class;

%{$self->{HOSTS}}   = ();
%{$self->{SESSION}} = ();

$self->{twig}     = new XML::Twig(
	start_tag_handlers 	=>
		{nmaprun => \&_nmaprun_start_tag_hdlr },
	twig_roots 		=> {
		scaninfo => \&_scaninfo_tag_hdlr,
		finished => \&_finished_tag_hdlr,
		host 	 => \&_host_tag_hdlr
				},
	ignore_elts 	=> {
		addport 	=> 1,
		debugging	=> 1,
		verbose		=> 1,
		hosts		=> 1
		}
		);

bless ($self,$class);
return $self;
}



sub callback {
	my $self = shift;
	my $callback = shift; #first arg is CODE
	if(ref($callback) eq 'CODE'){
		$self->{callback}{coderef} = $callback;
		$self->{callback}{is_registered} = 1;
	} else {
		$self->{callback}{is_registered} = 0;
	}
	
	#returns if a callback is registered or not
	return $self->{callback}{is_registered};
}


################################################################################
##			PARSE METHODS					      ##
################################################################################
#Safe parse and parsefile will return $@ which will contain the error
#that occured if the parsing failed (it might be empty when no error occurred)

sub _init {
	my $self = shift;
	$D{callback} = $self->{callback};
}

sub _clean {
	my $self = shift;
	$self->{SESSION} = dclone($D{$$}{SESSION}) if($D{$$}{SESSION});
	$self->{HOSTS}   = dclone($D{$$}{HOSTS}  ) if($D{$$}{HOSTS}  );
	delete $D{$$};
	delete $D{callback};
}

sub parse {
	my $self = shift;
	$self->_init();
	$self->{twig}->safe_parse(@_);
	if($@){die $@;}
	$self->_clean();
	$self->purge;
	return $self;
	}

sub parsefile {
	my $self = shift;
	$self->_init();
	$self->{twig}->safe_parsefile(@_);
	if($@){die $@;}
	$self->_clean();
	$self->purge;
	return $self;
	}

sub parsescan {
	my $self = shift;
	my $nmap = shift;
	my $args = shift;
	my @ips = @_;
	my $FH;

	if($args =~ /-o(?:X|N|G)/)
	{die "[Nmap-Parser] Cannot pass option '-oX', '-oN' or '-oG' to parsecan()";}
	my $cmd = "$nmap $args -v -v -v -oX - ".(join ' ',@ips);
	open $FH, "$cmd |" || die "[Nmap-Parser] Could not perform nmap scan - $!";
	$self->_init();
	$self->parse($FH);
	close $FH;
	$self->_clean();
	$self->purge;
	return $self;
	
	}


sub purge { my $self = shift; $self->{twig}->purge; return $self;  }

sub ipv4_sort {
my $self = shift;

return (sort {
	my @ipa = split('\.',$a);
	my @ipb = split('\.',$b);
		$ipa[0] <=> $ipb[0] ||
		$ipa[1] <=> $ipb[1] ||
		$ipa[2] <=> $ipb[2] ||
		$ipa[3] <=> $ipb[3]
	} @_);
}


#MAIN SCAN INFORMATION
sub get_session {
	my $self = shift;
	my $obj = Nmap::Parser::Session->new($self->{SESSION});
	return $obj;
	}

#HOST STUFF
sub get_host {
	my ($self,$ip) = (@_);
	if($ip eq ''){
		warn "[Nmap-Parser] No IP address given to get_host()\n";return undef;
	}
	$self->{HOSTS}{$ip};	
}

sub del_host {
	my ($self,$ip) = (@_);
	if($ip eq ''){
		warn "[Nmap-Parser] No IP address given to del_host()\n";
		return undef;
	}
	delete $self->{HOSTS}{$ip};
}

sub all_hosts {
	my $self = shift;
	my $status = shift || '';
	
	return (values %{$self->{HOSTS}}) if($status eq '');
	
	my @hosts = grep {$_->{status} eq $status} (values %{$self->{HOSTS}});
	return @hosts;
}

sub get_ips {
	my $self = shift;
	my $status = shift || '';
	
	return $self->ipv4_sort(keys %{$self->{HOSTS}}) if($status eq '');
	
	my @hosts = grep {$self->{HOSTS}{$_}{status} eq $status} (keys %{$self->{HOSTS}});
	return $self->ipv4_sort(@hosts);
	
}


################################################################################
##				Parsing	Tag Handlers        		      ##
################################################################################

sub _nmaprun_start_tag_hdlr {

	my ($twig, $tag) = @_;
	
$D{$$}{SESSION}{start_time}  = $tag->{att}->{start};
$D{$$}{SESSION}{nmap_version}= $tag->{att}->{version};
$D{$$}{SESSION}{start_str}   = $tag->{att}->{startstr};
$D{$$}{SESSION}{xml_version} = $tag->{att}->{xmloutputversion};
$D{$$}{SESSION}{scan_args}   = $tag->{att}->{args};
$D{$$}{SESSION}              = Nmap::Parser::Session->new($D{$$}{SESSION});

$twig->purge;	

}

sub _scaninfo_tag_hdlr {
	my ($twig, $tag) = @_;
	my $type        = $tag->{att}->{type};
        my $proto       = $tag->{att}->{protocol};
	my $numservices = $tag->{att}->{numservices};
	
	if(defined($type)){ #there can be more than one type in one scan
		$D{$$}{SESSION}{type}{$type}        = $proto;
		$D{$$}{SESSION}{numservices}{$type} = $numservices;
	}
	$twig->purge;
}



sub _finished_tag_hdlr {
	my ($twig, $tag) = @_;
	$D{$$}{SESSION}{finish_time} = $tag->{att}->{time};
	$D{$$}{SESSION}{time_str}     = $tag->{att}->{timestr};
	$twig->purge;
}



#parses all the host information in one swoop (calling __host_*_tag_hdlrs)
sub _host_tag_hdlr {
	my ($twig,$tag) = @_;
	my $id = undef;
	
	return undef unless(defined $tag);
	#GET ADDRESS INFO
	my $addr_hashref;
	$addr_hashref = __host_addr_tag_hdlr($tag);
	#use this as the identifier
	$id           = $addr_hashref->{ipv4} ||
			$addr_hashref->{ipv6} ||
			$addr_hashref->{mac}; #worstcase use MAC
	
	$D{$$}{HOSTS}{$id}{addrs} = $addr_hashref;
	
	return undef unless(defined($id) || $id ne '');
	
	#GET HOSTNAMES
	$D{$$}{HOSTS}{$id}{hostnames} = __host_hostnames_tag_hdlr($tag);
	
	#GET STATUS
	$D{$$}{HOSTS}{$id}{status} = $tag->first_child('status')->{att}->{state};
    
	#CONTINUE PROCESSING IF STATUS IS UP - OTHERWISE NO MORE XML
	if(lc($D{$$}{HOSTS}{$id}{status}) eq 'up'){
	
	    $D{$$}{HOSTS}{$id}{ports}         = __host_port_tag_hdlr($tag);
	    $D{$$}{HOSTS}{$id}{os}            = __host_os_tag_hdlr($tag);
	    $D{$$}{HOSTS}{$id}{uptime}        = __host_uptime_tag_hdlr($tag);
	    $D{$$}{HOSTS}{$id}{tcpsequence}   = __host_tcpsequence_tag_hdlr($tag);
	    $D{$$}{HOSTS}{$id}{ipidsequence}  = __host_ipidsequence_tag_hdlr($tag);
	    $D{$$}{HOSTS}{$id}{tcptssequence} = __host_tcptssequence_tag_hdlr($tag);
	
	}	
	#CREATE HOST OBJECT FOR USER
	$D{$$}{HOSTS}{$id} = Nmap::Parser::Host->new($D{$$}{HOSTS}{$id});
	
	if($D{callback}{is_registered}){
		&{$D{callback}{coderef}}($D{$$}{HOSTS}{$id});
		delete $D{$$}{HOSTS}{$id};
	}

	$twig->purge;
    
}







sub __host_addr_tag_hdlr {
	my $tag = shift;
	my $addr_hashref;
	#children() will return all children with tag name address
	for my $addr ($tag->children('address')){
		if(lc($addr->{att}->{addrtype}) eq 'mac')
		{
			#we'll assume for now, only 1 MAC address per system
			$addr_hashref->{mac}{addr} = $addr->{att}->{addr};
			$addr_hashref->{mac}{vendor} = $addr->{att}->{vendor};
		}
		elsif(lc($addr->{att}->{addrtype}) eq 'ipv4') {
			$addr_hashref->{ipv4} = $addr->{att}->{addr};
		} #support for ipv6? we'll see
		elsif(lc($addr->{att}->{addrtype}) eq 'ipv6') {
			$addr_hashref->{ipv6} = $addr->{att}->{addr};
		}

	}

return $addr_hashref;
	}



sub __host_hostnames_tag_hdlr {
	my $tag = shift;
	
	my $hostnames_tag = $tag->first_child('hostnames');
	return undef unless(defined $hostnames_tag);
	
	my @hostnames;
	
	for my $name ($hostnames_tag->children('hostname'))
	{
		push @hostnames, $name->{att}->{name};
	}
	
	return \@hostnames;

}


sub __host_port_tag_hdlr {
	my $tag = shift;
	my ($port_hashref,$ports_tag);
	
	$ports_tag = $tag->first_child('ports');
	
	return undef unless(defined $ports_tag);
	
	#Parsing Extraports
	my $extraports_tag = $ports_tag->first_child('extraports');
	if(defined $extraports_tag && $extraports_tag ne ''){
		$port_hashref->{extraports}{state} = $extraports_tag->{att}->{state};
		$port_hashref->{extraports}{count} = $extraports_tag->{att}->{count};
	}
	
	#Parsing regular port information
	
	my ($tcp_port_count, $udp_port_count) = (0,0);
	
	for my $port_tag ($ports_tag->children('port')){
		my $proto  = $port_tag->{att}->{protocol};
		my $portid = $port_tag->{att}->{portid};
		my $state  = $port_tag->first_child('state');
		my $owner  = $port_tag->first_child('owner') || undef;
		
		$tcp_port_count++ if($proto eq 'tcp');
		$udp_port_count++ if($proto eq 'udp');
		
		$port_hashref->{$proto}{$portid}{state} = $state->{att}->{state} || 'unknown' 
			if($state ne '');
		
		#GET SERVICE INFORMATION
		$port_hashref->{$proto}{$portid}{service}        = __host_service_tag_hdlr($port_tag,$portid)
			if(defined($proto) && defined($portid));
		
		#GET OWNER INFORMATION
		$port_hashref->{$proto}{$portid}{service}{owner} = $owner->{att}->{name}
			if(defined($owner));
			
		#These are added at the end, otherwise __host_service_tag_hdlr will overwrite
		#GET PORT STATE
		
		
				
	}
	
	$port_hashref->{tcp_port_count} = $tcp_port_count;
	$port_hashref->{udp_port_count} = $udp_port_count;
	
	return $port_hashref;
	
	
}


sub __host_service_tag_hdlr {
	my $tag = shift;
	my $portid = shift; #need a way to remember what port this service runs on
	my $service = $tag->first_child('service[@name]');
	my $service_hashref;
	$service_hashref->{port}       = $portid;
	
	if(defined $service){
	$service_hashref->{name}       = $service->{att}->{name}  || 'unknown';
	$service_hashref->{version}    = $service->{att}->{version};
	$service_hashref->{product}    = $service->{att}->{product};
	$service_hashref->{extrainfo}  = $service->{att}->{extrainfo};
	$service_hashref->{proto}      = $service->{att}->{proto} || 'unknown';
	$service_hashref->{rpcnum}     = $service->{att}->{rpcnum};
	$service_hashref->{tunnel}     = $service->{att}->{tunnel};
	$service_hashref->{method}     = $service->{att}->{method};
	$service_hashref->{confidence} = $service->{att}->{conf};
	}

	return $service_hashref;
}


sub __host_os_tag_hdlr {
	my $tag = shift;
	my $os_tag = $tag->first_child('os');
	my $os_hashref;
	my $portused_tag;
	
	if(defined $os_tag){
	#get the open port used to match os
	$portused_tag = $os_tag->first_child("portused[\@state='open']"); 
	$os_hashref->{portused}{open} = $portused_tag->{att}->{portid}   if(defined $portused_tag);
	
	#get the closed port used to match os
	$portused_tag = $os_tag->first_child("portused[\@state='closed']");
	$os_hashref->{portused}{closed} = $portused_tag->{att}->{portid} if(defined $portused_tag);
	
	#This will go in Nmap::Parser::Host::OS
	my $osmatch_index = 0;
	for my $osmatch ($os_tag->children('osmatch')){
		$os_hashref->{osmatch_name}   [$osmatch_index] = $osmatch->{att}->{name};
		$os_hashref->{osmatch_name_accuracy}[$osmatch_index] = $osmatch->{att}->{accuracy};
		$osmatch_index++;
		}
	$os_hashref->{'osmatch_count'} = $osmatch_index;
	
	#parse osclass tags
	my $osclass_index = 0;
        for my $osclass ($os_tag->children('osclass')){
		$os_hashref->{osclass_osfamily}[$osclass_index] = $osclass->{att}->{osfamily};
		$os_hashref->{osclass_osgen}   [$osclass_index] = $osclass->{att}->{osgen};
		$os_hashref->{osclass_vendor}  [$osclass_index] = $osclass->{att}->{vendor};
		$os_hashref->{osclass_type}    [$osclass_index] = $osclass->{att}->{type};
		$os_hashref->{osclass_class_accuracy}[$osclass_index] = $osclass->{att}->{accuracy};
		$osclass_index++;		
	}
	$os_hashref->{'osclass_count'} = $osclass_index;
	}
	
	return $os_hashref;	
	
}

sub __host_uptime_tag_hdlr {
	my $tag = shift;
	my $uptime = $tag->first_child('uptime');
	my $uptime_hashref;
	
	if(defined $uptime){
		$uptime_hashref->{seconds}  = $uptime->{att}->{seconds};
		$uptime_hashref->{lastboot} = $uptime->{att}->{lastboot};
		
	}
	
	return $uptime_hashref;
	
}


sub __host_tcpsequence_tag_hdlr {
	my $tag = shift;
	my $sequence = $tag->first_child('tcpsequence');
	my $sequence_hashref;
	return undef unless($sequence);
	$sequence_hashref->{class} = $sequence->{att}->{class};
	$sequence_hashref->{values} = $sequence->{att}->{values};
	$sequence_hashref->{index} = $sequence->{att}->{index};
	
	return $sequence_hashref;
	
	}


sub __host_ipidsequence_tag_hdlr {
	my $tag = shift;
	my $sequence = $tag->first_child('ipidsequence');
	my $sequence_hashref;
	return undef unless($sequence);
	$sequence_hashref->{class} = $sequence->{att}->{class};
	$sequence_hashref->{values} = $sequence->{att}->{values};
	return $sequence_hashref;
	
	}


sub __host_tcptssequence_tag_hdlr {
	my $tag = shift;
	my $sequence = $tag->first_child('tcptssequence');
	my $sequence_hashref;
	return undef unless($sequence);
	$sequence_hashref->{class} = $sequence->{att}->{class};
	$sequence_hashref->{values} = $sequence->{att}->{values};
	return $sequence_hashref;
	}



################################################################################
##			Nmap::Parser::Session			              ##
################################################################################

package Nmap::Parser::Session;
use vars qw($AUTOLOAD);

sub new {
my $class = shift;
$class    = ref($class) || $class;
my $self  =  shift      || {};
bless ($self,$class);
return $self;
}

#Support for:
#start_time, start_str, finish_time, time_str, nmap_version, xml_version, scan_args
sub AUTOLOAD {
	(my $param = $AUTOLOAD) =~ s{.*::}{}xms;
	return if($param eq 'DESTROY');
	no strict 'refs';
	*$AUTOLOAD = sub {return $_[0]->{lc $param}};	
	goto &$AUTOLOAD;
}


sub numservices {
	my $self = shift;
	my $type = shift || ''; #(syn|ack|bounce|connect|null|xmas|window|maimon|fin|udp|ipproto)

	return unless(ref($self->{numservices}) eq 'HASH');

	if($type ne ''){return $self->{numservices}{$type};}
	else {
		my $total = 0;
		for (values %{$self->{numservices}}){$total +=$_;}
		return $total;
	}#(else) total number of services together
}


sub scan_types 		{return sort {$a cmp $b} (keys %{$_[0]->{type}}) if(ref($_[0]->{type}) eq 'HASH');}
sub scan_type_proto 	{return $_[1] ? $_[0]->{type}{$_[1]} : undef;}

package Nmap::Parser::Host;
use vars qw($AUTOLOAD);

sub new {
my $class = shift;
$class    = ref($class) || $class;
my $self  =  shift      || {};
bless ($self,$class);
return $self;
}

sub status {return $_[0]->{status};}
sub addr {my $default =  $_[0]->{addrs}{ipv4} || $_[0]->{addrs}{ipv6}; return $default;}
sub addrtype {
	if($_[0]->{addrs}{ipv4}){return 'ipv4';}
	elsif($_[0]->{addrs}{ipv6}){return 'ipv6';}
	}

sub ipv4_addr {return $_[0]->{addrs}{ipv4};}
sub ipv6_addr {return $_[0]->{addrs}{ipv6};}

sub mac_addr {return $_[0]->{addrs}{mac}{addr};}
sub mac_vendor {return $_[0]->{addrs}{mac}{vendor};}

#returns the first hostname
sub hostname  {
	my $self = shift;
	my $index = shift || 0;
	if(ref($self->{hostnames}) ne 'ARRAY'){return '';}
	if(scalar @{ $self->{hostnames}} <= $index){
	$index = scalar @{ $self->{hostnames}} -1;
	}
	return $self->{hostnames}[$index] if(scalar @{ $self->{hostnames} });
}

sub all_hostnames {return @{$_[0]->{hostnames}};}
sub extraports_state {return $_[0]->{ports}{extraports}{state};}
sub extraports_count {return $_[0]->{ports}{extraports}{count};}



sub _get_ports {
	my $self = shift;
	my $proto = pop; #param might be empty, so this goes first
	my $state = lc(shift);    #open, filtered, closed or any combination
	my @matched_ports = ();
	
	#if $state eq '', then tcp_ports or udp_ports was called for all ports
	#therefore, only return the keys of all ports found
	if($state eq ''){return sort {$a <=> $b} (keys %{ $self->{ports}{$proto} }) }
	
	#the port parameter can be set to either any of these also 'open|filtered'
	#can count as 'open' and 'filetered'. Therefore I need to use a regex from now on
	#if $param is empty, then all ports match.

	for my $portid (keys %{ $self->{ports}{$proto} }){
			
		#escape metacharacters ('|', for example in: open|filtered)
		#using \Q and \E
		push(@matched_ports, $portid)
		if($self->{ports}{$proto}{$portid}{state} =~ /\Q$state\E/);
		
	}
		
	return sort {$a <=> $b} @matched_ports;
	
}

sub _get_port_state {
	my $self = shift;
	my $proto = pop; #portid might be empty, so this goes first
	my $portid = lc(shift);    
	
	return undef unless(exists $self->{ports}{$proto}{$portid});
	return $self->{ports}{$proto}{$portid}{state};
	
	}

#changed this to use _get_ports since it was similar code
sub tcp_ports { return _get_ports(@_,'tcp');}
sub udp_ports { return _get_ports(@_,'udp');}

sub tcp_port_count {return $_[0]->{ports}{tcp_port_count};}
sub udp_port_count {return $_[0]->{ports}{udp_port_count};}

sub tcp_port_state {return _get_port_state(@_,'tcp');}
sub udp_port_state {return _get_port_state(@_,'udp');}

sub tcp_service {
	my $self = shift;
	my $portid = shift;
	if($portid eq ''){
	warn "[Nmap-Parser] No port number passed to tcp_service()\n";	
		return undef;}
	return Nmap::Parser::Host::Service->new(
			$self->{ports}{tcp}{$portid}{service}
			);
		 }

sub udp_service {
	my $self = shift;
	my $portid = shift;
	if($portid eq ''){
	warn "[Nmap-Parser] No port number passed to udp_service()\n";	
		return undef;}
	return Nmap::Parser::Host::Service->new(
			$self->{ports}{udp}{$portid}{service}
			);

		 }


#usually the first one is the highest accuracy

sub os_sig {return Nmap::Parser::Host::OS->new($_[0]->{os});}


#Support for:
#tcpsequence_class, tcpsequence_values, tcpsequence_index,
#ipidsequence_class, ipidsequence_values, tcptssequence_values,
#tcptssequence_class, uptime_seconds, uptime_lastboot
#tcp_open_ports, udp_open_ports, tcp_filtered_ports, udp_filtered_ports,
#tcp_closed_ports, udp_closed_ports
sub AUTOLOAD {
	(my $param = $AUTOLOAD) =~ s{.*::}{}xms;
	return if($param eq 'DESTROY');
	my($type,$val) = split /_/, lc($param);
	no strict 'refs';
	
	if( ($type eq 'tcp' || $type eq 'udp') && ($val eq 'open' || $val eq 'filtered' || $val eq 'closed') ){
					
		*$AUTOLOAD = sub {return _get_ports($_[0], $val, $type);};	
		goto &$AUTOLOAD;
		
	} elsif(defined $type && defined $val) {
		*$AUTOLOAD = sub {return $_[0]->{$type}{$val}};	
		goto &$AUTOLOAD;
	} else {die '[Nmap-Parser] method ->'.$param."() not defined!\n";}
}


package Nmap::Parser::Host::Service;
use vars qw($AUTOLOAD);

sub new {
my $class = shift;
$class    = ref($class) || $class;
my $self  =  shift      || {};
bless ($self,$class);
return $self;
}


#Support for:
#name port proto rpcnum owner version product extrainfo tunnel method confidence
#this will now only load functions that will be used. This saves
#on delay (increase speed) and memory

sub AUTOLOAD {
	(my $param = $AUTOLOAD) =~ s{.*::}{}xms;
	return if($param eq 'DESTROY');
	no strict 'refs';
	
	*$AUTOLOAD = sub {return $_[0]->{lc $param}};	
	goto &$AUTOLOAD;
}



package Nmap::Parser::Host::OS;
use vars qw($AUTOLOAD);

sub new {
my $class = shift;
$class    = ref($class) || $class;
my $self  =  shift      || {};
bless ($self,$class);
return $self;
}

sub portused_open   {return $_[0]->{portused}{open};} 
sub portused_closed {return $_[0]->{portused}{closed};}

sub name_count {return $_[0]->{osmatch_count};}

sub all_names {
    my $self = shift;
    @_=();
    if($self->{osclass_count} < 1){return @_;}
    if(ref($self->{osmatch_name}) eq 'ARRAY'){
    return sort @{$self->{osmatch_name}};}

} #given by decreasing accuracy

sub class_count {return $_[0]->{osclass_count};}

#Support for:
#name,names, name_accuracy, osfamily, vendor, type, osgen, class_accuracy
sub AUTOLOAD {
	(my $param = $AUTOLOAD) =~ s{.*::}{}xms;
	return if($param eq 'DESTROY');
	no strict 'refs';
	$param = lc($param);
	
	$param = 'name' if($param eq 'names');
	if($param eq 'name' || $param eq 'name_accuracy'){
		
		*$AUTOLOAD = sub {_get_info($_[0],$_[1],$param,'osmatch');};		
		goto &$AUTOLOAD;
	} else {

		*$AUTOLOAD = sub {_get_info($_[0],$_[1],$param,'osclass');};	
		goto &$AUTOLOAD;
	}
}

sub _get_info {
	my($self,$index,$param,$type) = @_;
	$index ||= 0;
	#type is either osclass or osmatch
	if($index >= $self->{$type.'_count'}){$index = $self->{$type.'_count'}-1;}
	return $self->{$type.'_'.$param}[$index];		
}

1;

__END__
