package NmapParser; 

use strict;
use warnings;

use XML::NmapParser::Host; 
use XML::NmapParser::Host::Service;
use XML::NmapParser::Host::OS;
use XML::NmapParser::Host::Script;
use XML::NmapParser::Host::TraceHop;

use Exporter qw(import);

our $VERSION = "0.1";  
our @EXPORT_OK = qw(new parse livehosts RecursiveXMLtags);


sub new {
    my $pkg = shift;
    my $self = bless {}, $pkg;
    $self->initialize(@_);
    $self;
}

sub initialize {
    my $self = shift;
    $self->{stem} = shift;
    $self->{english} = shift;
}
  
 
sub parse {
	
	my ($self,$filename) = @_;
	
	$self->{parsed} = {};
	 
	my $parser = XML::LibXML->new();
	my $nmapXML = $parser->parse_file($filename); 
	
	foreach ( $nmapXML->findnodes('/nmaprun') ) {
		if ( $_->hasAttributes() ) { 
			for my $attribute ( $_->attributes() ) { $self->{parsed}{nmaprun}{$attribute->name} = $attribute->getValue;} 
		}
	} 
	foreach ( $nmapXML->findnodes('/nmaprun/scaninfo') ) {
		if ( $_->hasAttributes() ) { 
			for my $attribute ( $_->attributes() ) {$self->{parsed}{scaninfo}{$attribute->name} = $attribute->getValue;} 
		}
	}
	
	foreach ( $nmapXML->findnodes('/nmaprun/runstats')) {
		if ( $_->hasAttributes() ) { 
			for my $attribute ( $_->attributes() ) { $self->{parsed}{runstats}{$attribute->name} = $attribute->getValue; } 
		}
		if ( $_->nonBlankChildNodes() ) {
			for my $child ( $_->nonBlankChildNodes() ) {
				my $name = $child->nodeName;
				if ( $child->hasAttributes() ) { 
					for my $attribute ( $child->attributes() ) { $self->{parsed}{runstats}{$name}{$attribute->name} = $attribute->getValue;}   
				}
			} 
		} 
	}
	
	#  need to add logic to get Verbose and debugging data.....
	# 
		
	#----------------------------------------------------------------------------------------------------------------------------------------
	# room for cleaner code below.....
	my @Hosts; 
	
	for my $HOST ( $nmapXML->findnodes('/nmaprun/host') ) {
		my %host;
		if ( $HOST->hasAttributes() ) { 
			for my $attribute ( $HOST->attributes() ) { $host{$attribute->name} = $attribute->getValue; }
		}
		foreach ($HOST->nonBlankChildNodes()) {
			my $name = $_->nodeName;
			
			if ( $_->hasAttributes() ) {
				if ( defined($host{$name})) { 
					# convert single hash to array of hashs.....
					my %hash;
					my @current;
					push(@current, {%{$host{$name}}});
					
					for my $attribute ( $_->attributes() ) {
						$hash{$attribute->name} = $attribute->getValue;
					} 
					push(@current, {%hash});
					$host{$name} = [@current]; 
				} else { 
					for my $attribute ( $_->attributes() ) {
						$host{$name}{$attribute->name} = $attribute->getValue;
					}				
				}  
			}
			if ( $name eq "ports") {
				# need to deal with port based scripts
				my @ports;
				# here we have extraports, port ports port data subs => service , cpe 
 
				if ( $_->hasChildNodes() ) {
					for my $node ($_->nonBlankChildNodes()) {
						my %port; 
						my %extraports;
						my @cpe; 
						
						if ( $node->nodeName() eq "extraports") { 
							if ( $node->hasAttributes() ) {
								for my $attribute ( $node->attributes() ) {$extraports{$attribute->name} = $attribute->getValue;} 
							}
							if ($node->hasChildNodes()) {
								for my $n2 ($node->nonBlankChildNodes()) {
									for my $attribute ( $n2->attributes() ) {$extraports{reasons}{$attribute->name} = $attribute->getValue;}
								} 
							}
							push(@{$host{extraports}}, { %extraports } );
							next; 
						}
						elsif ( $node->nodeName() eq "port") { 
							my @scripts; 
							if ( $node->hasAttributes() ) {for my $attribute ( $node->attributes() ) {$port{$attribute->name} = $attribute->getValue;} }
							if ($node->hasChildNodes()) { 
								for my $n2 ($node->nonBlankChildNodes()) {
									my $child = $n2->nodeName(); 
									if ($child eq "script") {
										my %script;  
#										printf STDOUT "script: %s\n",$n2;
										if ( $n2->hasAttributes() ) {for my $attribute ( $n2->attributes() ) {
											$script{$attribute->name} = $attribute->getValue;}
										}
										if ( $n2->hasChildNodes()) { 
											for my $n3 ( $n2->nonBlankChildNodes()) {
												my $elemKey = 0;
												#printf "%s\n", $n3;
												if (  $n3->hasAttributes() ) {
													for my $attribute ( $n3->attributes() ) { 
														$elemKey = $attribute->getValue;
														if ( defined($elemKey)) { $script{elem}{$elemKey} = $n3->textContent; }
													}
												}
												
												
#												printf "\n\t%s => %s\n",$elemKey,$n3->textContent;
												
											}
										}
										push(@scripts, {%script}); 
										
									} else { 
										if ( $n2->hasAttributes() ) {for my $attribute ( $n2->attributes() ) {$port{$child}{$attribute->name} = $attribute->getValue;}}
										if ( $n2->hasChildNodes()) { 
											for my $n3 ( $n2->nonBlankChildNodes()) {
												if (  $n3->nodeName() eq "cpe" ) { 
													if ( $n3->hasAttributes() ) {
														for my $attribute ( $n3->attributes() ) {
															$attribute->name = $attribute->getValue;
															push (@cpe, $attribute->getValue );
														}
													} else { push (@cpe,$n3->textContent); }
												} else {
													if ( $n3->hasAttributes() ) {for my $attribute ( $n3->attributes() ) {$port{$n3->nodeName()}{$attribute->name} = $attribute->getValue;}} 
												}
											}
										}										
									}
									if ( $#cpe > -1 ) { $port{service}{cpe} = [ @cpe ];}
									if ( $#scripts > -1 ) { $port{scripts} = [ @scripts]; }
								}
							}
#							print " "; 
							push(@{$host{ports}}, { %port } );							
							
						}						
					}
				}
			}  elsif ( $name eq "os") {
				my %os; 
				if ( $_->hasChildNodes() ) {
					my @portsused;  
					my @OSmatch;
					for my $node ($_->nonBlankChildNodes()) {
						my @cpe; 
						my $name1 = $node->nodeName();
						if ( $name1 eq "portused") { 
							my %hash; 
							if ( $node->hasAttributes() ) {
								for my $attribute ( $node->attributes() ) { $hash{$attribute->name} = $attribute->getValue; } 
							} 
							push(@portsused,{%hash});
						} elsif ( $name1 eq "osmatch") {
							my @match; 
							my %OSmatch; 
#							printf STDOUT "\t%s [$node]\n", $host{address}{addr};
							# may have to change logic and make this an array......
#							my @cpe;  
							if ( $node->hasAttributes() ) {for my $attribute ( $node->attributes() ) {$OSmatch{$attribute->name} = $attribute->getValue;} }
							if ($node->hasChildNodes()) {
								my @osclass;
								for my $n2 ($node->nonBlankChildNodes()) {
									if ( $n2->nodeName() eq "osclass" ) {
										my @cpe; 
										my %hash; 
										if ( $n2->hasAttributes() ) {for my $attribute ( $n2->attributes() ) {$hash{$attribute->name} = $attribute->getValue;}}
										if ( $n2->hasChildNodes()) {
											for my $n3 ( $n2->nonBlankChildNodes()) {
												if (  $n3->nodeName() eq "cpe" ) { 
													if ( $n3->hasAttributes() ) {
														for my $attribute ( $n3->attributes() ) {
															$attribute->name = $attribute->getValue;
															push (@cpe, $attribute->getValue );
														}
													} else { push (@cpe,$n3->textContent); }
												}
											}
										}
										if ( $#cpe > -1 ) { $hash{cpe} = [ @cpe]; } 
										push(@osclass, { %hash } );
#										push(@osclass, {$os{$name1}{$child}} );
									}
								}
								if ( $#osclass > "-1" ) { $OSmatch{osclass}  = [ @osclass]; } 
							}
							push( @OSmatch, { %OSmatch });  
						} elsif ( $name1 eq "osfingerprint") { 
							if ( $node->hasAttributes() ) {for my $attribute ( $node->attributes() ) {$os{$name1}{$attribute->name} = $attribute->getValue;} }
						}						

					}
					if ( $#portsused > "-1") { $os{portsused} = [ @portsused ]; }
					if ( $#OSmatch > "-1" ) { $os{osmatch}  = [ @OSmatch ]; }
					
# 					push(@{$host{os}},{%os});
 					$host{os} = { %os };
					
				}
			} elsif ( $name eq "hostnames") {
				my %hostname; 
				if ( $_->hasChildNodes() ) {
					for my $node ($_->nonBlankChildNodes()) {
						if ( $node->hasAttributes() ) {for my $attribute ( $node->attributes() ) {$hostname{$attribute->name} = $attribute->getValue;} }
						if ($node->hasChildNodes()) { 
							for my $n2 ($node->nonBlankChildNodes()) {
								if ( $n2->hasAttributes() ) { for my $attribute ( $n2->attributes() ) {$hostname{$attribute->name} = $attribute->getValue;} }
							}
						}
						push(@{$host{hostname}},{%hostname});
					}
				}
			} elsif ( $name eq "trace") {					
				if ( $_->hasAttributes() ) {
					for my $attribute ( $_->attributes() ) {
							$host{traceroute}{$attribute->name} = $attribute->getValue;
						}
				} 
				for my $T ( $_->nonBlankChildNodes() ) {
					my %hop;
					if ( $T->hasAttributes() ) {
						for my $attribute ( $T->attributes() ) {
							$hop{$attribute->name} = $attribute->getValue;
						} 
					}
					push(@{$host{traceroute}{hop}}, {%hop} );
				}
				# next;
			} elsif ( $name eq "hostscript" ) {
				my @hostscripts; 
				#need to be an aray.....				
				if ( $_->hasAttributes() ) {
					for my $attribute ( $_->attributes() ) { $host{hostscript}{$attribute->name} = $attribute->getValue; }
				} 
				if ( $_->hasChildNodes() ) {
					for my $node ($_->nonBlankChildNodes()) { 
						my %script; 
						if ( $node->nodeName() eq "script" ) { 
							if ( $node->hasAttributes() ) {
								for my $attribute ( $node->attributes() ) { $script{$attribute->name} = $attribute->getValue; }
								if ( $node->hasChildNodes() ) {
									for my $n2 ( $node->nonBlankChildNodes() ) {
										my $elemKey;
										for my $attribute ( $n2->attributes() ) { $elemKey = $attribute->getValue; }
#										printf "\n\t%s\n",$n2->textContent;
										$script{elem}{$elemKey} = $n2->textContent;
									}
								}
							} 
						}
						push(@hostscripts, {%script} );
					}
				}
				$host{hostscript}{scripts} = [ @hostscripts ];
			} 
		} 
#		push(@{$self->{parsed}},{%host});
		push(@Hosts,{%host})
	} # END $HOST
	$self->{parsed}{hosts} = [ @Hosts ] ;
		
	undef $nmapXML;
	return 0;
	 
}
 

sub nmap_version { 
	my ($self) = @_;
	return $self->{parsed}{nmaprun}{version};	
}
sub numservices { 
	my ($self) = @_;
	return $self->{parsed}{scaninfo}{numservices};	
}

sub scan_args {
	my ($self) = @_;
	return $self->{parsed}{nmaprun}{args};
}

sub scan_type_proto { }
sub scan_types { }


sub start_str { 
	my ($self) = @_;
	return $self->{parsed}{nmaprun}{startstr};	
}

sub start_time { 
	my ($self) = @_;
	return $self->{parsed}{nmaprun}{start};	
}

sub finished_str {
	my ($self) = @_;
	return $self->{parsed}{runstats}{finished}{timestr};
} 
sub finished_time {
	my ($self) = @_;
	return $self->{parsed}{runstats}{finished}{time}; 	
} 	
sub time_str {
	my ($self) = @_;
	return $self->{parsed}{runstats}{finished}{timestr}; 
}

sub xml_version { 
	my ($self) = @_;
	return $self->{parsed}{nmaprun}{xmloutputversion};
}

sub live {	
	my ($self) = @_;
	return $self->{parsed}{runstats}{hosts}{up}; 
}

sub down {	
	my ($self) = @_;
	return $self->{parsed}{runstats}{hosts}{down}; 
}


sub scanned {	
	my ($self) = @_;
	return $self->{parsed}{runstats}{hosts}{total}; 
}

sub get_address { 
	my ($self,$state,$type) = @_;
	my @list;
	foreach ( @{$self->{parsed}{hosts}}) {
		if ((defined($state)) && ($_->{status}{state} ne $state) ) {
#			printf STDOUT "\tstate: %s [%s]\n",$_->{status}{state}, $state;  
			next; 
		} else { 
			if ( ref($_->{address}) eq "HASH" ) { 
				if ( $_->{address}{addrtype} eq $type) { 
					push(@list, $_->{address}{addr}); 
				}
			} else {
				for my $A ( @{$_->{address}}) { 
					if ( $A->{addrtype} eq $type ) { 
						push(@list, $A->{addr});
					}
				}
			} 			
		}
	} 
	
	return @list; 
		
}

sub get_ips {
	my ($self,$state,$type) = @_;
	if ( ! defined($type)) { $type = "ipv4"; }
	my @iplist =  get_address($self,$state,$type); 

	return @iplist;
}

sub get_host {
	 
	my ($self,$hostIP) = @_;
	my $host; 	
	foreach ( @{$self->{parsed}{hosts}}) {
		if (ref($_->{address}) eq "HASH" ) { 
			$host = $_ if ( $_->{address}{addr} eq $hostIP );
		} elsif (ref($_->{address}) eq "ARRAY") {
			for my $element ( @{$_->{address}}) { 
				$host = $_ if ( $element->{addr} eq $hostIP );
			} 
		}
	}
 
	my $HOST = NmapParser::Host->new($host);
	return $HOST; 
		
}












1;
