File Coverage

blib/lib/Nmap/Parser.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Nmap::Parser;
2              
3 4     4   101567 use strict;
  4         13  
  4         154  
4 4     4   9747 use XML::Twig;
  0            
  0            
5              
6             our $VERSION = 1.36;
7              
8              
9             sub new {
10              
11             my ( $class, $self ) = shift;
12             $class = ref($class) || $class;
13              
14             $self->{twig} = new XML::Twig(
15             start_tag_handlers => { nmaprun => sub {$self->_nmaprun_start_tag_hdlr(@_)} },
16              
17             twig_roots => {
18             scaninfo => sub {$self->_scaninfo_tag_hdlr(@_)},
19             prescript => sub {$self->_prescript_tag_hdlr(@_)},
20             postscript => sub {$self->_postscript_tag_hdlr(@_)},
21             finished => sub {$self->_finished_tag_hdlr(@_)},
22             host => sub {$self->_host_tag_hdlr(@_)},
23             },
24             ignore_elts => {
25             addport => 1,
26             debugging => 1,
27             verbose => 1,
28             hosts => 1,
29             taskbegin => 1,
30             taskend => 1,
31             taskprogress => 1
32             }
33             );
34              
35             bless( $self, $class );
36             return $self;
37             }
38              
39             #/*****************************************************************************/
40             # NMAP::PARSER OBJECT METHODS
41             #/*****************************************************************************/
42              
43             #Safe parse and parsefile will return $@ which will contain the error
44             #that occured if the parsing failed (it might be empty when no error occurred)
45              
46             sub callback {
47             my $self = shift;
48             my $callback = shift; #first arg is CODE
49             if ( ref($callback) eq 'CODE' ) {
50             $self->{callback}{coderef} = $callback;
51             $self->{callback}{is_registered} = 1;
52             }
53             else {
54             $self->{callback}{is_registered} = 0;
55             }
56              
57             #returns if a callback is registered or not
58             return $self->{callback}{is_registered};
59             }
60              
61             sub _parse {
62             my $type = shift;
63             my $self = shift;
64             $self->{HOSTS} = undef;
65             $self->{SESSION} = undef;
66             {
67             file => sub { $self->{twig}->safe_parsefile(@_); },
68             string => sub { $self->{twig}->safe_parse(@_); },
69             }->{$type}->(@_);
70             if ($@) { die $@; }
71             $self->purge;
72             return $self;
73             }
74              
75             sub parse {
76             return _parse('string', @_);
77             }
78              
79             sub parsefile {
80             return _parse('file', @_);
81             }
82              
83             sub parsescan {
84             my $self = shift;
85             my $nmap = shift;
86             my $args = shift;
87             my @ips = @_;
88             my $FH;
89              
90             if ( $args =~ /-o(?:X|N|G)/ ) {
91             die
92             "[Nmap-Parser] Cannot pass option '-oX', '-oN' or '-oG' to parsecan()";
93             }
94              
95             my $cmd;
96              
97             #if output file is defined, point it to a localfile then call parsefile instead.
98             if ( defined( $self->{cache_file} ) ) {
99             $cmd =
100             "$nmap $args -v -v -v -oX "
101             . $self->{cache_file} . " "
102             . ( join ' ', @ips );
103             `$cmd`; #remove output from STDOUT
104             $self->parsefile( $self->{cache_file} );
105             }
106             else {
107             $cmd = "$nmap $args -v -v -v -oX - " . ( join ' ', @ips );
108             open $FH,
109             "$cmd |" || die "[Nmap-Parser] Could not perform nmap scan - $!";
110             $self->parse($FH);
111             close $FH;
112             }
113              
114             return $self;
115             }
116              
117             sub cache_scan {
118             my $self = shift;
119             $self->{cache_file} = shift || 'nmap-parser-cache.' . time() . '.xml';
120             }
121              
122             sub purge {
123             my $self = shift;
124             $self->{twig}->purge;
125             return $self;
126             }
127              
128             sub addr_sort {
129             my $self = shift if ref $_[0];
130              
131             return (
132             map { unpack("x16A*", $_) }
133             sort { $a cmp $b }
134             map {
135             my @vals;
136             if( /:/ ) { #IPv6
137             @vals = split /:/;
138             @vals = map { $_ eq '' ? (0) x (8-$#vals) : hex } @vals
139             } else { #IPv4
140             my @v4 = split /\./;
141             # Sort as IPv4-mapped IPv6, per RFC 4291 Section 2.5.5.2
142             @vals = ( (0) x 5, 0xffff, map { 256*$v4[$_] + $v4[$_+1] } (0,2) );
143             }
144             pack("n8A*", @vals, $_)
145             } @_
146             );
147             }
148              
149             #MAIN SCAN INFORMATION
150             sub get_session {
151             my $self = shift;
152             my $obj = Nmap::Parser::Session->new( $self->{SESSION} );
153             return $obj;
154             }
155              
156             #HOST STUFF
157             sub get_host {
158             my ( $self, $ip ) = (@_);
159             if ( $ip eq '' ) {
160             warn "[Nmap-Parser] No IP address given to get_host()\n";
161             return undef;
162             }
163             $self->{HOSTS}{$ip};
164             }
165              
166             sub del_host {
167             my ( $self, $ip ) = (@_);
168             if ( $ip eq '' ) {
169             warn "[Nmap-Parser] No IP address given to del_host()\n";
170             return undef;
171             }
172             delete $self->{HOSTS}{$ip};
173             }
174              
175             sub all_hosts {
176             my $self = shift;
177             my $status = shift || '';
178              
179             return ( values %{ $self->{HOSTS} } ) if ( $status eq '' );
180              
181             my @hosts = grep { $_->{status} eq $status } ( values %{ $self->{HOSTS} } );
182             return @hosts;
183             }
184              
185             sub get_ips {
186             my $self = shift;
187             my $status = shift || '';
188              
189             return $self->addr_sort( keys %{ $self->{HOSTS} } ) if ( $status eq '' );
190              
191             my @hosts =
192             grep { $self->{HOSTS}{$_}{status} eq $status }
193             ( keys %{ $self->{HOSTS} } );
194             return $self->addr_sort(@hosts);
195              
196             }
197              
198             #/*****************************************************************************/
199             # PARSING TAG HANDLERS FOR XML::TWIG
200             #/*****************************************************************************/
201              
202             sub _nmaprun_start_tag_hdlr {
203              
204             my ($self, $twig, $tag ) = @_;
205              
206             $self->{SESSION}{start_time} = $tag->{att}->{start};
207             $self->{SESSION}{nmap_version} = $tag->{att}->{version};
208             $self->{SESSION}{start_str} = $tag->{att}->{startstr};
209             $self->{SESSION}{xml_version} = $tag->{att}->{xmloutputversion};
210             $self->{SESSION}{scan_args} = $tag->{att}->{args};
211             $self->{SESSION} = Nmap::Parser::Session->new( $self->{SESSION} );
212              
213             $twig->purge;
214              
215             }
216              
217             sub _scaninfo_tag_hdlr {
218             my ( $self, $twig, $tag ) = @_;
219             my $type = $tag->{att}->{type};
220             my $proto = $tag->{att}->{protocol};
221             my $numservices = $tag->{att}->{numservices};
222              
223             if ( defined($type) ) { #there can be more than one type in one scan
224             $self->{SESSION}{type}{$type} = $proto;
225             $self->{SESSION}{numservices}{$type} = $numservices;
226             }
227             $twig->purge;
228             }
229              
230             sub _prescript_tag_hdlr {
231             my ($self, $twig, $tag ) = @_;
232             my $scripts_hashref;
233             for my $script ( $tag->children('script') ) {
234             $scripts_hashref->{ $script->{att}->{id} } =
235             __script_tag_hdlr( $script );
236             }
237             $self->{SESSION}{prescript} = $scripts_hashref;
238             $twig->purge;
239             }
240              
241             sub _postscript_tag_hdlr {
242             my ($self, $twig, $tag ) = @_;
243             my $scripts_hashref;
244             for my $script ( $tag->children('script') ) {
245             $scripts_hashref->{ $script->{att}->{id} } =
246             __script_tag_hdlr( $script );
247             }
248             $self->{SESSION}{postscript} = $scripts_hashref;
249             $twig->purge;
250             }
251              
252             sub _finished_tag_hdlr {
253             my ($self, $twig, $tag ) = @_;
254             $self->{SESSION}{finish_time} = $tag->{att}->{time};
255             $self->{SESSION}{time_str} = $tag->{att}->{timestr};
256             $twig->purge;
257             }
258              
259             #parses all the host information in one swoop (calling __host_*_tag_hdlrs)
260             sub _host_tag_hdlr {
261             my ($self, $twig, $tag ) = @_;
262             my $id = undef;
263              
264             return undef unless ( defined $tag );
265              
266             #GET ADDRESS INFO
267             my $addr_hashref;
268             $addr_hashref = __host_addr_tag_hdlr($tag);
269              
270             #use this as the identifier
271             $id =
272             $addr_hashref->{ipv4}
273             || $addr_hashref->{ipv6}
274             || $addr_hashref->{mac}; #worstcase use MAC
275              
276             $self->{HOSTS}{$id}{addrs} = $addr_hashref;
277              
278             return undef unless ( defined($id) || $id ne '' );
279              
280             #GET HOSTNAMES
281             $self->{HOSTS}{$id}{hostnames} = __host_hostnames_tag_hdlr($tag);
282              
283             #GET STATUS
284             $self->{HOSTS}{$id}{status} = $tag->first_child('status')->{att}->{state};
285              
286             #CONTINUE PROCESSING IF STATUS IS UP - OTHERWISE NO MORE XML
287             if ( lc( $self->{HOSTS}{$id}{status} ) eq 'up' ) {
288              
289             $self->{HOSTS}{$id}{ports} = __host_port_tag_hdlr($tag);
290             $self->{HOSTS}{$id}{os} = __host_os_tag_hdlr($tag);
291             $self->{HOSTS}{$id}{uptime} = __host_uptime_tag_hdlr($tag);
292             $self->{HOSTS}{$id}{tcpsequence} = __host_tcpsequence_tag_hdlr($tag);
293             $self->{HOSTS}{$id}{ipidsequence} = __host_ipidsequence_tag_hdlr($tag);
294             $self->{HOSTS}{$id}{tcptssequence} = __host_tcptssequence_tag_hdlr($tag);
295             $self->{HOSTS}{$id}{hostscript} = __host_hostscript_tag_hdlr($tag);
296             $self->{HOSTS}{$id}{distance} =
297             __host_distance_tag_hdlr($tag); #returns simple value
298             $self->{HOSTS}{$id}{trace} = __host_trace_tag_hdlr($tag);
299             $self->{HOSTS}{$id}{trace_error} = __host_trace_error_tag_hdlr($tag);
300             }
301              
302             #CREATE HOST OBJECT FOR USER
303             $self->{HOSTS}{$id} = Nmap::Parser::Host->new( $self->{HOSTS}{$id} );
304              
305             if ( $self->{callback}{is_registered} ) {
306             &{ $self->{callback}{coderef} }( $self->{HOSTS}{$id} );
307             delete $self->{HOSTS}{$id};
308             }
309              
310             $twig->purge;
311              
312             }
313              
314             sub __host_addr_tag_hdlr {
315             my $tag = shift;
316             my $addr_hashref;
317              
318             #children() will return all children with tag name address
319             for my $addr ( $tag->children('address') ) {
320             if ( lc( $addr->{att}->{addrtype} ) eq 'mac' ) {
321              
322             #we'll assume for now, only 1 MAC address per system
323             $addr_hashref->{mac}{addr} = $addr->{att}->{addr};
324             $addr_hashref->{mac}{vendor} = $addr->{att}->{vendor};
325             }
326             elsif ( lc( $addr->{att}->{addrtype} ) eq 'ipv4' ) {
327             $addr_hashref->{ipv4} = $addr->{att}->{addr};
328             } #support for ipv6? we'll see
329             elsif ( lc( $addr->{att}->{addrtype} ) eq 'ipv6' ) {
330             $addr_hashref->{ipv6} = $addr->{att}->{addr};
331             }
332              
333             }
334              
335             return $addr_hashref;
336             }
337              
338             sub __host_hostnames_tag_hdlr {
339             my $tag = shift;
340              
341             my $hostnames_tag = $tag->first_child('hostnames');
342             return undef unless ( defined $hostnames_tag );
343              
344             my @hostnames;
345              
346             for my $name ( $hostnames_tag->children('hostname') ) {
347             push @hostnames, $name->{att}->{name};
348             }
349              
350             return \@hostnames;
351              
352             }
353              
354             sub __host_port_tag_hdlr {
355             my $tag = shift;
356             my ( $port_hashref, $ports_tag );
357              
358             $ports_tag = $tag->first_child('ports');
359              
360             return undef unless ( defined $ports_tag );
361              
362             #Parsing Extraports
363             my $extraports_tag = $ports_tag->first_child('extraports');
364             if ( defined $extraports_tag && $extraports_tag ne '' ) {
365             $port_hashref->{extraports}{state} = $extraports_tag->{att}->{state};
366             $port_hashref->{extraports}{count} = $extraports_tag->{att}->{count};
367             }
368              
369             #Parsing regular port information
370              
371             my ( $tcp_port_count, $udp_port_count ) = ( 0, 0 );
372              
373             for my $port_tag ( $ports_tag->children('port') ) {
374             my $proto = $port_tag->{att}->{protocol};
375             my $portid = $port_tag->{att}->{portid};
376             my $state = $port_tag->first_child('state');
377             my $owner = $port_tag->first_child('owner') || undef;
378              
379             $tcp_port_count++ if ( $proto eq 'tcp' );
380             $udp_port_count++ if ( $proto eq 'udp' );
381              
382             $port_hashref->{$proto}{$portid}{state} = $state->{att}->{state}
383             || 'unknown'
384             if ( $state ne '' );
385              
386             $port_hashref->{$proto}{$portid}{reason_ttl} = $state->{att}->{reason_ttl}
387             || 'unknown'
388             if($state ne '');
389              
390             #GET SERVICE INFORMATION
391             $port_hashref->{$proto}{$portid}{service} =
392             __host_service_tag_hdlr( $port_tag, $portid )
393             if ( defined($proto) && defined($portid) );
394              
395             #GET SCRIPT INFORMATION
396             $port_hashref->{$proto}{$portid}{service}{script} =
397             __host_script_tag_hdlr( $port_tag, $portid)
398             if ( defined($proto) && defined($portid) );
399              
400             #GET OWNER INFORMATION
401             $port_hashref->{$proto}{$portid}{service}{owner} = $owner->{att}->{name}
402             if ( defined($owner) );
403              
404             #These are added at the end, otherwise __host_service_tag_hdlr will overwrite
405             #GET PORT STATE
406              
407             }
408              
409             $port_hashref->{tcp_port_count} = $tcp_port_count;
410             $port_hashref->{udp_port_count} = $udp_port_count;
411              
412             return $port_hashref;
413              
414             }
415              
416             sub __host_service_tag_hdlr {
417             my $tag = shift;
418             my $portid = shift; #need a way to remember what port this service runs on
419             my $service = $tag->first_child('service[@name]');
420             my $service_hashref;
421             $service_hashref->{port} = $portid;
422              
423             if ( defined $service ) {
424             $service_hashref->{name} = $service->{att}->{name} || 'unknown';
425             $service_hashref->{version} = $service->{att}->{version};
426             $service_hashref->{product} = $service->{att}->{product};
427             $service_hashref->{devicetype} = $service->{att}->{devicetype};
428             $service_hashref->{extrainfo} = $service->{att}->{extrainfo};
429             $service_hashref->{proto} =
430             $service->{att}->{proto}
431             || $service->{att}->{protocol}
432             || 'unknown';
433             $service_hashref->{rpcnum} = $service->{att}->{rpcnum};
434             $service_hashref->{tunnel} = $service->{att}->{tunnel};
435             $service_hashref->{method} = $service->{att}->{method};
436             $service_hashref->{confidence} = $service->{att}->{conf};
437             $service_hashref->{fingerprint} = $service->{att}->{servicefp};
438             }
439              
440             return $service_hashref;
441             }
442              
443             sub __host_script_tag_hdlr {
444             my $tag = shift;
445             my $script_hashref;
446              
447             for ( $tag->children('script') ) {
448             $script_hashref->{ $_->{att}->{id} } =
449             __script_tag_hdlr($_);
450             }
451              
452             return $script_hashref;
453             }
454              
455             sub __host_os_tag_hdlr {
456             my $tag = shift;
457             my $os_tag = $tag->first_child('os');
458             my $os_hashref;
459             my $portused_tag;
460             my $os_fingerprint;
461              
462             if ( defined $os_tag ) {
463              
464             #get the open port used to match os
465             $portused_tag = $os_tag->first_child("portused[\@state='open']");
466             $os_hashref->{portused}{open} = $portused_tag->{att}->{portid}
467             if ( defined $portused_tag );
468              
469             #get the closed port used to match os
470             $portused_tag = $os_tag->first_child("portused[\@state='closed']");
471             $os_hashref->{portused}{closed} = $portused_tag->{att}->{portid}
472             if ( defined $portused_tag );
473              
474             #os fingerprint
475             $os_fingerprint = $os_tag->first_child("osfingerprint");
476             $os_hashref->{os_fingerprint} =
477             $os_fingerprint->{'att'}->{'fingerprint'}
478             if ( defined $os_fingerprint );
479              
480             #This will go in Nmap::Parser::Host::OS
481             my $osmatch_index = 0;
482             my $osclass_index = 0;
483             for my $osmatch ( $os_tag->children('osmatch') ) {
484             $os_hashref->{osmatch_name}[$osmatch_index] =
485             $osmatch->{att}->{name};
486             $os_hashref->{osmatch_name_accuracy}[$osmatch_index] =
487             $osmatch->{att}->{accuracy};
488             $osmatch_index++;
489             for my $osclass ( $osmatch->children('osclass') ) {
490             $os_hashref->{osclass_osfamily}[$osclass_index] =
491             $osclass->{att}->{osfamily};
492             $os_hashref->{osclass_osgen}[$osclass_index] =
493             $osclass->{att}->{osgen};
494             $os_hashref->{osclass_vendor}[$osclass_index] =
495             $osclass->{att}->{vendor};
496             $os_hashref->{osclass_type}[$osclass_index] =
497             $osclass->{att}->{type};
498             $os_hashref->{osclass_class_accuracy}[$osclass_index] =
499             $osclass->{att}->{accuracy};
500             $osclass_index++;
501             }
502             }
503             $os_hashref->{'osmatch_count'} = $osmatch_index;
504              
505             #parse osclass tags
506             for my $osclass ( $os_tag->children('osclass') ) {
507             $os_hashref->{osclass_osfamily}[$osclass_index] =
508             $osclass->{att}->{osfamily};
509             $os_hashref->{osclass_osgen}[$osclass_index] =
510             $osclass->{att}->{osgen};
511             $os_hashref->{osclass_vendor}[$osclass_index] =
512             $osclass->{att}->{vendor};
513             $os_hashref->{osclass_type}[$osclass_index] =
514             $osclass->{att}->{type};
515             $os_hashref->{osclass_class_accuracy}[$osclass_index] =
516             $osclass->{att}->{accuracy};
517             $osclass_index++;
518             }
519             $os_hashref->{'osclass_count'} = $osclass_index;
520             }
521              
522             return $os_hashref;
523              
524             }
525              
526             sub __host_uptime_tag_hdlr {
527             my $tag = shift;
528             my $uptime = $tag->first_child('uptime');
529             my $uptime_hashref;
530              
531             if ( defined $uptime ) {
532             $uptime_hashref->{seconds} = $uptime->{att}->{seconds};
533             $uptime_hashref->{lastboot} = $uptime->{att}->{lastboot};
534              
535             }
536              
537             return $uptime_hashref;
538              
539             }
540              
541             sub __host_tcpsequence_tag_hdlr {
542             my $tag = shift;
543             my $sequence = $tag->first_child('tcpsequence');
544             my $sequence_hashref;
545             return undef unless ($sequence);
546             $sequence_hashref->{class} = $sequence->{att}->{class};
547             $sequence_hashref->{difficulty} = $sequence->{att}->{difficulty};
548             $sequence_hashref->{values} = $sequence->{att}->{values};
549             $sequence_hashref->{index} = $sequence->{att}->{index};
550              
551             return $sequence_hashref;
552              
553             }
554              
555             sub __host_ipidsequence_tag_hdlr {
556             my $tag = shift;
557             my $sequence = $tag->first_child('ipidsequence');
558             my $sequence_hashref;
559             return undef unless ($sequence);
560             $sequence_hashref->{class} = $sequence->{att}->{class};
561             $sequence_hashref->{values} = $sequence->{att}->{values};
562             return $sequence_hashref;
563              
564             }
565              
566             sub __host_tcptssequence_tag_hdlr {
567             my $tag = shift;
568             my $sequence = $tag->first_child('tcptssequence');
569             my $sequence_hashref;
570             return undef unless ($sequence);
571             $sequence_hashref->{class} = $sequence->{att}->{class};
572             $sequence_hashref->{values} = $sequence->{att}->{values};
573             return $sequence_hashref;
574             }
575              
576             sub __host_hostscript_tag_hdlr {
577             my $tag = shift;
578             my $scripts = $tag->first_child('hostscript');
579             my $scripts_hashref;
580             return undef unless ($scripts);
581             for my $script ( $scripts->children('script') ) {
582             $scripts_hashref->{ $script->{att}->{id} } =
583             __script_tag_hdlr( $script );
584             }
585             return $scripts_hashref;
586             }
587              
588             sub __host_distance_tag_hdlr {
589             my $tag = shift;
590             my $distance = $tag->first_child('distance');
591             return undef unless ($distance);
592             return $distance->{att}->{value};
593             }
594              
595             sub __host_trace_tag_hdlr {
596             my $tag = shift;
597             my $trace_tag = $tag->first_child('trace');
598             my $trace_hashref = { hops => [], };
599              
600             if ( defined $trace_tag ) {
601              
602             my $proto = $trace_tag->{att}->{proto};
603             $trace_hashref->{proto} = $proto if defined $proto;
604              
605             my $port = $trace_tag->{att}->{port};
606             $trace_hashref->{port} = $port if defined $port;
607              
608             for my $hop_tag ( $trace_tag->children('hop') ) {
609              
610             # Copy the known hop attributes, they will go in
611             # Nmap::Parser::Host::TraceHop
612             my %hop_data;
613             $hop_data{$_} = $hop_tag->{att}->{$_} for qw( ttl rtt ipaddr host );
614             delete $hop_data{rtt} if $hop_data{rtt} !~ /^[\d.]+$/;
615              
616             push @{ $trace_hashref->{hops} }, \%hop_data;
617             }
618              
619             }
620              
621             return $trace_hashref;
622             }
623              
624             sub __host_trace_error_tag_hdlr {
625             my $tag = shift;
626             my $trace_tag = $tag->first_child('trace');
627              
628             if ( defined $trace_tag ) {
629              
630             my $error_tag = $trace_tag->first_child('error');
631             if ( defined $error_tag ) {
632              
633             # If an error happens, always provide a true value even if
634             # it doesn't contains a useful string
635             my $errorstr = $error_tag->{att}->{errorstr} || 1;
636             return $errorstr;
637             }
638             }
639              
640             return;
641             }
642              
643             sub __script_tag_hdlr {
644             my $tag = shift;
645             my $script_hashref = {
646             output => $tag->{att}->{output}
647             };
648             chomp %$script_hashref;
649             if ( not $tag->is_empty()) {
650             $script_hashref->{contents} = __script_table($tag);
651             }
652             return $script_hashref;
653             }
654              
655             sub __script_table {
656             my $tag = shift;
657             my ($ref, $subref);
658             my $fc = $tag->first_child();
659             if ($fc) {
660             if ($fc->is_text) {
661             $ref = $fc->text;
662             }
663             else {
664             if ($fc->{att}->{key}) {
665             $ref = {};
666             $subref = sub {
667             $ref->{$_->{att}->{key}} = shift;
668             };
669             }
670             else {
671             $ref = [];
672             $subref = sub {
673             push @$ref, shift;
674             };
675             }
676             for ($tag->children()) {
677             if ($_->tag() eq "table") {
678             $subref->(__script_table( $_ ));
679             }
680             else {
681             $subref->($_->text);
682             }
683             }
684             }
685             }
686             return $ref
687             }
688              
689             #/*****************************************************************************/
690             # NMAP::PARSER::SESSION
691             #/*****************************************************************************/
692              
693             package Nmap::Parser::Session;
694             use vars qw($AUTOLOAD);
695              
696             sub new {
697             my $class = shift;
698             $class = ref($class) || $class;
699             my $self = shift || {};
700             bless( $self, $class );
701             return $self;
702             }
703              
704             #Support for:
705             #start_time, start_str, finish_time, time_str, nmap_version, xml_version, scan_args
706             sub AUTOLOAD {
707             ( my $param = $AUTOLOAD ) =~ s{.*::}{}xms;
708             return if ( $param eq 'DESTROY' );
709             no strict 'refs';
710             *$AUTOLOAD = sub { return $_[0]->{ lc $param } };
711             goto &$AUTOLOAD;
712             }
713              
714             sub numservices {
715             my $self = shift;
716             my $type = shift
717             || ''; #(syn|ack|bounce|connect|null|xmas|window|maimon|fin|udp|ipproto)
718              
719             return unless ( ref( $self->{numservices} ) eq 'HASH' );
720              
721             if ( $type ne '' ) { return $self->{numservices}{$type}; }
722             else {
723             my $total = 0;
724             for ( values %{ $self->{numservices} } ) { $total += $_; }
725             return $total;
726             } #(else) total number of services together
727             }
728              
729             sub scan_types {
730             return sort { $a cmp $b } ( keys %{ $_[0]->{type} } )
731             if ( ref( $_[0]->{type} ) eq 'HASH' );
732             }
733             sub scan_type_proto { return $_[1] ? $_[0]->{type}{ $_[1] } : undef; }
734              
735             sub prescripts {
736             my $self = shift;
737             my $id = shift;
738             unless ( defined $id ) {
739             return sort keys %{ $self->{prescript} };
740             }
741             else {
742             return $self->{prescript}{$id};
743             }
744             }
745              
746             sub postscripts {
747             my $self = shift;
748             my $id = shift;
749             unless ( defined $id ) {
750             return sort keys %{ $self->{postscript} };
751             }
752             else {
753             return $self->{postscript}{$id};
754             }
755             }
756              
757             #/*****************************************************************************/
758             # NMAP::PARSER::HOST
759             #/*****************************************************************************/
760              
761             package Nmap::Parser::Host;
762             use vars qw($AUTOLOAD);
763              
764             sub new {
765             my $class = shift;
766             $class = ref($class) || $class;
767             my $self = shift || {};
768             bless( $self, $class );
769             return $self;
770             }
771              
772             sub status { return $_[0]->{status}; }
773              
774             sub addr {
775             my $default = $_[0]->{addrs}{ipv4} || $_[0]->{addrs}{ipv6};
776             return $default;
777             }
778              
779             sub addrtype {
780             if ( $_[0]->{addrs}{ipv4} ) { return 'ipv4'; }
781             elsif ( $_[0]->{addrs}{ipv6} ) { return 'ipv6'; }
782             }
783              
784             sub ipv4_addr { return $_[0]->{addrs}{ipv4}; }
785             sub ipv6_addr { return $_[0]->{addrs}{ipv6}; }
786              
787             sub mac_addr { return $_[0]->{addrs}{mac}{addr}; }
788             sub mac_vendor { return $_[0]->{addrs}{mac}{vendor}; }
789              
790             #returns the first hostname
791             sub hostname {
792             my $self = shift;
793             my $index = shift || 0;
794             if ( ref( $self->{hostnames} ) ne 'ARRAY' ) { return ''; }
795             if ( scalar @{ $self->{hostnames} } <= $index ) {
796             $index = scalar @{ $self->{hostnames} } - 1;
797             }
798             return $self->{hostnames}[$index] if ( scalar @{ $self->{hostnames} } );
799             }
800              
801             sub all_hostnames { return @{ $_[0]->{hostnames} || [] }; }
802             sub extraports_state { return $_[0]->{ports}{extraports}{state}; }
803             sub extraports_count { return $_[0]->{ports}{extraports}{count}; }
804             sub distance { return $_[0]->{distance}; }
805              
806             sub hostscripts {
807             my $self = shift;
808             my $id = shift;
809             unless ( defined $id ) {
810             return sort keys %{ $self->{hostscript} };
811             }
812             else {
813             return $self->{hostscript}{$id};
814             }
815             }
816              
817             sub all_trace_hops {
818              
819             my $self = shift;
820              
821             return unless defined $self->{trace}->{hops};
822             return map { Nmap::Parser::Host::TraceHop->new( $_ ) }
823             @{ $self->{trace}->{hops} };
824             }
825              
826             sub trace_port { return $_[0]->{trace}->{port} }
827             sub trace_proto { return $_[0]->{trace}->{proto} }
828             sub trace_error { return $_[0]->{trace_error} }
829              
830             sub _del_port {
831             my $self = shift;
832             my $proto = pop; #portid might be empty, so this goes first
833             my @portids = @_;
834             @portids = grep { $_ + 0 } @portids;
835              
836             unless ( scalar @portids ) {
837             warn "[Nmap-Parser] No port number given to del_port()\n";
838             return undef;
839             }
840              
841             delete $self->{ports}{$proto}{$_} for (@portids);
842             }
843              
844             sub _get_ports {
845             my $self = shift;
846             my $proto = pop; #param might be empty, so this goes first
847             my $state = shift; #open, filtered, closed or any combination
848             my @matched_ports = ();
849              
850             #if $state is undef, then tcp_ports or udp_ports was called for all ports
851             #therefore, only return the keys of all ports found
852             if ( not defined $state ) {
853             return sort { $a <=> $b } ( keys %{ $self->{ports}{$proto} } );
854             }
855             else {
856             $state = lc($state)
857             }
858              
859             #the port parameter can be set to either any of these also 'open|filtered'
860             #can count as 'open' and 'filetered'. Therefore I need to use a regex from now on
861             #if $param is empty, then all ports match.
862              
863             for my $portid ( keys %{ $self->{ports}{$proto} } ) {
864              
865             #escape metacharacters ('|', for example in: open|filtered)
866             #using \Q and \E
867             push( @matched_ports, $portid )
868             if ( $self->{ports}{$proto}{$portid}{state} =~ /\Q$state\E/ );
869              
870             }
871              
872             return sort { $a <=> $b } @matched_ports;
873              
874             }
875              
876             sub _get_port_state {
877             my $self = shift;
878             my $proto = pop; #portid might be empty, so this goes first
879             my $portid = lc(shift);
880              
881             return undef unless ( exists $self->{ports}{$proto}{$portid} );
882             return $self->{ports}{$proto}{$portid}{state};
883              
884             }
885              
886             sub _get_port_state_ttl {
887             my $self = shift;
888             my $proto = pop;
889             my $portid = lc(shift);
890              
891             return undef unless ( exists $self->{ports}{$proto}{$portid} );
892             return $self->{ports}{$proto}{$portid}{reason_ttl};
893             }
894              
895             #changed this to use _get_ports since it was similar code
896             sub tcp_ports { return _get_ports( @_, 'tcp' ); }
897             sub udp_ports { return _get_ports( @_, 'udp' ); }
898              
899             sub tcp_port_count { return $_[0]->{ports}{tcp_port_count}; }
900             sub udp_port_count { return $_[0]->{ports}{udp_port_count}; }
901              
902             sub tcp_port_state_ttl { return _get_port_state_ttl( @_, 'tcp' ); }
903              
904             sub tcp_port_state { return _get_port_state( @_, 'tcp' ); }
905             sub udp_port_state { return _get_port_state( @_, 'udp' ); }
906              
907             sub tcp_del_ports { return _del_port( @_, 'tcp' ); }
908             sub udp_del_ports { return _del_port( @_, 'udp' ); }
909              
910             sub tcp_service {
911             my $self = shift;
912             my $portid = shift;
913             if ( $portid eq '' ) {
914             warn "[Nmap-Parser] No port number passed to tcp_service()\n";
915             return undef;
916             }
917             return Nmap::Parser::Host::Service->new(
918             $self->{ports}{tcp}{$portid}{service} );
919             }
920              
921             sub udp_service {
922             my $self = shift;
923             my $portid = shift;
924             if ( $portid eq '' ) {
925             warn "[Nmap-Parser] No port number passed to udp_service()\n";
926             return undef;
927             }
928             return Nmap::Parser::Host::Service->new(
929             $self->{ports}{udp}{$portid}{service} );
930              
931             }
932              
933             #usually the first one is the highest accuracy
934              
935             sub os_sig { return Nmap::Parser::Host::OS->new( $_[0]->{os} ); }
936              
937             #Support for:
938             #tcpsequence_class, tcpsequence_values, tcpsequence_index,
939             #ipidsequence_class, ipidsequence_values, tcptssequence_values,
940             #tcptssequence_class, uptime_seconds, uptime_lastboot
941             #tcp_open_ports, udp_open_ports, tcp_filtered_ports, udp_filtered_ports,
942             #tcp_closed_ports, udp_closed_ports
943             sub AUTOLOAD {
944             ( my $param = $AUTOLOAD ) =~ s{.*::}{}xms;
945             return if ( $param eq 'DESTROY' );
946             my ( $type, $val ) = split /_/, lc($param);
947              
948             #splits the given method name by '_'. This will determine the function and param
949             no strict 'refs';
950              
951             if ( ( $type eq 'tcp' || $type eq 'udp' )
952             && ( $val eq 'open' || $val eq 'filtered' || $val eq 'closed' ) )
953             {
954              
955             #they must be looking for port info: tcp or udp. The $val is either open|filtered|closed
956             *$AUTOLOAD = sub { return _get_ports( $_[0], $val, $type ); };
957             goto &$AUTOLOAD;
958              
959             }
960             elsif ( defined $type && defined $val ) {
961              
962             #must be one of the 'sequence' functions asking for class/values/index
963             *$AUTOLOAD = sub { return $_[0]->{$type}{$val} };
964             goto &$AUTOLOAD;
965             }
966             else { die '[Nmap-Parser] method ->' . $param . "() not defined!\n"; }
967             }
968              
969             #/*****************************************************************************/
970             # NMAP::PARSER::HOST::SERVICE
971             #/*****************************************************************************/
972              
973             package Nmap::Parser::Host::Service;
974             use vars qw($AUTOLOAD);
975              
976             sub new {
977             my $class = shift;
978             $class = ref($class) || $class;
979             my $self = shift || {};
980             bless( $self, $class );
981             return $self;
982             }
983              
984             sub scripts {
985             my $self = shift;
986             my $id = shift;
987              
988             unless ( defined $id ) {
989             return sort keys %{ $self->{script} };
990             }
991             else {
992             return $self->{script}{$id};
993             }
994             }
995              
996             #Support for:
997             #name port proto rpcnum owner version product extrainfo tunnel method confidence
998             #this will now only load functions that will be used. This saves
999             #on delay (increase speed) and memory
1000              
1001             sub AUTOLOAD {
1002             ( my $param = $AUTOLOAD ) =~ s{.*::}{}xms;
1003             return if ( $param eq 'DESTROY' );
1004             no strict 'refs';
1005              
1006             *$AUTOLOAD = sub { return $_[0]->{ lc $param } };
1007             goto &$AUTOLOAD;
1008             }
1009              
1010             #/*****************************************************************************/
1011             # NMAP::PARSER::HOST::OS
1012             #/*****************************************************************************/
1013              
1014             package Nmap::Parser::Host::OS;
1015             use vars qw($AUTOLOAD);
1016              
1017             sub new {
1018             my $class = shift;
1019             $class = ref($class) || $class;
1020             my $self = shift || {};
1021             bless( $self, $class );
1022             return $self;
1023             }
1024              
1025             sub portused_open { return $_[0]->{portused}{open}; }
1026             sub portused_closed { return $_[0]->{portused}{closed}; }
1027             sub os_fingerprint { return $_[0]->{os_fingerprint}; }
1028              
1029             sub name_count { return $_[0]->{osmatch_count}; }
1030              
1031             sub all_names {
1032             my $self = shift;
1033             @_ = ();
1034             if ( $self->{osclass_count} < 1 ) { return @_; }
1035             if ( ref( $self->{osmatch_name} ) eq 'ARRAY' ) {
1036             return sort @{ $self->{osmatch_name} };
1037             }
1038              
1039             } #given by decreasing accuracy
1040              
1041             sub class_count { return $_[0]->{osclass_count}; }
1042              
1043             #Support for:
1044             #name,names, name_accuracy, osfamily, vendor, type, osgen, class_accuracy
1045             sub AUTOLOAD {
1046             ( my $param = $AUTOLOAD ) =~ s{.*::}{}xms;
1047             return if ( $param eq 'DESTROY' );
1048             no strict 'refs';
1049             $param = lc($param);
1050              
1051             $param = 'name' if ( $param eq 'names' );
1052             if ( $param eq 'name' || $param eq 'name_accuracy' ) {
1053              
1054             *$AUTOLOAD = sub { _get_info( $_[0], $_[1], $param, 'osmatch' ); };
1055             goto &$AUTOLOAD;
1056             }
1057             else {
1058              
1059             *$AUTOLOAD = sub { _get_info( $_[0], $_[1], $param, 'osclass' ); };
1060             goto &$AUTOLOAD;
1061             }
1062             }
1063              
1064             sub _get_info {
1065             my ( $self, $index, $param, $type ) = @_;
1066             $index ||= 0;
1067              
1068             #type is either osclass or osmatch
1069             if ( $index >= $self->{ $type . '_count' } ) {
1070             $index = $self->{ $type . '_count' } - 1;
1071             }
1072             return $self->{ $type . '_' . $param }[$index];
1073             }
1074              
1075             #/*****************************************************************************/
1076             # NMAP::PARSER::HOST::TRACEHOP
1077             #/*****************************************************************************/
1078              
1079             package Nmap::Parser::Host::TraceHop;
1080             use vars qw($AUTOLOAD);
1081              
1082             sub new {
1083             my $class = shift;
1084             $class = ref($class) || $class;
1085             my $self = shift || {};
1086             bless( $self, $class );
1087             return $self;
1088             }
1089              
1090             sub AUTOLOAD {
1091             ( my $param = $AUTOLOAD ) =~ s{.*::}{}xms;
1092             return if ( $param eq 'DESTROY' );
1093             no strict 'refs';
1094             $param = lc($param);
1095              
1096             # Supported accessors:
1097             my %subs;
1098             @subs{ qw( ttl rtt ipaddr host ) } = 1;
1099              
1100             if ( exists $subs{$param} ) {
1101              
1102             *$AUTOLOAD = sub { $_[0]->{$param} };
1103             goto &$AUTOLOAD;
1104             }
1105             else { die '[Nmap-Parser] method ->' . $param . "() not defined!\n"; }
1106             }
1107              
1108             1;
1109              
1110             __END__