nmap-parser 0.3.2 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
data/BUGS CHANGED
@@ -1,4 +1,5 @@
1
- If you find any bugs, please report them to me:
1
+ $Id: BUGS 196 2009-06-26 01:50:27Z kjak $
2
2
 
3
- Kris Katterjohn <katterjohn[at]gmail.com>
3
+ If you find any bugs, please report them (or even better: send a patch!) to me,
4
+ Kris Katterjohn, at katterjohn(a)gmail.com
4
5
 
@@ -0,0 +1,249 @@
1
+ = Ruby Nmap::Parser ChangeLog
2
+
3
+ $Id: ChangeLog.rdoc 219 2010-06-02 03:46:41Z kjak $
4
+
5
+ === 0.3.5 (Jun 1 2010)
6
+
7
+ * Nmap::Parser.new() now no longer accepts a string to parse, but rather a hash
8
+ of options (more on this below). new() of course returns a new Parser object,
9
+ but now after you have the object you call instance parsing methods such as
10
+ parsescan() or parsefile() to do the work. All of the class methods which
11
+ previously existed are still available; they're just wrappers around new()
12
+ and the instance methods now.
13
+
14
+ * Switched from parsing the XML using the tree-style approach to using a stream
15
+ approach. It was kind of a pain in the ass setting up the "listener" class
16
+ and associated helper structures since I've never done it before, but it
17
+ was all well worth it to help speed things up. I'm still using REXML instead
18
+ of something I've heard to be much faster (e.g. libxml) because I think a
19
+ big determining factor on what to use is its availability. REXML has come in
20
+ Ruby's standard library since 1.8.
21
+
22
+ * You can now parse using callbacks. Tell Parser what method or proc you want
23
+ to use like this:
24
+ Nmap::Parser.new(:callback => mymeth)
25
+ Now as soon as a new Host object is created (which happens a lot faster now
26
+ thanks to the stream-style XML parsing), it will be passed to your callback
27
+ much like this:
28
+ mymeth.call(newhostobj)
29
+ The callback is run in a new thread.
30
+
31
+ * Added support for the SCTP scanning capabilities Nmap newly acquired.
32
+
33
+ * Added getport(), getports() and getportlist() methods to ::Host. The first
34
+ argument can be symbol specifying the desired protocol to use (:tcp, :udp,
35
+ :sctp or :ip), and the rest is the same as the corresponding method, e.g.
36
+ getports(:tcp, 'open')
37
+ is the same as
38
+ tcp_ports('open')
39
+
40
+ getports() also supports an :any type to give matching ports from all
41
+ protocols. I want to thank Tom Sellers (nmap(a)fadedcode.net) for discussing
42
+ this latter aspect with me as he had the general idea for this functionality
43
+ but just a different design in mind.
44
+
45
+ The first argument can also be an array of symbols specifying the protocols
46
+ to use, such as [:tcp, :udp].
47
+
48
+ * Added numhosts() to ::Session, which currently returns the number of up, down
49
+ or total hosts scanned. This can be very different from calling things like
50
+ hosts.size or get_ips.size in Parser because Nmap usually won't list all of
51
+ the individual hosts that it doesn't know or assume to be up (unless, for
52
+ example, you're doing a ping or list scan).
53
+
54
+ * Added a + operator to Nmap::Parser to return a new Parser object containing
55
+ hosts from both operands (but no duplicates from the second, as determined by
56
+ host.addr info). rawxml and session are both nil in the new object. The new
57
+ combination? method is provided to return a boolean value depending on whether
58
+ or not the current object is just a combination of others.
59
+
60
+ * Added exit and errormsg to ::Session for the new XML attributes. The former
61
+ specifies if Nmap exited successfully ("success") or in error ("error"), and
62
+ if applicable, in the latter provides the error message.
63
+
64
+ * Added version constants to Parser:
65
+ Major - Major version number (currently 0)
66
+ Minor - Minor version number (currently 3)
67
+ Teeny - Teeny version number (currently 5)
68
+ Stage - Development stage ("release" or "dev", currently the former)
69
+ Version - Pre-built string in the form Major.Minor.Teeny
70
+
71
+ * Fixed some methods in ::OS which could have lead to an exception if given
72
+ an invalid index.
73
+
74
+ * Fixed get_ips() in ::Parser which was broken and would end up generating
75
+ an ArgumentError.
76
+
77
+ * Have scan_time in ::Session take on (stop_time-start_time).to_f (which was
78
+ its original value) if the "elapsed" XML attribute isn't available. I added
79
+ this attribute to Nmap, but I was totally absent-minded here and completely
80
+ neglected earlier versions. Thanks to Dustin Webber (dustinw(a)aos5.com)
81
+ for the report of 0.0 results.
82
+
83
+ * Fixed the tcpsequence_index Host attribute so that it's an integer instead
84
+ of a string (from the XML parsing).
85
+
86
+ * Added proto to ::Port to hold the port protocol (e.g. "tcp"). This is one
87
+ of those simple things that just should've been in from the start. Thanks
88
+ to Tom Sellers (nmap(a)fadedcode.net) for the patch.
89
+
90
+ * Fixed the regex which looks for output options (-o*) in parsescan(); it was
91
+ matching things like the script name smb-os-discovery. Thanks to Russell
92
+ Fulton (r.fulton(a)auckland.ac.nz) for the report and fix.
93
+
94
+ * Instead of being quite so anal, the Parser now accepts XML not only as a
95
+ String but also as anything responding to to_str().
96
+
97
+ * Use /usr/bin/env in the shebang (#!) lines of the example scripts for some
98
+ improved portability. Thanks to Daniel Roethlisberger (daniel(a)roe.ch) for
99
+ noticing the problem and taking care of this.
100
+
101
+ * The mysterious and totally inconspicuously named :blinken key can also be
102
+ used in the new() option hash. :)
103
+
104
+ * Made a lot of internal implementation improvements, including (but most
105
+ definitely not limited to!) doing some metaprogramming.
106
+
107
+ * Added some unit tests, starting with test/test.ts.rb. The tests performed
108
+ now are messy and incomplete so far, but I originally wrote them nearly a
109
+ year ago and I think that it's better than nothing. I hope to eventually
110
+ have unit tests covering every aspect of the parser, even all of the little
111
+ tedious things I was too lazy to write tests for before. Any help writing
112
+ tests would be much appreciated! :)
113
+
114
+ * Made many documentation improvements.
115
+
116
+ * Renamed this ChangeLog to ChangeLog.rdoc and formatted it RDoc-style.
117
+
118
+ === 0.3.2 (Feb 8 2009)
119
+
120
+ * Fixed a state matching bug in methods like tcp_ports() in which a specified
121
+ state would sometimes match an unrelated one. Now these methods properly
122
+ match all states exactly as well as combinations like "open|filtered" as
123
+ they should. (This bug didn't affect matching "open" ports).
124
+
125
+ * Added parsestring() to parse a string of XML data. This is currently just
126
+ an alias for new()
127
+
128
+ * Added an examples directory to hold some (currently simple) scripts
129
+
130
+ * Added <=> method to ExtraPorts which compares port counts
131
+
132
+ * Added <=> method to Traceroute::Hop which compares TTLs
133
+
134
+ * Updated scan_time in ::Session to use the new "elapsed" attribute (which
135
+ can give a floating point value)
136
+
137
+ * Improved exception messages raised by the library
138
+
139
+ * Many doc and general code improvements
140
+
141
+ === 0.3.1 (Jan 1 2009)
142
+
143
+ * Enhanced port specification parsing code
144
+
145
+ * Added the newer starttime and endtime host attributes (individual host
146
+ scan times) to ::Host
147
+
148
+ * Added the newer scanflags scaninfo attribute as scanflags() in ::Session
149
+ for printing the scanflags associated with the specified scan type (e.g.
150
+ "PSHACK" for scan type "ack")
151
+
152
+ * Various implementation reworkings
153
+
154
+ * Many documentation updates
155
+
156
+ === 0.3 (Apr 24 2008)
157
+
158
+ * Moved the library (and changed the filename) from nmapparser to
159
+ nmap/parser, so any existing scripts need to be updated
160
+
161
+ * Added nmap-parser.gemspec as a gem will be available from now on
162
+
163
+ * Added parseread() for reading and parsing XML from any object that
164
+ responds to a read() method that returns a String
165
+
166
+ * Added tcp_port(), udp_port() and ip_proto() to ::Host for requesting a
167
+ Port object for a single port
168
+
169
+ * Added tcp_script(), udp_script() and script() to ::Host for requesting
170
+ a single Script object
171
+
172
+ * Added script() and script_output() to ::Port for requesting either the
173
+ Script object or output for the specified script
174
+
175
+ * Added hop() to ::Traceroute for requesting the Hop object for the host
176
+ at the specified hop (TTL)
177
+
178
+ * Nmap::Parser.[get_]host() and Nmap::Parser.del[ete]_host() now operate
179
+ on an IP address or hostname
180
+
181
+ * Methods and class members which are naturally numeric (port numbers,
182
+ TTL counts, RTT, etc.) are now returned as such. Before they were
183
+ being returned as strings from the XML parsing.
184
+
185
+ * Nmap::Parser.new() now yields the new object to a block if one is
186
+ given. This was supposed to be implemented before, but wasn't.
187
+
188
+ * ::Port, ::OS::OSClass and ::OS::OSMatch now all have a <=> operator
189
+ for sorting
190
+
191
+ * Nmap::Parser now has a == operator defined which compares the rawxml
192
+ members
193
+
194
+ * All previously available arrays have been turned into methods which,
195
+ besides just returning the array, yields each element to a block if
196
+ one is given. Most arrays were set up this way already, but this
197
+ will be the "standard" way arrays are presented now. Any existing
198
+ code will still work without change because Ruby is awesome like that.
199
+
200
+ * Methods which take an array index or hash key have been updated to use
201
+ conversion methods (e.g. to_i and to_s) to allow for (depending on the
202
+ circumstance) more correct and straightforward use
203
+
204
+ * Updated parsescan() to better check for Nmap output options (-o*)
205
+ being passed to it, and to use Open3.popen3() instead of IO.popen()
206
+ for running Nmap. If Open3 fails to load, however, popen() is used.
207
+
208
+ * Added the following aliases: ::Host hostnames -> all_hostnames, ::OS
209
+ names -> all_names, and ::Hop ipaddr -> addr
210
+
211
+ * Fixed the ::OS tcpport_open, tcpport_closed, and udpport_closed methods
212
+
213
+ * Fixed the ::Session stop_str and stop_time
214
+
215
+ * Added a *lot* of comments, mainly for better RDoc documentation
216
+
217
+ * Removed the addrtype member from ::Host as it wasn't useful and often
218
+ wrong
219
+
220
+ * Removed min_parallelism and max_parallelism from ::Session as I seem
221
+ to have.. uh.. imagined they existed :)
222
+
223
+ === 0.2.1 (Jan 4 2008)
224
+
225
+ * Fixed a bug that spawned from an assumption I made. The parsing
226
+ would fail if OS detection was performed but the scan wasn't run
227
+ with -v or -d. The OS fingerprint is printed in the XML output
228
+ if either of these are used. I virtually always run with -d, so
229
+ the fingerprint was always there for me, and I just assumed that
230
+ the fingerprint is *always* printed. parsescan() always sets -d,
231
+ so it isn't affected.
232
+
233
+ === 0.2 (Jan 3 2008)
234
+
235
+ * Fixed a bug (typo) with the read accessor for the MAC address
236
+ information in ::Host (mac_addr and mac_vendor). Thanks to
237
+ Stefan Friedli (stfr(a)scip.ch) for the report and patch.
238
+
239
+ * Added extraports information via ::Host::ExtraPorts class
240
+
241
+ * Added devicetype, ostype and the RPC lowver and highver to
242
+ ::Host::Port::Service
243
+
244
+ * Updated and reworded docs
245
+
246
+ === 0.1 (Dec 11 2007)
247
+
248
+ * Initial Release
249
+
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007-2009 Kris Katterjohn
1
+ Copyright (c) 2007-2010 Kris Katterjohn
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README CHANGED
@@ -1,41 +1,45 @@
1
- ===============================================================================
2
- == Ruby Nmap::Parser http://rubynmap.sourceforge.net ==
3
- == Kris Katterjohn katterjohn@gmail.com ==
4
- ===============================================================================
1
+ -------------------------------------------------------------------------------
2
+ -- Ruby Nmap::Parser http://rubynmap.sourceforge.net --
3
+ -- Kris Katterjohn katterjohn@gmail.com --
4
+ -------------------------------------------------------------------------------
5
5
 
6
- $Id: README 126 2009-02-08 15:32:04Z kjak $
6
+ $Id: README 208 2010-06-01 17:59:08Z kjak $
7
7
 
8
8
 
9
9
  OVERVIEW
10
- ========
10
+ --------
11
11
 
12
- This library provides a Ruby interface to Nmap's scan data. It can run Nmap
13
- and parse its XML output directly from the scan, parse a file containing the
14
- XML data from a separate scan, parse a String of XML data from a scan, or parse
15
- XML data from an object via its read() method. This information is presented
16
- in an easy-to-use and intuitive fashion for storage and manipulation.
12
+ This library provides a Ruby interface to the Nmap Security Scanner and its
13
+ XML formatted scan data. It can run Nmap and parse its XML output directly
14
+ from the scan, parse a file or string of XML scan data, or parse XML scan
15
+ data from an object via its read() method. This information is presented in
16
+ an easy-to-use and intuitive fashion for further storage and manipulation.
17
17
 
18
- Keep in mind that this is not just some Ruby port of Anthony Persaud's Perl
19
- Nmap::Parser! There are more classes, many different methods, and blocks are
20
- extensively available.
18
+ Note that while Anthony Persaud's Perl Nmap::Parser was certainly an
19
+ inspiration when designing this library, there are a number of distinguishing
20
+ characteristics. Very briefly, this library contains more classes, many more
21
+ methods, and has blocks extensively available.
21
22
 
22
23
 
23
24
  REQUIREMENTS and RECOMMENDATIONS
24
- ================================
25
+ --------------------------------
25
26
 
26
27
  Things Required:
27
28
 
28
- - Nmap (http://nmap.org) [Only required for parsescan()]
29
+ * Nmap Security Scanner (http://nmap.org) [Only required for parsescan()]
30
+
31
+ * REXML Ruby Library [In standard library with Ruby >= 1.8]
29
32
 
30
- - REXML Ruby Library [In standard library with Ruby >= 1.8]
31
33
 
32
34
  Things Recommended:
33
35
 
34
- - Open3 Ruby Library [In standard library]
36
+ * Ruby version >= 1.9.1, which is far faster than the 1.8 versions
37
+
38
+ * Open3 Ruby Library [In standard library; for parsescan()]
35
39
 
36
40
 
37
41
  HELP
38
- ====
42
+ ----
39
43
 
40
44
  The website has detailed RDoc documentation available. You can create a quick
41
45
  set of these docs yourself by issuing the following command from the base
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ $Id: TODO 216 2010-06-02 02:32:21Z kjak $
2
+
3
+ * Continue writing unit tests
4
+
@@ -1,4 +1,4 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
  #
3
3
  # Very simple script to print out the open TCP ports for each host
4
4
  #
@@ -6,13 +6,13 @@
6
6
 
7
7
  require 'nmap/parser'
8
8
 
9
- p = Nmap::Parser.parsescan("nmap", "192.168.11.0/24")
9
+ p = Nmap::Parser.parsescan("nmap", "-T4 192.168.10.0/24")
10
10
 
11
11
  p.hosts("up") do |host|
12
12
  puts "#{host.addr}:"
13
13
 
14
14
  host.tcp_ports("open") do |port|
15
- puts "Got #{port.num}/tcp"
15
+ puts "Got #{port.num}/#{port.proto}"
16
16
  end
17
17
 
18
18
  puts
@@ -1,29 +1,28 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
  #
3
- # A really simplistic script which prints out service information for the
4
- # open TCP and UDP ports found by scanning a host
3
+ # A simple script which prints out service information for the open TCP and
4
+ # UDP ports found by scanning a host
5
5
  #
6
- # Kris Katterjohn 01/27/2009
6
+ # Kris Katterjohn 02/16/2009
7
7
 
8
8
  require 'nmap/parser'
9
9
 
10
- def printport(port, proto)
11
- srv = port.service
12
-
13
- puts
14
- puts "Port ##{port.num}/#{proto} is open (#{port.reason})"
15
- puts "\tService: #{srv.name}" if srv.name
16
- puts "\tProduct: #{srv.product}" if srv.product
17
- puts "\tVersion: #{srv.version}" if srv.version
18
- puts
19
- end
20
-
21
- p = Nmap::Parser.parsescan("sudo nmap", "-sSUV 192.168.11.1")
10
+ p = Nmap::Parser.parsescan("sudo nmap", "-T4 -sSUV 192.168.10.0/24")
22
11
 
23
12
  p.hosts("up") do |host|
24
13
  puts "#{host.addr}:"
25
- host.tcp_ports("open") { |port| printport(port, "tcp") }
26
- host.udp_ports("open") { |port| printport(port, "udp") }
14
+
15
+ [:tcp, :udp].each do |type|
16
+ host.getports(type, "open") do |port|
17
+ srv = port.service
18
+ puts "Port ##{port.num}/#{port.proto} is open (#{port.reason})"
19
+ puts "\tService: #{srv.name}" if srv.name
20
+ puts "\tProduct: #{srv.product}" if srv.product
21
+ puts "\tVersion: #{srv.version}" if srv.version
22
+ puts
23
+ end
24
+ end
25
+
27
26
  puts
28
27
  end
29
28
 
@@ -1,10 +1,18 @@
1
- # Ruby library for parsing Nmap's XML output
1
+ # = nmap/parser.rb: Nmap::Parser
2
+ #
3
+ # Ruby interface to the Nmap Security Scanner and its XML formatted scan data
4
+ #
5
+ # = Homepage
2
6
  #
3
7
  # http://rubynmap.sourceforge.net
4
8
  #
5
- # Author: Kris Katterjohn <katterjohn@gmail.com>
9
+ # = Author
10
+ #
11
+ # Kris Katterjohn (katterjohn@gmail.com)
6
12
  #
7
- # Copyright (c) 2007-2009 Kris Katterjohn
13
+ # = License
14
+ #
15
+ # Copyright (c) 2007-2010 Kris Katterjohn
8
16
  #
9
17
  # Permission is hereby granted, free of charge, to any person obtaining a copy
10
18
  # of this software and associated documentation files (the "Software"), to deal
@@ -24,37 +32,56 @@
24
32
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
33
  # THE SOFTWARE.
26
34
 
27
- # $Id: parser.rb 129 2009-02-08 17:14:39Z kjak $
28
- # https://rubynmap.svn.sourceforge.net/svnroot/rubynmap
35
+ # $Id: parser.rb 220 2010-06-02 03:51:54Z kjak $
36
+ # https://rubynmap.svn.sourceforge.net/svnroot/rubynmap/trunk
37
+
38
+ # :main: Nmap::Parser
39
+ # :title: Ruby Nmap::Parser
29
40
 
30
41
  require 'rexml/document'
31
42
 
32
43
  begin
33
44
  require 'open3'
34
45
  rescue LoadError
35
- # We'll just use IO.popen()
36
46
  end
37
47
 
38
- # Just holds the big Parser class.
48
+ # :stopdoc:
49
+ begin
50
+ require 'rubygems'
51
+ require 'blinkenlights'
52
+ rescue LoadError
53
+ end
54
+ # :startdoc:
55
+
56
+ # Provides a namespace for everything this library creates
39
57
  module Nmap
58
+ # :stopdoc:
59
+
60
+ # Holds all of the classes for the stream-style XML parsing (the
61
+ # listener class and my helper structure classes)
62
+ module XmlParsing
63
+ end
64
+
65
+ # :startdoc:
40
66
  end
41
67
 
42
68
  =begin rdoc
43
69
 
44
70
  == What Is This Library For?
45
71
 
46
- This library provides a Ruby interface to Nmap's scan data. It can run Nmap
47
- and parse its XML output directly from the scan, parse a file containing the
48
- XML data from a separate scan, parse a String of XML data from a scan, or parse
49
- XML data from an object via its read() method. This information is presented
50
- in an easy-to-use and intuitive fashion for storage and manipulation.
72
+ This library provides a Ruby interface to the Nmap Security Scanner and its
73
+ XML formatted scan data. It can run Nmap and parse its XML output directly
74
+ from the scan, parse a file or string of XML scan data, or parse XML scan
75
+ data from an object via its read() method. This information is presented in
76
+ an easy-to-use and intuitive fashion for further storage and manipulation.
51
77
 
52
- Keep in mind that this is not just some Ruby port of Anthony Persaud's Perl
53
- Nmap::Parser! There are more classes, many different methods, and blocks are
54
- extensively available.
78
+ Note that while Anthony Persaud's Perl Nmap::Parser was certainly an
79
+ inspiration when designing this library, there are a number of distinguishing
80
+ characteristics. Very briefly, this library contains more classes, many more
81
+ methods, and has blocks extensively available.
55
82
 
56
- The Nmap Security Scanner is an awesome program written and maintained by
57
- Fyodor <fyodor@insecure.org>. Its main function is port scanning, but it also
83
+ The Nmap Security Scanner is an awesome utility written and maintained by
84
+ Fyodor (fyodor(a)insecure.org). Its main function is port scanning, but it also
58
85
  has service and operating system detection, its own scripting engine and a
59
86
  whole lot more. One of its many available output formats is XML, which allows
60
87
  machines to handle all of the information instead of us slowly sifting through
@@ -64,10 +91,10 @@ tons of output.
64
91
 
65
92
  Depending on the data type, unavailable information is presented differently:
66
93
 
67
- - Arrays are empty
68
- - Non-arrays are nil, unless it's a method that returns the size of one of
69
- the previously mentioned empty arrays. In this case they still return the
70
- size (which would be 0).
94
+ * Arrays are empty
95
+ * Non-arrays are nil, unless it's a method that returns the size of one of
96
+ the previously mentioned empty arrays. In this case they still return the
97
+ size (which would be 0).
71
98
 
72
99
  All information available as arrays are presented via methods. These methods
73
100
  not only return the array, but they also yield each element to a block if one
@@ -101,29 +128,49 @@ is given.
101
128
  |
102
129
  + OSMatch <- OS Match information
103
130
 
104
- == Parsing XML Data Already Available
131
+ == Examples
132
+
133
+ There are two ways to go about getting a new Parser object and actually parsing
134
+ Nmap's XML output:
135
+
136
+ * Call one of the Nmap::Parser class methods to parse the XML and return a new
137
+ Parser object all in one step.
138
+
139
+ * Call Nmap::Parser.new to get a new object and then call one of the instance
140
+ parsing methods (e.g. parsefile()). The main reason to go this route is that
141
+ new() takes a hash of options; for example, this is how the callback feature
142
+ is implemented.
143
+
144
+ === Parsing XML Data Already Available as a String
145
+
146
+ This method is not limited to only String objects, but rather any object which
147
+ responsds to to_str().
105
148
 
106
149
  require 'nmap/parser'
107
150
 
108
- parser = Nmap::Parser.parsestring(xml) # String of XML
109
- parser = Nmap::Parser.new(xml) # Same thing
151
+ parser = Nmap::Parser.new
152
+ parser.parsestring(xml)
110
153
 
111
- == Reading and Parsing from a File
154
+ or
155
+
156
+ parser = Nmap::Parser.parsestring(xml)
157
+
158
+ === Reading and Parsing From a File
112
159
 
113
160
  require 'nmap/parser'
114
161
 
115
162
  parser = Nmap::Parser.parsefile("log.xml")
116
163
 
117
- == Reading and Parsing from an Object
164
+ === Reading and Parsing From an Object
118
165
 
119
- This method can read from any object that responds to a read() method that
120
- returns a String.
166
+ This method can read from any object responding to a read() method that
167
+ returns a String (or something else responding to to_str())
121
168
 
122
169
  require 'nmap/parser'
123
170
 
124
171
  parser = Nmap::Parser.parseread($stdin)
125
172
 
126
- == Scanning and Parsing
173
+ === Scanning and Parsing
127
174
 
128
175
  This is the only Parser method that requires Nmap to be available.
129
176
 
@@ -131,12 +178,34 @@ This is the only Parser method that requires Nmap to be available.
131
178
 
132
179
  parser = Nmap::Parser.parsescan("sudo nmap", "-sVC 192.168.1.0/24")
133
180
 
134
- == Actually Doing Something
181
+ === Registering a Callback
182
+
183
+ To use a callback you create a new Parser object and register a proc or method
184
+ to call each time a new host is parsed, as soon as it's parsed. The callback
185
+ is then run in a new thread and is passed the newly created Nmap::Parser::Host
186
+ object.
187
+
188
+ require 'nmap/parser'
189
+
190
+ callback = proc do |host|
191
+ return if host.status != "up"
192
+ puts "Found #{host.addr}"
193
+ end
194
+
195
+ parser = Nmap::Parser.new(:callback => callback)
196
+ parser.parsefile("nmaplog.xml")
135
197
 
136
- After printing a little session information, this example will cycle
137
- through all of the up hosts, printing state and service information on
138
- the open TCP ports. See the examples directory that comes with this
139
- library for more examples.
198
+ # Found 192.168.10.1
199
+ # Found 192.168.10.2
200
+ # Found 192.168.10.7
201
+ # [...]
202
+
203
+ === Doing a Bit More
204
+
205
+ After printing a little session information, this example will cycle through
206
+ all of the up hosts, printing state and service information on the open TCP
207
+ and UDP ports. See the examples directory that comes with this library for
208
+ more examples.
140
209
 
141
210
  puts "Nmap args: #{parser.session.scan_args}"
142
211
  puts "Runtime: #{parser.session.scan_time} seconds"
@@ -146,130 +215,181 @@ library for more examples.
146
215
  puts "#{host.addr} is up:"
147
216
  puts
148
217
 
149
- host.tcp_ports("open") do |port|
150
- srv = port.service
218
+ [:tcp, :udp].each do |type|
219
+ host.getports(type, "open") do |port|
220
+ srv = port.service
151
221
 
152
- puts "Port ##{port.num}/tcp is open (#{port.reason})"
153
- puts "\tService: #{srv.name}" if srv.name
154
- puts "\tProduct: #{srv.product}" if srv.product
155
- puts "\tVersion: #{srv.version}" if srv.version
156
- puts
222
+ puts "Port ##{port.num}/#{port.proto} is open (#{port.reason})"
223
+ puts "\tService: #{srv.name}" if srv.name
224
+ puts "\tProduct: #{srv.product}" if srv.product
225
+ puts "\tVersion: #{srv.version}" if srv.version
226
+ puts
227
+ end
157
228
  end
158
229
 
159
230
  puts
160
231
  end
232
+
233
+ == Credits
234
+
235
+ Author & Maintainer:
236
+
237
+ * Kris Katterjohn (katterjohn(a)gmail.com)
238
+
239
+ Contributors (in chronological order of first contribution):
240
+
241
+ * Stefan Friedli (stfr(a)scip.ch)
242
+ * Daniel Roethlisberger (daniel(a)roe.ch)
243
+ * Dustin Webber (dustinw(a)aos5.com)
244
+ * Tom Sellers (nmap(a)fadedcode.net)
245
+ * Rory McCune (rorym(a)nmrconsult.net)
246
+ * Russell Fulton (r.fulton(a)auckland.ac.nz)
247
+
248
+ Thanks a lot for taking the time and helping out, everybody!
249
+
250
+ For information on what each contributor actually did, please take a look at
251
+ the project's ChangeLog and Subversion logs.
252
+
161
253
  =end
162
254
  class Nmap::Parser
163
- # Holds the raw XML output from Nmap
255
+ # Raw XML output from the scan
164
256
  attr_reader :rawxml
165
- # Session object for this scan
257
+ # Session object for the scan
166
258
  attr_reader :session
167
259
 
168
- # Read and parse XML from the +obj+. +obj+ can be any object type
169
- # that responds to a read() method that returns a String. IO and
170
- # File are just a couple of examples.
260
+ # Major version number
261
+ Major = 0
262
+ # Minor version number
263
+ Minor = 3
264
+ # Teeny version number
265
+ Teeny = 5
266
+ # Development stage (currently "dev" or "release")
267
+ Stage = "release"
268
+ # Pre-built version string
269
+ Version = "#{Major}.#{Minor}.#{Teeny}"
270
+
271
+ # :method: self.parsefile(filename)
272
+ # Wrapper around the instance method's functionality
171
273
  #
172
- # Returns a new Nmap::Parser object, and passes it to a block if
173
- # one is given
174
- def self.parseread(obj) # :yields: parser
175
- if not obj.respond_to?("read")
176
- raise "Passed object must respond to read()"
177
- end
274
+ # Returns a new Nmap::Parser object and yields it to a block if one is
275
+ # given
276
+
277
+ # :method: self.parseread(obj)
278
+ # Wrapper around the instance method's functionality
279
+ #
280
+ # Returns a new Nmap::Parser object and yields it to a block if one is
281
+ # given
282
+
283
+ # :method: self.parsescan(nmap,args,targets=[])
284
+ # Wrapper around the instance method's functionality
285
+ #
286
+ # Returns a new Nmap::Parser object and yields it to a block if one is
287
+ # given
178
288
 
179
- r = obj.read
289
+ # :method: self.parsestring(str)
290
+ # Wrapper around the instance method's functionality
291
+ #
292
+ # Returns a new Nmap::Parser object and yields it to a block if one is
293
+ # given
180
294
 
181
- if not r.is_a?(String)
182
- raise "Passed object's read() must return a String (got #{r.class})"
295
+ #
296
+ ["file", "read", "scan", "string"].each do |name|
297
+ meth = "parse#{name}"
298
+ self.class_eval("
299
+ def self.#{meth}(*args)
300
+ parser = self.new
301
+ parser.#{meth}(*args)
302
+ yield parser if block_given?
303
+ parser
304
+ end
305
+ ")
306
+ end
307
+
308
+ # Read and parse XML from +obj+. +obj+ can be any object responding
309
+ # to a read() method that returns a String (or something else responding
310
+ # to to_str()). IO and File are just a couple of examples.
311
+ def parseread(obj)
312
+ if not obj.respond_to?(:read)
313
+ raise TypeError, "Passed object must respond to read()"
183
314
  end
184
315
 
185
- new(r) { |p| yield p if block_given? }
316
+ parsestring(obj.read)
186
317
  end
187
318
 
188
319
  # Read and parse the contents of the Nmap XML file +filename+
189
- #
190
- # Returns a new Nmap::Parser object, and passes it to a block if
191
- # one is given
192
- def self.parsefile(filename) # :yields: parser
193
- begin
194
- File.open(filename) { |f|
195
- parseread(f) { |p| yield p if block_given? }
196
- }
197
- rescue
198
- raise "Error parsing \"#{filename}\": #{$!}"
199
- end
320
+ def parsefile(filename)
321
+ File.open(filename) { |f| parseread(f) }
322
+ rescue
323
+ raise $!.class, "Error parsing \"#{filename}\": #{$!}"
200
324
  end
201
325
 
202
- # Read and parse the String of XML. Currently an alias for new().
203
- #
204
- # Returns a new Nmap::Parser object, and passes it to a block if
205
- # one is given
206
- def self.parsestring(str) # :yields: parser
207
- new(str) { |p| yield p if block_given? }
326
+ # Read and parse a String (or something else responding to to_str()) of
327
+ # XML
328
+ def parsestring(str)
329
+ if not str.respond_to?(:to_str)
330
+ raise TypeError, "XML data should be a String, or must respond to to_str()"
331
+ end
332
+
333
+ parse(str.to_str)
208
334
  end
209
335
 
210
- # Runs "+nmap+ -d +args+ +targets+"; returns a new Nmap::Parser object,
211
- # and passes it to a block if one is given.
336
+ # Essentially runs "+nmap+ -d +args+ +targets+"
212
337
  #
213
338
  # +nmap+ is here to allow you to do things like:
214
339
  #
215
- # parser = Nmap::Parser.parsescan("sudo ./nmap", ....)
340
+ # parser.parsescan("sudo ./nmap", arguments, targets)
216
341
  #
217
- # and still make it easy for me to inject the options for XML
218
- # output and debugging.
342
+ # and still make it easy for me to inject the options for XML output
343
+ # and debugging.
219
344
  #
220
- # +args+ can't contain arguments like -oA, -oX, etc. as these can
345
+ # +args+ can't contain arguments like -oA, -oX, etc. as these could
221
346
  # interfere with Parser's processing. If you need that other output,
222
- # just run Nmap yourself and pass -oX output to Parser via new. Or,
223
- # you can use rawxml to grab the whole XML (as a String) and save it
224
- # to a different file.
347
+ # you could run Nmap yourself and just pass the -oX output to Parser.
348
+ # Or you could use rawxml to grab the XML from the scan and write it
349
+ # to a file, for example.
225
350
  #
226
- # +targets+ is an array of targets which will be split and appended to
227
- # the command. It's optional and only for convenience because you can
228
- # put any targets you want scanned in +args+.
229
- def self.parsescan(nmap, args, targets = []) # :yields: parser
230
- if args =~ /[^-]-o|^-o/
231
- raise "Output option (-o*) passed to parsescan()"
351
+ # +targets+ is an optional array of target specifications. It's here
352
+ # only for convenience because you can also put any targets you want
353
+ # scanned in +args+ (which is what I tend to do unless I happen to
354
+ # already have a collection of targets as an array).
355
+ def parsescan(nmap, args, targets = [])
356
+ if args =~ /\s-o|^-o/
357
+ raise ArgumentError, "Output option (-o*) passed to parsescan()"
232
358
  end
233
359
 
234
- # Enable debugging, give us our XML output, pass args
235
- command = nmap + " -d -oX - " + args + " "
236
-
237
- command += targets.join(" ") if targets.any?
238
-
239
- p = nil
360
+ # Enable debugging and XML; pass args and targets
361
+ command = "#{nmap} -d -oX - #{args} #{targets.join(" ")}"
240
362
 
241
363
  begin
242
364
  # First try popen3() if it loaded successfully..
243
365
  Open3.popen3(command) do |sin, sout, serr|
244
- p = parseread(sout)
366
+ parseread(sout)
245
367
  end
246
368
  rescue NameError
247
369
  # ..but fall back to popen() if not
248
370
  IO.popen(command) do |io|
249
- p = parseread(io)
371
+ parseread(io)
250
372
  end
251
373
  end
252
-
253
- yield p if block_given?
254
-
255
- p
256
374
  end
257
375
 
258
- # Returns an array of Host objects, and passes them each to a block if
376
+ # Returns an array of Host objects and yields them each to a block if
259
377
  # one is given
260
378
  #
261
379
  # If an argument is given, only hosts matching +status+ are given
380
+ #
381
+ # NOTE: Calling parser.hosts(status).size can be very different than
382
+ # running parser.session.numhosts(status) because the information there
383
+ # and here are coming from different places in the XML. Nmap will not
384
+ # typically list individual hosts which it doesn't know or assume are
385
+ # "up".
262
386
  def hosts(status = "") # :yields: host
263
- shosts = []
264
-
265
- @hosts.each do |host|
387
+ @hosts.map { |host|
266
388
  if status.empty? or host.status == status
267
- shosts << host
268
389
  yield host if block_given?
390
+ host
269
391
  end
270
- end
271
-
272
- shosts
392
+ }.compact
273
393
  end
274
394
 
275
395
  # Returns a Host object for the host with the specified IP address
@@ -284,11 +404,8 @@ class Nmap::Parser
284
404
 
285
405
  # Deletes host with the specified IP address or hostname +hostip+
286
406
  #
287
- # Note: From inside of a block given to a method like hosts() or
288
- # get_ips(), calling this method on a host passed to the block may
289
- # lead to adverse effects:
290
- #
291
- # parser.hosts { |h| puts h.addr; parser.del_host(h) } # Don't do this!
407
+ # Calling this method from inside of a block given to a method like
408
+ # hosts() or get_ips() may lead to adverse effects.
292
409
  def del_host(hostip)
293
410
  @hosts.delete_if do |host|
294
411
  host.addr == hostip or host.hostname == hostip
@@ -297,60 +414,148 @@ class Nmap::Parser
297
414
 
298
415
  alias delete_host del_host
299
416
 
300
- # Returns an array of IPs scanned, and passes them each to a
301
- # block if one is given
417
+ # Returns an array of IPs scanned and yields them each to a block if
418
+ # one is given
302
419
  #
303
420
  # If an argument is given, only hosts matching +status+ are given
421
+ #
422
+ # NOTE: Calling parser.get_ips(status).size can be very different than
423
+ # running parser.session.numhosts(status) because the information there
424
+ # and here are coming from different places in the XML. Nmap will not
425
+ # typically list individual hosts which it doesn't know or assume are
426
+ # "up".
304
427
  def get_ips(status = "") # :yields: host.addr
305
- ips = hosts(status).map { |h| h.addr }
428
+ hosts(status).map do |host|
429
+ yield host.addr if block_given?
430
+ host.addr
431
+ end
432
+ end
306
433
 
307
- ips.each { |ip| yield host.addr } if block_given?
434
+ # This operator simply compares the rawxml members
435
+ def ==(pa)
436
+ @rawxml == pa.rawxml
437
+ end
438
+
439
+ # Returns a new Parser object with the following characteristics:
440
+ # * rawxml = nil
441
+ # * session = nil
442
+ # * contains hosts from both operands. If any of the hosts in the
443
+ # first operand are also in the second (as determined by comparing
444
+ # host.addr information), the duplicate hosts from the second one
445
+ # are not available.
446
+ def +(pa)
447
+ return nil unless self.class == pa.class
448
+ n = Nmap::Parser.new
449
+ n.rawxml = nil
450
+ n.session = nil
451
+ [ self.hosts, pa.hosts ].each do |h|
452
+ n.addhosts(h)
453
+ end
454
+ n
455
+ end
308
456
 
309
- ips
457
+ # Returns a boolean value depending on whether this object is just a
458
+ # combination of others (e.g. using +)
459
+ def combination?
460
+ rawxml.nil? and session.nil? and not @fresh
310
461
  end
311
462
 
312
- # This operator compares the rawxml members
313
- def ==(parser)
314
- @rawxml == parser.rawxml
463
+ protected
464
+
465
+ # :stopdoc:
466
+
467
+ attr_writer :rawxml, :session
468
+
469
+ def addhosts(a)
470
+ @hosts << a.find_all do |h|
471
+ not host(h.addr) # ignore dups
472
+ end
473
+ @fresh = false
474
+ @hosts.flatten!
315
475
  end
316
476
 
317
477
  private
318
478
 
319
- def initialize(xml) # :yields: parser
320
- if not xml.is_a?(String)
321
- raise "Must be passed a String (got #{xml.class})"
479
+ def option_callback(callback)
480
+ return if callback.nil?
481
+
482
+ @callback = callback
483
+
484
+ klasses = []
485
+ klasses.push(Proc)
486
+ klasses.push(Method)
487
+ klasses.push(UnboundMethod) if defined?(UnboundMethod)
488
+
489
+ return if klasses.find do |klass|
490
+ @callback.instance_of?(klass)
322
491
  end
323
492
 
324
- parse(xml)
493
+ raise TypeError, "Bad callback type: must be a Proc, Method or UnboundMethod"
494
+ end
325
495
 
326
- yield self if block_given?
496
+ def option_blinken(tty)
497
+ return if tty.nil? or not defined?(BlinkenLights)
498
+ path = (tty.instance_of?(String) and tty or "/dev/console")
499
+ @blinken = proc do
500
+ BlinkenLights.open(path, 0.2) do |lights|
501
+ lights.left_to_right while true
502
+ end
503
+ end
327
504
  end
328
505
 
329
506
  def parse(xml)
507
+ return unless @fresh
508
+
330
509
  @rawxml = xml
331
510
 
332
- begin
333
- root = REXML::Document.new(xml).root
334
- rescue
335
- raise "Error parsing XML: #{$!}"
336
- end
511
+ # :)
512
+ lthr = Thread.new { @blinken.call } if @blinken
337
513
 
338
- raise "Error in XML data" if root.nil?
514
+ parser = Nmap::XmlParsing::MyParser.new(@callback)
515
+ REXML::Document.parse_stream(xml, parser)
339
516
 
340
- @session = Session.new(root)
517
+ lthr.kill if @blinken
341
518
 
342
- @hosts = root.elements.collect('host') do |host|
343
- Host.new(host)
519
+ raise IOError, "Error parsing XML" unless parser.completed?
520
+
521
+ @session = parser.session
522
+
523
+ @hosts = parser.hosts
524
+
525
+ @fresh = false
526
+
527
+ true
528
+ end
529
+
530
+ # :startdoc:
531
+
532
+ # Creates a fresh Parser object, taking a hash of options as an optional
533
+ # argument. Use the instance parsing methods to read in the XML and
534
+ # parse the data into the available classes.
535
+ #
536
+ # Returns the new Nmap::Parser object and yields it to a block if one is
537
+ # given
538
+ def initialize(opts = {}) # :yields: parser
539
+ @hosts = []
540
+ @fresh = true
541
+
542
+ opts.keys.each do |key|
543
+ begin
544
+ send("option_#{key}", opts[key])
545
+ rescue NoMethodError
546
+ end
344
547
  end
548
+
549
+ yield self if block_given?
345
550
  end
346
551
  end
347
552
 
348
553
  # This holds session information, such as runtime, Nmap's arguments,
349
554
  # and verbosity/debugging
350
555
  class Nmap::Parser::Session
351
- # Holds the command run to initiate the scan
556
+ # Command run to initiate the scan
352
557
  attr_reader :scan_args
353
- # The Nmap version number used to scan
558
+ # Version number of the Nmap used to scan
354
559
  attr_reader :nmap_version
355
560
  # XML version of Nmap's output
356
561
  attr_reader :xml_version
@@ -358,8 +563,12 @@ class Nmap::Parser::Session
358
563
  attr_reader :start_str, :start_time
359
564
  # Ending time
360
565
  attr_reader :stop_str, :stop_time
361
- # Total scan time in seconds (can differ from stop_time - start_time)
566
+ # Total scan time in seconds (could differ from stop_time - start_time)
362
567
  attr_reader :scan_time
568
+ # Nmap's exit status ("success" or "error")
569
+ attr_reader :exit
570
+ # Nmap's error message if exit status is "error"
571
+ attr_reader :errormsg
363
572
  # Amount of verbosity (-v) used while scanning
364
573
  attr_reader :verbose
365
574
  # Amount of debugging (-d) used while scanning
@@ -368,21 +577,28 @@ class Nmap::Parser::Session
368
577
  alias verbosity verbose
369
578
  alias debugging debug
370
579
 
580
+ # Returns the total number of hosts that were scanned or, if an
581
+ # argument is given, returns the number of hosts scanned that were
582
+ # matching +status+ (e.g. "up" or "down")
583
+ #
584
+ # NOTE: Calling parser.sessions.numhosts(status) can be very different
585
+ # than running parser.hosts(status).size because the information there
586
+ # and here are coming from different places in the XML. Nmap will not
587
+ # typically list individual hosts which it doesn't know or assume are
588
+ # "up".
589
+ def numhosts(state = "")
590
+ @numhosts[state.empty? ? "total" : state]
591
+ end
592
+
371
593
  # Returns the total number of services that were scanned or, if an
372
594
  # argument is given, returns the number of services scanned for +type+
373
595
  # (e.g. "syn")
374
596
  def numservices(type = "")
375
- total = 0
376
-
377
- @scaninfo.each do |info|
378
- if type.empty?
379
- total += info.numservices
380
- elsif info.type == type
381
- return info.numservices
382
- end
383
- end
384
-
385
- total
597
+ @scaninfo.find_all { |info|
598
+ type.empty? or info.type == type
599
+ }.inject(0) { |acc, info|
600
+ acc + info.numservices
601
+ }
386
602
  end
387
603
 
388
604
  # Returns the protocol associated with the specified scan +type+
@@ -395,17 +611,13 @@ class Nmap::Parser::Session
395
611
  nil
396
612
  end
397
613
 
398
- # Returns an array of all the scan types performed, and passes them
614
+ # Returns an array of all the scan types performed and yields them
399
615
  # each to a block if one if given
400
616
  def scan_types() # :yields: scantype
401
- types = []
402
-
403
- @scaninfo.each do |info|
404
- types << info.type
617
+ @scaninfo.map do |info|
405
618
  yield info.type if block_given?
619
+ info.type
406
620
  end
407
-
408
- types
409
621
  end
410
622
 
411
623
  # Returns the scanflags associated with the specified scan +type+
@@ -420,32 +632,48 @@ class Nmap::Parser::Session
420
632
 
421
633
  private
422
634
 
635
+ # :stopdoc:
636
+
423
637
  def initialize(root)
424
638
  parse(root)
425
639
  end
426
640
 
427
641
  def parse(root)
428
- @scan_args = root.attributes['args']
642
+ @scan_args = root[:attrs]['args']
643
+
644
+ @nmap_version = root[:attrs]['version']
429
645
 
430
- @nmap_version = root.attributes['version']
646
+ @xml_version = root[:attrs]['xmloutputversion'].to_f
431
647
 
432
- @xml_version = root.attributes['xmloutputversion'].to_f
648
+ @start_str = root[:attrs]['startstr']
649
+ @start_time = root[:attrs]['start'].to_i
433
650
 
434
- @start_str = root.attributes['startstr']
435
- @start_time = root.attributes['start'].to_i
651
+ runstats = root[:kids].find_tag(:runstats)
652
+ finished = runstats[:kids].find_tag(:finished)
436
653
 
437
- @stop_str = root.elements['runstats/finished'].attributes['timestr']
438
- @stop_time = root.elements['runstats/finished'].attributes['time'].to_i
654
+ @stop_str = finished[:attrs]['timestr']
655
+ @stop_time = finished[:attrs]['time'].to_i
439
656
 
440
- @scan_time = root.elements['runstats/finished'].attributes['elapsed'].to_f
657
+ elapsed = finished[:attrs]['elapsed']
658
+ @scan_time = elapsed ? elapsed.to_f : (@stop_time - @start_time).to_f
441
659
 
442
- @verbose = root.elements['verbose'].attributes['level'].to_i
443
- @debug = root.elements['debugging'].attributes['level'].to_i
660
+ @exit = finished[:attrs]['exit']
661
+ @errormsg = finished[:attrs]['errormsg']
662
+
663
+ @verbose = root[:kids].find_tag(:verbose)[:attrs]['level'].to_i
664
+ @debug = root[:kids].find_tag(:debugging)[:attrs]['level'].to_i
665
+
666
+ @numhosts = {}
667
+ runstats[:kids].find_tag(:hosts)[:attrs].each_pair do |k, v|
668
+ @numhosts[k] = v.to_i
669
+ end
444
670
 
445
- @scaninfo = root.elements.collect('scaninfo') do |info|
671
+ @scaninfo = root[:kids].collect_tags(:scaninfo) do |info|
446
672
  ScanInfo.new(info)
447
673
  end
448
674
  end
675
+
676
+ # :startdoc:
449
677
  end
450
678
 
451
679
  class Nmap::Parser::Session::ScanInfo # :nodoc: all
@@ -458,10 +686,10 @@ class Nmap::Parser::Session::ScanInfo # :nodoc: all
458
686
  end
459
687
 
460
688
  def parse(info)
461
- @type = info.attributes['type']
462
- @scanflags = info.attributes['scanflags']
463
- @proto = info.attributes['protocol']
464
- @numservices = info.attributes['numservices'].to_i
689
+ @type = info[:attrs]['type']
690
+ @scanflags = info[:attrs]['scanflags']
691
+ @proto = info[:attrs]['protocol']
692
+ @numservices = info[:attrs]['numservices'].to_i
465
693
  end
466
694
  end
467
695
 
@@ -471,21 +699,21 @@ end
471
699
  # available in this class; either accessed through here or directly
472
700
  # from a Port object.
473
701
  class Nmap::Parser::Host
474
- # The status of the host, typically "up" or "down"
702
+ # Status of the host, typically "up" or "down"
475
703
  attr_reader :status
476
- # The reason for the status
704
+ # Reason for the status
477
705
  attr_reader :reason
478
- # The IPv4 address
706
+ # IPv4 address
479
707
  attr_reader :ip4_addr
480
- # The IPv6 address
708
+ # IPv6 address
481
709
  attr_reader :ip6_addr
482
- # The MAC address
710
+ # MAC address
483
711
  attr_reader :mac_addr
484
- # The MAC vendor
712
+ # MAC vendor
485
713
  attr_reader :mac_vendor
486
714
  # OS object holding Operating System information
487
715
  attr_reader :os
488
- # The number of "weird responses"
716
+ # Number of "weird responses"
489
717
  attr_reader :smurf
490
718
  # TCP Sequence Number information
491
719
  attr_reader :tcpsequence_index, :tcpsequence_class
@@ -514,45 +742,81 @@ class Nmap::Parser::Host
514
742
  @ip4_addr or @ip6_addr
515
743
  end
516
744
 
517
- # Returns an array containing all of the hostnames for this host,
518
- # and passes them each to a block if one is given
519
- def all_hostnames
745
+ # Returns an array containing all of the hostnames for this host and
746
+ # yields them each to a block if one is given
747
+ def hostnames
520
748
  @hostnames.each { |hostname| yield hostname } if block_given?
521
-
522
749
  @hostnames
523
750
  end
524
751
 
525
- alias hostnames all_hostnames
752
+ alias all_hostnames hostnames
526
753
 
527
- # Returns the first hostname, or nil if unavailable
754
+ # Returns the first hostname
528
755
  def hostname
529
756
  @hostnames[0]
530
757
  end
531
758
 
532
- # Returns an array of ExtraPorts objects, and passes them each to a
759
+ # Returns an array of ExtraPorts objects and yields them each to a
533
760
  # block if one if given
534
761
  def extraports # :yields: extraports
535
762
  @extraports.each { |e| yield e } if block_given?
536
-
537
763
  @extraports
538
764
  end
539
765
 
540
- # Returns the Port object for the TCP port +portnum+, and passes it to
541
- # a block if one is given
542
- def tcp_port(portnum) # :yields: port
543
- port = @tcpPorts[portnum.to_i]
766
+ # Returns the Port object for the port +portnum+ of protocol +type+
767
+ # (:tcp, :udp, :sctp or :ip) and yields it to a block if one is given.
768
+ def getport(type, portnum) # :yields: port
769
+ type = type.to_sym
770
+
771
+ port = case type
772
+ when :tcp, :udp, :sctp, :ip
773
+ @ports[type][portnum.to_i]
774
+ else
775
+ raise ArgumentError, "Invalid protocol type"
776
+ end
777
+
544
778
  yield port if block_given?
779
+
545
780
  port
546
781
  end
547
782
 
548
- # Returns an array of Port objects for each TCP port, and passes them
549
- # each to a block if one is given
783
+ # Returns an array of Port objects for each port of protocol +type+
784
+ # (:tcp, :udp, :sctp or :ip) and yields them each to a block if one is
785
+ # given
786
+ #
787
+ # If +type+ is :any rather than a protocol name, then matching ports
788
+ # from all protocols are given.
789
+ #
790
+ # If +type+ is an array, it's assumed to be filled with any combination
791
+ # of the aforementioned protocol types. Some handling is done on the
792
+ # array, but you could do [:tcp, :udp, :any] if you really want.
550
793
  #
551
- # If an argument is given, only ports matching +state+ are given. Note
794
+ # If +state+ is given, only ports matching that state are given. Note
552
795
  # that combinations like "open|filtered" will get matched by "open" and
553
796
  # "filtered"
554
- def tcp_ports(state = "")
555
- list = @tcpPorts.values.find_all { |port|
797
+ def getports(type, state = "")
798
+ if type.instance_of?(Array)
799
+ list = type.flatten.uniq.inject([]) { |acc, typ|
800
+ acc + getports(typ, state)
801
+ }.sort
802
+
803
+ list.each { |port| yield port } if block_given?
804
+
805
+ return list
806
+ end
807
+
808
+ type = type.to_sym
809
+
810
+ ports = case type
811
+ when :tcp, :udp, :sctp, :ip
812
+ @ports[type].values
813
+ when :any
814
+ @ports.map { |ent| ent[1].values }.flatten
815
+ else
816
+ raise ArgumentError, "Invalid protocol type (#{type})"
817
+ end
818
+
819
+ list = ports.find_all { |port|
556
820
  state.empty? or
557
821
  port.state == state or
558
822
  port.state.split(/\|/).include?(state)
@@ -563,209 +827,186 @@ class Nmap::Parser::Host
563
827
  list
564
828
  end
565
829
 
566
- # Returns an array of TCP port numbers, and passes them each to a block
567
- # if one given
830
+ # Returns an array of port numbers of protocol +type+ (:tcp, :udp, :sctp
831
+ # or :ip) and yields them each to a block if one given
568
832
  #
569
- # If an argument is given, only ports matching +state+ are given.
570
- def tcp_port_list(state = "")
571
- list = tcp_ports(state).map { |p| p.num }
572
- list.each { |port| yield port } if block_given?
573
- list
833
+ # If +state+ is given, only ports matching that state are given. Note
834
+ # that combinations like "open|filtered" will get matched by "open" and
835
+ # "filtered"
836
+ def getportlist(type, state = "") # :yields: port
837
+ getports(type, state).map do |port|
838
+ yield port.num if block_given?
839
+ port.num
840
+ end
574
841
  end
575
842
 
843
+ # :method: tcp_port(portnum)
844
+ # Just like getport(:tcp, +portnum+)
845
+
846
+ # :method: tcp_ports(state="")
847
+ # Just like getports(:tcp, +state+)
848
+
849
+ # :method: tcp_port_list(state="")
850
+ # Just like getportlist(:tcp, +state+)
851
+
852
+ # :method: tcp_state(portnum)
853
+ # Returns the state of TCP port +portnum+
854
+
855
+ # :method: tcp_reason(portnum)
576
856
  # Returns the state reason of TCP port +portnum+
577
- def tcp_reason(portnum)
578
- port = tcp_port(portnum)
579
- return nil if port.nil?
580
- port.reason
581
- end
582
857
 
858
+ # :method: tcp_service(portnum)
859
+ # Returns the Port::Service for TCP port +portnum+
860
+
861
+ # :method: tcp_script(portnum,name)
583
862
  # Returns the Script object for the script +name+ run against the
584
863
  # TCP port +portnum+
585
- def tcp_script(portnum, name)
586
- port = tcp_port(portnum)
587
- return nil if port.nil?
588
- port.script(name)
589
- end
590
864
 
591
- # Returns an array of Script objects for each script run on the
592
- # TCP port +portnum+, and passes them each to a block if given
593
- def tcp_scripts(portnum) # :yields: script
594
- port = tcp_port(portnum)
595
- return nil if port.nil?
596
- port.scripts { |s| yield s } if block_given?
597
- port.scripts
598
- end
865
+ # :method: tcp_scripts(portnum)
866
+ # Returns an array of Script objects for each script run on the TCP
867
+ # port +portnum+ and yields them each to a block if one is given
599
868
 
869
+ # :method: tcp_script_output(portnum,name)
600
870
  # Returns the output of the script +name+ on the TCP port +portnum+
601
- def tcp_script_output(portnum, name)
602
- port = tcp_port(portnum)
603
- return nil if port.nil?
604
- port.script_output(name)
605
- end
606
871
 
607
- # Returns a Port::Service object for TCP port +portnum+
608
- def tcp_service(portnum)
609
- port = tcp_port(portnum)
610
- return nil if port.nil?
611
- port.service
612
- end
872
+ # :method: udp_port(portnum)
873
+ # Just like getport(:udp, +portnum+)
613
874
 
614
- # Returns the state of TCP port +portnum+
615
- def tcp_state(portnum)
616
- port = tcp_port(portnum)
617
- return nil if port.nil?
618
- port.state
619
- end
875
+ # :method: udp_ports(state="")
876
+ # Just like getports(:udp, +state+)
620
877
 
621
- # Returns the Port object for the UDP port +portnum+, and passes it to
622
- # a block if one is given
623
- def udp_port(portnum) # :yields: port
624
- port = @udpPorts[portnum.to_i]
625
- yield port if block_given?
626
- port
627
- end
878
+ # :method: udp_port_list(state="")
879
+ # Just like getportlist(:udp, +state+)
628
880
 
629
- # Returns an array of Port objects for each UDP port, and passes them
630
- # each to a block if one is given
631
- #
632
- # If an argument is given, only ports matching +state+ are given. Note
633
- # that combinations like "open|filtered" will get matched by "open" and
634
- # "filtered"
635
- def udp_ports(state = "")
636
- list = @udpPorts.values.find_all { |port|
637
- state.empty? or
638
- port.state == state or
639
- port.state.split(/\|/).include?(state)
640
- }.sort
641
-
642
- list.each { |port| yield port } if block_given?
643
-
644
- list
645
- end
646
-
647
- # Returns an array of UDP port numbers, and passes them each to a block
648
- # if one is given
649
- #
650
- # If an argument is given, only ports matching +state+ are given.
651
- def udp_port_list(state = "")
652
- list = udp_ports(state).map { |p| p.num }
653
- list.each { |port| yield port } if block_given?
654
- list
655
- end
881
+ # :method: udp_state(portnum)
882
+ # Returns the state of UDP port +portnum+
656
883
 
884
+ # :method: udp_reason(portnum)
657
885
  # Returns the state reason of UDP port +portnum+
658
- def udp_reason(portnum)
659
- port = udp_port(portnum)
660
- return nil if port.nil?
661
- port.reason
662
- end
663
886
 
887
+ # :method: udp_service(portnum)
888
+ # Returns the Port::Service for UDP port +portnum+
889
+
890
+ # :method: udp_script(portnum,name)
664
891
  # Returns the Script object for the script +name+ run against the
665
892
  # UDP port +portnum+
666
- def udp_script(portnum, name)
667
- port = udp_port(portnum)
668
- return nil if port.nil?
669
- port.script(name)
670
- end
671
893
 
672
- # Returns an array of Script objects for each script run on the
673
- # UDP port +portnum+, and passes them each to a block if given
674
- def udp_scripts(portnum) # :yields: script
675
- port = udp_port(portnum)
676
- return nil if port.nil?
677
- port.scripts { |s| yield s } if block_given?
678
- port.scripts
679
- end
894
+ # :method: udp_scripts(portnum)
895
+ # Returns an array of Script objects for each script run on the UDP
896
+ # port +portnum+ and yields them each to a block if one is given
680
897
 
898
+ # :method: udp_script_output(portnum,name)
681
899
  # Returns the output of the script +name+ on the UDP port +portnum+
682
- def udp_script_output(portnum, name)
683
- port = udp_port(portnum)
684
- return nil if port.nil?
685
- port.script_output(name)
686
- end
687
900
 
688
- # Returns a Port::Service object for UDP port +portnum+
689
- def udp_service(portnum)
690
- port = udp_port(portnum)
691
- return nil if port.nil?
692
- port.service
693
- end
901
+ # :method: sctp_port(portnum)
902
+ # Just like getport(:sctp, +portnum+)
694
903
 
695
- # Returns the state of UDP port +portnum+
696
- def udp_state(portnum)
697
- port = udp_port(portnum)
698
- return nil if port.nil?
699
- port.state
700
- end
904
+ # :method: sctp_ports(state="")
905
+ # Just like getports(:sctp, +state+)
701
906
 
702
- # Returns the Port object for the IP protocol +protonum+, and passes it
703
- # to a block if one is given
704
- def ip_proto(protonum) # :yields: proto
705
- proto = @ipProtos[protonum.to_i]
706
- yield proto if block_given?
707
- proto
708
- end
907
+ # :method: sctp_port_list(state="")
908
+ # Just like getportlist(:sctp, +state+)
709
909
 
710
- # Returns an array of Port objects for each IP protocol, and passes
711
- # them each to a block if one is given
712
- #
713
- # If an argument is given, only protocols matching +state+ are given.
714
- # Note that combinations like "open|filtered" will get matched by
715
- # "open" and "filtered"
716
- def ip_protos(state = "")
717
- list = @ipProtos.values.find_all { |proto|
718
- state.empty? or
719
- proto.state == state or
720
- proto.state.split(/\|/).include?(state)
721
- }.sort
910
+ # :method: sctp_state(portnum)
911
+ # Returns the state of SCTP port +portnum+
722
912
 
723
- list.each { |proto| yield proto } if block_given?
913
+ # :method: sctp_reason(portnum)
914
+ # Returns the state reason of SCTP port +portnum+
724
915
 
725
- list
726
- end
916
+ # :method: sctp_service(portnum)
917
+ # Returns the Port::Service for SCTP port +portnum+
918
+
919
+ # :method: ip_proto(protonum)
920
+ # Just like getport(:ip, +protonum+)
921
+
922
+ # :method: ip_protos(state="")
923
+ # Just like getports(:ip, +state+)
924
+
925
+ # :method: ip_proto_list(state="")
926
+ # Just like getportlist(:ip, +state+)
927
+
928
+ # :method: ip_reason(protonum)
929
+ # Returns the state of IP proto +protonum+
930
+
931
+ # :method: ip_state(protonum)
932
+ # Returns the state reason of IP proto +protonum+
933
+
934
+ # :method: ip_service(protonum)
935
+ # Returns the Port::Service for IP proto +protonum+
727
936
 
728
- # Returns an array of IP protocol numbers, and passes them each to a
729
- # block if one given
730
937
  #
731
- # If an argument is given, only protocols matching +state+ are given.
732
- def ip_proto_list(state = "")
733
- list = ip_protos(state).map { |p| p.num }
734
- list.each { |proto| yield proto } if block_given?
735
- list
736
- end
938
+ [
939
+ ["tcp", "port"],
940
+ ["udp", "port"],
941
+ ["sctp", "port"],
942
+ ["ip", "proto"]
943
+ ].each do |type, prt|
944
+ self.class_eval("
945
+ def #{type}_#{prt}(portnum)
946
+ getport(:#{type}, portnum) do |p|
947
+ yield p if block_given?
948
+ end
949
+ end
737
950
 
738
- # Returns the state reason of IP protocol +protonum+
739
- def ip_reason(protonum)
740
- proto = ip_proto(protonum)
741
- return nil if proto.nil?
742
- proto.reason
743
- end
951
+ def #{type}_#{prt}s(state = '')
952
+ getports(:#{type}, state) do |p|
953
+ yield p if block_given?
954
+ end
955
+ end
744
956
 
745
- # Returns a Port::Service object for IP protocol +protonum+
746
- def ip_service(protonum)
747
- proto = ip_proto(protonum)
748
- return nil if proto.nil?
749
- proto.service
750
- end
957
+ def #{type}_#{prt}_list(state = '')
958
+ getportlist(:#{type}, state) do |p|
959
+ yield p if block_given?
960
+ end
961
+ end
962
+
963
+ def #{type}_state(portnum)
964
+ (#{type}_#{prt}(portnum) or return).state
965
+ end
966
+
967
+ def #{type}_reason(portnum)
968
+ (#{type}_#{prt}(portnum) or return).reason
969
+ end
970
+
971
+ def #{type}_service(portnum)
972
+ (#{type}_#{prt}(portnum) or return).service
973
+ end
974
+ ")
751
975
 
752
- # Returns the state of IP protocol +protonum+
753
- def ip_state(protonum)
754
- proto = ip_proto(protonum)
755
- return nil if proto.nil?
756
- proto.state
976
+ next unless ['tcp', 'udp'].include?(type)
977
+
978
+ self.class_eval("
979
+ def #{type}_script(portnum, name)
980
+ script = (#{type}_port(portnum) or return).script(name)
981
+ return if not script
982
+ yield script if block_given?
983
+ script
984
+ end
985
+
986
+ def #{type}_scripts(portnum)
987
+ port = #{type}_port(portnum) or return
988
+ port.scripts { |script| yield script } if block_given?
989
+ port.scripts
990
+ end
991
+
992
+ def #{type}_script_output(portnum, name)
993
+ (#{type}_port(portnum) or return).script_output(name)
994
+ end
995
+ ")
757
996
  end
758
997
 
759
998
  # Returns the Script object for the specified host script +name+
760
999
  def script(name)
761
- @scripts.find { |script| script.id == name }
1000
+ sc = @scripts.find { |script| script.id == name }
1001
+ return if not sc
1002
+ yield sc if block_given?
1003
+ sc
762
1004
  end
763
1005
 
764
- # Returns an array of Script objects for each host script run, and
765
- # passes them each to a block if given
1006
+ # Returns an array of Script objects for each host script run and
1007
+ # yields them each to a block if given
766
1008
  def scripts
767
1009
  @scripts.each { |script| yield script } if block_given?
768
-
769
1010
  @scripts
770
1011
  end
771
1012
 
@@ -780,49 +1021,47 @@ class Nmap::Parser::Host
780
1021
 
781
1022
  private
782
1023
 
783
- def initialize(hostinfo)
784
- parse(hostinfo)
785
- end
1024
+ # :stopdoc:
786
1025
 
787
1026
  def parseAddr(elem)
788
- case elem.attributes['addrtype']
1027
+ case elem[:attrs]['addrtype']
789
1028
  when "mac"
790
- @mac_addr = elem.attributes['addr']
791
- @mac_vendor = elem.attributes['vendor']
1029
+ @mac_addr = elem[:attrs]['addr']
1030
+ @mac_vendor = elem[:attrs]['vendor']
792
1031
  when "ipv4"
793
- @ip4_addr = elem.attributes['addr']
1032
+ @ip4_addr = elem[:attrs]['addr']
794
1033
  when "ipv6"
795
- @ip6_addr = elem.attributes['addr']
1034
+ @ip6_addr = elem[:attrs]['addr']
796
1035
  end
797
1036
  end
798
1037
 
799
1038
  def parseHostnames(elem)
800
1039
  @hostnames = []
801
1040
 
802
- return nil if elem.nil?
1041
+ return if elem.nil?
803
1042
 
804
- @hostnames = elem.elements.collect('hostname') do |name|
805
- name.attributes['name']
1043
+ @hostnames = elem[:kids].collect_tags(:hostname) do |name|
1044
+ name[:attrs]['name']
806
1045
  end
807
1046
  end
808
1047
 
809
1048
  def parsePorts(ports)
810
- @tcpPorts = {}
811
- @udpPorts = {}
812
- @ipProtos = {}
813
-
814
- return nil if ports.nil?
815
-
816
- ports.each_element('port') do |port|
817
- num = port.attributes['portid'].to_i
818
- proto = port.attributes['protocol']
819
-
820
- if proto == "tcp"
821
- @tcpPorts[num] = Port.new(port)
822
- elsif proto == "udp"
823
- @udpPorts[num] = Port.new(port)
824
- elsif proto == "ip"
825
- @ipProtos[num] = Port.new(port)
1049
+ @ports = {
1050
+ :tcp => {},
1051
+ :udp => {},
1052
+ :sctp => {},
1053
+ :ip => {}
1054
+ }
1055
+
1056
+ return if ports.nil?
1057
+
1058
+ ports[:kids].each_tag(:port) do |port|
1059
+ num = port[:attrs]['portid'].to_i
1060
+ proto = port[:attrs]['protocol'].to_sym
1061
+
1062
+ case proto
1063
+ when :tcp, :udp, :sctp, :ip
1064
+ @ports[proto][num] = Port.new(port)
826
1065
  end
827
1066
  end
828
1067
  end
@@ -830,104 +1069,107 @@ class Nmap::Parser::Host
830
1069
  def parseExtraPorts(ports)
831
1070
  @extraports = []
832
1071
 
833
- return nil if ports.nil?
1072
+ return if ports.nil?
834
1073
 
835
- @extraports = ports.elements.collect('extraports') do |e|
1074
+ @extraports = ports[:kids].collect_tags(:extraports) { |e|
836
1075
  ExtraPorts.new(e)
837
- end
838
-
839
- @extraports.sort!
1076
+ }.sort
840
1077
  end
841
1078
 
842
1079
  def parseScripts(scriptlist)
843
1080
  @scripts = []
844
1081
 
845
- return nil if scriptlist.nil?
1082
+ return if scriptlist.nil?
846
1083
 
847
- @scripts = scriptlist.elements.collect('script') do |script|
1084
+ @scripts = scriptlist[:kids].collect_tags(:script) do |script|
848
1085
  Script.new(script)
849
1086
  end
850
1087
  end
851
1088
 
852
1089
  def tcpseq(seq)
853
- return nil if seq.nil?
1090
+ return if seq.nil?
854
1091
 
855
- @tcpsequence_index = seq.attributes['index']
856
- @tcpsequence_class = seq.attributes['class']
857
- @tcpsequence_values = seq.attributes['values']
858
- @tcpsequence_difficulty = seq.attributes['difficulty']
1092
+ @tcpsequence_index = seq[:attrs]['index'].to_i
1093
+ @tcpsequence_class = seq[:attrs]['class']
1094
+ @tcpsequence_values = seq[:attrs]['values']
1095
+ @tcpsequence_difficulty = seq[:attrs]['difficulty']
859
1096
  end
860
1097
 
861
1098
  def ipidseq(seq)
862
- return nil if seq.nil?
1099
+ return if seq.nil?
863
1100
 
864
- @ipidsequence_class = seq.attributes['class']
865
- @ipidsequence_values = seq.attributes['values']
1101
+ @ipidsequence_class = seq[:attrs]['class']
1102
+ @ipidsequence_values = seq[:attrs]['values']
866
1103
  end
867
1104
 
868
1105
  def tcptsseq(seq)
869
- return nil if seq.nil?
1106
+ return if seq.nil?
870
1107
 
871
- @tcptssequence_class = seq.attributes['class']
872
- @tcptssequence_values = seq.attributes['values']
1108
+ @tcptssequence_class = seq[:attrs]['class']
1109
+ @tcptssequence_values = seq[:attrs]['values']
873
1110
  end
874
1111
 
875
1112
  def uptime(time)
876
- return nil if time.nil?
1113
+ return if time.nil?
1114
+
1115
+ @uptime_seconds = time[:attrs]['seconds'].to_i
1116
+ @uptime_lastboot = time[:attrs]['lastboot']
1117
+ end
877
1118
 
878
- @uptime_seconds = time.attributes['seconds'].to_i
879
- @uptime_lastboot = time.attributes['lastboot']
1119
+ def initialize(hostinfo)
1120
+ parse(hostinfo)
880
1121
  end
881
1122
 
882
1123
  def parse(host)
883
- status = host.elements['status']
1124
+ status = host[:kids].find_tag(:status)
884
1125
 
885
- @status = status.attributes['state']
1126
+ @status = status[:attrs]['state']
886
1127
 
887
- @reason = status.attributes['reason']
1128
+ @reason = status[:attrs]['reason']
888
1129
 
889
- @os = OS.new(host.elements['os'])
1130
+ @os = OS.new(host[:kids].find_tag(:os))
890
1131
 
891
- host.each_element('address') do |elem|
1132
+ host[:kids].each_tag(:address) do |elem|
892
1133
  parseAddr(elem)
893
1134
  end
894
1135
 
895
- parseHostnames(host.elements['hostnames'])
1136
+ parseHostnames(host[:kids].find_tag(:hostnames))
896
1137
 
897
- smurf = host.elements['smurf']
898
- @smurf = smurf.attributes['responses'] if smurf
1138
+ smurf = host[:kids].find_tag(:smurf)
1139
+ @smurf = smurf[:attrs]['responses'] if smurf
899
1140
 
900
- ports = host.elements['ports']
1141
+ ports = host[:kids].find_tag(:ports)
901
1142
 
902
1143
  parsePorts(ports)
903
1144
 
904
1145
  parseExtraPorts(ports)
905
1146
 
906
- parseScripts(host.elements['hostscript'])
1147
+ parseScripts(host[:kids].find_tag(:hostscript))
907
1148
 
908
- if trace = host.elements['trace']
909
- @traceroute = Traceroute.new(trace)
910
- end
1149
+ trace = host[:kids].find_tag(:trace)
1150
+ @traceroute = Traceroute.new(trace) if trace
911
1151
 
912
- tcpseq(host.elements['tcpsequence'])
1152
+ tcpseq(host[:kids].find_tag(:tcpsequence))
913
1153
 
914
- ipidseq(host.elements['ipidsequence'])
1154
+ ipidseq(host[:kids].find_tag(:ipidsequence))
915
1155
 
916
- tcptsseq(host.elements['tcptssequence'])
1156
+ tcptsseq(host[:kids].find_tag(:tcptssequence))
917
1157
 
918
- uptime(host.elements['uptime'])
1158
+ uptime(host[:kids].find_tag(:uptime))
919
1159
 
920
- distance = host.elements['distance']
921
- @distance = distance.attributes['value'].to_i if distance
1160
+ distance = host[:kids].find_tag(:distance)
1161
+ @distance = distance[:attrs]['value'].to_i if distance
922
1162
 
923
- @times = Times.new(host.elements['times'])
1163
+ @times = Times.new(host[:kids].find_tag(:times))
924
1164
 
925
- stime = host.attributes['starttime']
1165
+ stime = host[:attrs]['starttime']
926
1166
  @starttime = stime.to_i if stime
927
1167
 
928
- etime = host.attributes['endtime']
1168
+ etime = host[:attrs]['endtime']
929
1169
  @endtime = etime.to_i if etime
930
1170
  end
1171
+
1172
+ # :startdoc:
931
1173
  end
932
1174
 
933
1175
  # This holds information on the time statistics for this host
@@ -941,17 +1183,21 @@ class Nmap::Parser::Host::Times
941
1183
 
942
1184
  private
943
1185
 
1186
+ # :stopdoc:
1187
+
944
1188
  def initialize(times)
945
1189
  parse(times)
946
1190
  end
947
1191
 
948
1192
  def parse(times)
949
- return nil if times.nil?
1193
+ return if times.nil?
950
1194
 
951
- @srtt = times.attributes['srtt'].to_i
952
- @rttvar = times.attributes['rttvar'].to_i
953
- @to = times.attributes['to'].to_i
1195
+ @srtt = times[:attrs]['srtt'].to_i
1196
+ @rttvar = times[:attrs]['rttvar'].to_i
1197
+ @to = times[:attrs]['to'].to_i
954
1198
  end
1199
+
1200
+ # :startdoc:
955
1201
  end
956
1202
 
957
1203
  # This holds the information about an NSE script run against a host or port
@@ -965,27 +1211,33 @@ class Nmap::Parser::Host::Script
965
1211
 
966
1212
  private
967
1213
 
1214
+ # :stopdoc:
1215
+
968
1216
  def initialize(script)
969
1217
  parse(script)
970
1218
  end
971
1219
 
972
1220
  def parse(script)
973
- return nil if script.nil?
1221
+ return if script.nil?
974
1222
 
975
- @id = script.attributes['id']
976
- @output = script.attributes['output']
1223
+ @id = script[:attrs]['id']
1224
+ @output = script[:attrs]['output']
977
1225
  end
1226
+
1227
+ # :startdoc:
978
1228
  end
979
1229
 
980
1230
  # This holds the information about an individual port or protocol
981
1231
  class Nmap::Parser::Host::Port
982
1232
  # Port number
983
1233
  attr_reader :num
1234
+ # Port protocol ("tcp", "udp", etc)
1235
+ attr_reader :proto
984
1236
  # Service object for this port
985
1237
  attr_reader :service
986
1238
  # Port state ("open", "closed", "filtered", etc)
987
1239
  attr_reader :state
988
- # Why the port is in the state
1240
+ # Reason for the port state
989
1241
  attr_reader :reason
990
1242
  # The host that responded, if different than the target
991
1243
  attr_reader :reason_ip
@@ -997,11 +1249,10 @@ class Nmap::Parser::Host::Port
997
1249
  @scripts.find { |script| script.id == name }
998
1250
  end
999
1251
 
1000
- # Returns an array of Script objects associated with this port, and
1001
- # passes them each to a block if one is given
1252
+ # Returns an array of Script objects associated with this port and
1253
+ # yields them each to a block if one is given
1002
1254
  def scripts
1003
1255
  @scripts.each { |script| yield script } if block_given?
1004
-
1005
1256
  @scripts
1006
1257
  end
1007
1258
 
@@ -1021,25 +1272,29 @@ class Nmap::Parser::Host::Port
1021
1272
 
1022
1273
  private
1023
1274
 
1275
+ # :stopdoc:
1276
+
1024
1277
  def initialize(portinfo)
1025
1278
  parse(portinfo)
1026
1279
  end
1027
1280
 
1028
1281
  def parse(port)
1029
- @num = port.attributes['portid'].to_i
1030
-
1031
- state = port.elements['state']
1032
- @state = state.attributes['state']
1033
- @reason = state.attributes['reason']
1034
- @reason_ttl = state.attributes['reason_ttl'].to_i
1035
- @reason_ip = state.attributes['reason_ip']
1282
+ @num = port[:attrs]['portid'].to_i
1283
+ @proto = port[:attrs]['protocol']
1284
+ state = port[:kids].find_tag(:state)
1285
+ @state = state[:attrs]['state']
1286
+ @reason = state[:attrs]['reason']
1287
+ @reason_ttl = state[:attrs]['reason_ttl'].to_i
1288
+ @reason_ip = state[:attrs]['reason_ip']
1036
1289
 
1037
1290
  @service = Service.new(port)
1038
1291
 
1039
- @scripts = port.elements.collect('script') do |script|
1292
+ @scripts = port[:kids].collect_tags(:script) do |script|
1040
1293
  Nmap::Parser::Host::Script.new(script)
1041
1294
  end
1042
1295
  end
1296
+
1297
+ # :startdoc:
1043
1298
  end
1044
1299
 
1045
1300
  # This holds the information about "extra ports": groups of ports which have
@@ -1052,13 +1307,12 @@ class Nmap::Parser::Host::ExtraPorts
1052
1307
 
1053
1308
  # Returns an array of arrays, each of which are in the form of:
1054
1309
  #
1055
- # [ <port count>, reason ]
1310
+ # [ port count, reason ]
1056
1311
  #
1057
- # for each set of reasons, and passes them each to a block if one is
1312
+ # for each set of reasons and yields them each to a block if one is
1058
1313
  # given
1059
1314
  def reasons
1060
1315
  @reasons.each { |reason| yield reason } if block_given?
1061
-
1062
1316
  @reasons
1063
1317
  end
1064
1318
 
@@ -1069,30 +1323,34 @@ class Nmap::Parser::Host::ExtraPorts
1069
1323
 
1070
1324
  private
1071
1325
 
1326
+ # :stopdoc:
1327
+
1072
1328
  def initialize(extraports)
1073
1329
  parse(extraports)
1074
1330
  end
1075
1331
 
1076
1332
  def parse(extraports)
1077
- @count = extraports.attributes['count'].to_i
1078
- @state = extraports.attributes['state']
1333
+ @count = extraports[:attrs]['count'].to_i
1334
+ @state = extraports[:attrs]['state']
1079
1335
 
1080
1336
  @reasons = []
1081
1337
 
1082
- extraports.each_element('extrareasons') do |extra|
1083
- ecount = extra.attributes['count'].to_i
1084
- ereason = extra.attributes['reason']
1338
+ extraports[:kids].each_tag(:extrareasons) do |extra|
1339
+ ecount = extra[:attrs]['count'].to_i
1340
+ ereason = extra[:attrs]['reason']
1085
1341
  @reasons << [ ecount, ereason ]
1086
1342
  end
1087
1343
  end
1344
+
1345
+ # :startdoc:
1088
1346
  end
1089
1347
 
1090
1348
  # This holds information on a traceroute, such as the port and protocol used
1091
1349
  # and an array of responsive hops
1092
1350
  class Nmap::Parser::Host::Traceroute
1093
- # The port used during traceroute
1351
+ # Port number used during traceroute
1094
1352
  attr_reader :port
1095
- # The protocol used during traceroute
1353
+ # Protocol used during traceroute
1096
1354
  attr_reader :proto
1097
1355
 
1098
1356
  # Returns the Hop object for the given TTL
@@ -1101,38 +1359,41 @@ class Nmap::Parser::Host::Traceroute
1101
1359
  end
1102
1360
 
1103
1361
  # Returns an array of Hop objects, which are each a responsive hop,
1104
- # and passes them each to a block if one if given.
1362
+ # and yields them each to a block if one if given.
1105
1363
  def hops
1106
1364
  @hops.each { |hop| yield hop } if block_given?
1107
-
1108
1365
  @hops
1109
1366
  end
1110
1367
 
1111
1368
  private
1112
1369
 
1370
+ # :stopdoc:
1371
+
1113
1372
  def initialize(trace)
1114
1373
  parse(trace)
1115
1374
  end
1116
1375
 
1117
1376
  def parse(trace)
1118
- @port = trace.attributes['port'].to_i
1119
- @proto = trace.attributes['proto']
1377
+ @port = trace[:attrs]['port'].to_i
1378
+ @proto = trace[:attrs]['proto']
1120
1379
 
1121
- @hops = trace.each_element('hop') do |hop|
1380
+ @hops = trace[:kids].collect_tags(:hop) do |hop|
1122
1381
  Hop.new(hop)
1123
1382
  end
1124
1383
  end
1384
+
1385
+ # :startdoc:
1125
1386
  end
1126
1387
 
1127
1388
  # This holds information on an individual traceroute hop
1128
1389
  class Nmap::Parser::Host::Traceroute::Hop
1129
1390
  # How many hops away the host is
1130
1391
  attr_reader :ttl
1131
- # The round-trip time of the host
1392
+ # Round-trip time of the host
1132
1393
  attr_reader :rtt
1133
- # The IP address of the host
1394
+ # IP address of the host
1134
1395
  attr_reader :addr
1135
- # The hostname of the host
1396
+ # Hostname of the host
1136
1397
  attr_reader :hostname
1137
1398
 
1138
1399
  alias host hostname
@@ -1145,21 +1406,25 @@ class Nmap::Parser::Host::Traceroute::Hop
1145
1406
 
1146
1407
  private
1147
1408
 
1409
+ # :stopdoc:
1410
+
1148
1411
  def initialize(hop)
1149
1412
  parse(hop)
1150
1413
  end
1151
1414
 
1152
1415
  def parse(hop)
1153
- @ttl = hop.attributes['ttl'].to_i
1154
- @rtt = hop.attributes['rtt'].to_f
1155
- @addr = hop.attributes['ipaddr']
1156
- @hostname = hop.attributes['host']
1416
+ @ttl = hop[:attrs]['ttl'].to_i
1417
+ @rtt = hop[:attrs]['rtt'].to_f
1418
+ @addr = hop[:attrs]['ipaddr']
1419
+ @hostname = hop[:attrs]['host']
1157
1420
  end
1421
+
1422
+ # :startdoc:
1158
1423
  end
1159
1424
 
1160
1425
  # This holds the service information for a port
1161
1426
  class Nmap::Parser::Host::Port::Service
1162
- # The name of the service
1427
+ # Name of the service
1163
1428
  attr_reader :name
1164
1429
  # Vendor name
1165
1430
  attr_reader :product
@@ -1181,49 +1446,51 @@ class Nmap::Parser::Host::Port::Service
1181
1446
  attr_reader :proto
1182
1447
  # Extra misc. information about the service
1183
1448
  attr_reader :extra
1184
- # The type of device the service is running on
1449
+ # Type of device the service is running on
1185
1450
  attr_reader :devicetype
1186
- # The OS the service is running on
1451
+ # OS the service is running on
1187
1452
  attr_reader :ostype
1188
- # The service fingerprint
1453
+ # Service fingerprint
1189
1454
  attr_reader :fingerprint
1190
1455
 
1191
1456
  alias extrainfo extra
1192
1457
 
1193
1458
  private
1194
1459
 
1460
+ # :stopdoc:
1461
+
1195
1462
  def initialize(port)
1196
1463
  parse(port)
1197
1464
  end
1198
1465
 
1199
1466
  def parse(port)
1200
- return nil if port.nil?
1201
-
1202
- service = port.elements['service']
1203
-
1204
- return nil if service.nil?
1205
-
1206
- @name = service.attributes['name']
1207
- @product = service.attributes['product']
1208
- @version = service.attributes['version']
1209
- @method = service.attributes['method']
1210
- owner = port.elements['owner']
1211
- @owner = owner.attributes['name'] if owner
1212
- @tunnel = service.attributes['tunnel']
1213
- rpcnum = service.attributes['rpcnum']
1467
+ service = port[:kids].find_tag(:service)
1468
+
1469
+ return if service.nil?
1470
+
1471
+ @name = service[:attrs]['name']
1472
+ @product = service[:attrs]['product']
1473
+ @version = service[:attrs]['version']
1474
+ @method = service[:attrs]['method']
1475
+ owner = port[:kids].find_tag(:owner)
1476
+ @owner = owner[:attrs]['name'] if owner
1477
+ @tunnel = service[:attrs]['tunnel']
1478
+ rpcnum = service[:attrs]['rpcnum']
1214
1479
  @rpcnum = rpcnum.to_i if rpcnum
1215
- lowver = service.attributes['lowver']
1480
+ lowver = service[:attrs]['lowver']
1216
1481
  @lowver = lowver.to_i if lowver
1217
- highver = service.attributes['highver']
1482
+ highver = service[:attrs]['highver']
1218
1483
  @highver = highver.to_i if highver
1219
- conf = service.attributes['conf']
1484
+ conf = service[:attrs]['conf']
1220
1485
  @confidence = conf.to_i if conf
1221
- @proto = service.attributes['proto']
1222
- @extra = service.attributes['extrainfo']
1223
- @devicetype = service.attributes['devicetype']
1224
- @ostype = service.attributes['ostype']
1225
- @fingerprint = service.attributes['servicefp']
1486
+ @proto = service[:attrs]['proto']
1487
+ @extra = service[:attrs]['extrainfo']
1488
+ @devicetype = service[:attrs]['devicetype']
1489
+ @ostype = service[:attrs]['ostype']
1490
+ @fingerprint = service[:attrs]['servicefp']
1226
1491
  end
1492
+
1493
+ # :startdoc:
1227
1494
  end
1228
1495
 
1229
1496
  # This holds the OS information from OS Detection
@@ -1231,19 +1498,17 @@ class Nmap::Parser::Host::OS
1231
1498
  # OS fingerprint
1232
1499
  attr_reader :fingerprint
1233
1500
 
1234
- # Returns an array of OSClass objects, and passes them each to a
1235
- # block if one is given
1501
+ # Returns an array of OSClass objects and yields them each to a block
1502
+ # if one is given
1236
1503
  def osclasses
1237
1504
  @osclasses.each { |osclass| yield osclass } if block_given?
1238
-
1239
1505
  @osclasses
1240
1506
  end
1241
1507
 
1242
- # Returns an array of OSMatch objects, and passes them each to a
1243
- # block if one is given
1508
+ # Returns an array of OSMatch objects and yields them each to a block
1509
+ # if one is given
1244
1510
  def osmatches
1245
1511
  @osmatches.each { |osmatch| yield osmatch } if block_given?
1246
-
1247
1512
  @osmatches
1248
1513
  end
1249
1514
 
@@ -1255,41 +1520,32 @@ class Nmap::Parser::Host::OS
1255
1520
  # Returns OS class accuracy of the first OS class record, or Nth record
1256
1521
  # as specified by +index+
1257
1522
  def class_accuracy(index = 0)
1258
- return nil if @osclasses.empty?
1259
-
1260
- @osclasses[index.to_i].accuracy
1523
+ (@osclasses[index.to_i] or return).accuracy
1261
1524
  end
1262
1525
 
1526
+ # :method: osfamily(index=0)
1263
1527
  # Returns OS family information of first OS class record, or Nth record
1264
1528
  # as specified by +index+
1265
- def osfamily(index = 0)
1266
- return nil if @osclasses.empty?
1267
-
1268
- @osclasses[index.to_i].osfamily
1269
- end
1270
1529
 
1530
+ # :method: osgen(index=0)
1271
1531
  # Returns OS generation information of first OS class record, or Nth
1272
1532
  # record as specified by +index+
1273
- def osgen(index = 0)
1274
- return nil if @osclasses.empty?
1275
-
1276
- @osclasses[index.to_i].osgen
1277
- end
1278
1533
 
1534
+ # :method: ostype(index=0)
1279
1535
  # Returns OS type information of the first OS class record, or Nth
1280
1536
  # record as specified by +index+
1281
- def ostype(index = 0)
1282
- return nil if @osclasses.empty?
1283
-
1284
- @osclasses[index.to_i].ostype
1285
- end
1286
1537
 
1538
+ # :method: osvendor(index=0)
1287
1539
  # Returns OS vendor information of the first OS class record, or Nth
1288
1540
  # record as specified by +index+
1289
- def osvendor(index = 0)
1290
- return nil if @osclasses.empty?
1291
1541
 
1292
- @osclasses[index.to_i].osvendor
1542
+ #
1543
+ ["family", "gen", "type", "vendor"].each do |name|
1544
+ self.class_eval("
1545
+ def os#{name}(index = 0)
1546
+ (@osclasses[index.to_i] or return).os#{name}
1547
+ end
1548
+ ")
1293
1549
  end
1294
1550
 
1295
1551
  # Returns the number of OS match records
@@ -1300,66 +1556,48 @@ class Nmap::Parser::Host::OS
1300
1556
  # Returns name of first OS match record, or Nth record as specified by
1301
1557
  # +index+
1302
1558
  def name(index = 0)
1303
- return nil if @osmatches.empty?
1304
-
1305
- @osmatches[index.to_i].name
1559
+ (@osmatches[index.to_i] or return).name
1306
1560
  end
1307
1561
 
1308
1562
  # Returns OS name accuracy of the first OS match record, or Nth record
1309
1563
  # as specified by +index+
1310
1564
  def name_accuracy(index = 0)
1311
- return nil if @osmatches.empty?
1312
-
1313
- @osmatches[index.to_i].accuracy
1565
+ (@osmatches[index.to_i] or return).accuracy
1314
1566
  end
1315
1567
 
1316
- # Returns an array of names from all OS records, and passes them each
1317
- # to a block if one is given
1318
- def all_names() # :yields: name
1319
- names = []
1320
-
1321
- @osmatches.each do |match|
1322
- names << match.name
1568
+ # Returns an array of names from all OS records and yields them each to
1569
+ # a block if one is given
1570
+ def names() # :yields: name
1571
+ @osmatches.map do |match|
1323
1572
  yield match.name if block_given?
1573
+ match.name
1324
1574
  end
1325
-
1326
- names
1327
1575
  end
1328
1576
 
1329
- alias names all_names
1577
+ alias all_names names
1330
1578
 
1331
1579
  # Returns the closed TCP port used for this OS Detection run
1332
1580
  def tcpport_closed
1333
- return nil if @portsused.nil?
1334
-
1335
- @portsused.each do |port|
1336
- if port.proto == "tcp" and port.state == "closed"
1337
- return port.num
1338
- end
1339
- end
1340
-
1341
- nil
1581
+ getportnum("tcp", "closed")
1342
1582
  end
1343
1583
 
1344
1584
  # Returns the open TCP port used for this OS Detection run
1345
1585
  def tcpport_open
1346
- return nil if @portsused.nil?
1347
-
1348
- @portsused.each do |port|
1349
- if port.proto == "tcp" and port.state == "open"
1350
- return port.num
1351
- end
1352
- end
1353
-
1354
- nil
1586
+ getportnum("tcp", "open")
1355
1587
  end
1356
1588
 
1357
1589
  # Returns the closed UDP port used for this OS Detection run
1358
1590
  def udpport_closed
1359
- return nil if @portsused.nil?
1591
+ getportnum("udp", "closed")
1592
+ end
1593
+
1594
+ private
1360
1595
 
1596
+ # :stopdoc:
1597
+
1598
+ def getportnum(proto, state)
1361
1599
  @portsused.each do |port|
1362
- if port.proto == "udp" and port.state == "closed"
1600
+ if port.proto == proto and port.state == state
1363
1601
  return port.num
1364
1602
  end
1365
1603
  end
@@ -1367,8 +1605,6 @@ class Nmap::Parser::Host::OS
1367
1605
  nil
1368
1606
  end
1369
1607
 
1370
- private
1371
-
1372
1608
  def initialize(os)
1373
1609
  parse(os)
1374
1610
  end
@@ -1378,27 +1614,25 @@ class Nmap::Parser::Host::OS
1378
1614
  @osclasses = []
1379
1615
  @osmatches = []
1380
1616
 
1381
- return nil if os.nil?
1617
+ return if os.nil?
1382
1618
 
1383
- @portsused = os.elements.collect('portused') do |port|
1619
+ @portsused = os[:kids].collect_tags(:portused) do |port|
1384
1620
  PortUsed.new(port)
1385
1621
  end
1386
1622
 
1387
- @osclasses = os.elements.collect('osclass') do |osclass|
1623
+ @osclasses = os[:kids].collect_tags(:osclass) { |osclass|
1388
1624
  OSClass.new(osclass)
1389
- end
1390
-
1391
- @osclasses.sort!.reverse!
1625
+ }.sort.reverse
1392
1626
 
1393
- @osmatches = os.elements.collect('osmatch') do |match|
1627
+ @osmatches = os[:kids].collect_tags(:osmatch) { |match|
1394
1628
  OSMatch.new(match)
1395
- end
1629
+ }.sort.reverse
1396
1630
 
1397
- @osmatches.sort!.reverse!
1398
-
1399
- fp = os.elements['osfingerprint']
1400
- @fingerprint = fp.attributes['fingerprint'] if fp
1631
+ fp = os[:kids].find_tag(:osfingerprint)
1632
+ @fingerprint = fp[:attrs]['fingerprint'] if fp
1401
1633
  end
1634
+
1635
+ # :startdoc:
1402
1636
  end
1403
1637
 
1404
1638
  class Nmap::Parser::Host::OS::PortUsed # :nodoc: all
@@ -1411,9 +1645,9 @@ class Nmap::Parser::Host::OS::PortUsed # :nodoc: all
1411
1645
  end
1412
1646
 
1413
1647
  def parse(ports)
1414
- @state = ports.attributes['state']
1415
- @proto = ports.attributes['proto']
1416
- @num = ports.attributes['portid'].to_i
1648
+ @state = ports[:attrs]['state']
1649
+ @proto = ports[:attrs]['proto']
1650
+ @num = ports[:attrs]['portid'].to_i
1417
1651
  end
1418
1652
  end
1419
1653
 
@@ -1437,17 +1671,21 @@ class Nmap::Parser::Host::OS::OSClass
1437
1671
 
1438
1672
  private
1439
1673
 
1674
+ # :stopdoc:
1675
+
1440
1676
  def initialize(osclass)
1441
1677
  parse(osclass)
1442
1678
  end
1443
1679
 
1444
1680
  def parse(osclass)
1445
- @ostype = osclass.attributes['type']
1446
- @osvendor = osclass.attributes['vendor']
1447
- @osfamily = osclass.attributes['osfamily']
1448
- @osgen = osclass.attributes['osgen']
1449
- @accuracy = osclass.attributes['accuracy'].to_i
1681
+ @ostype = osclass[:attrs]['type']
1682
+ @osvendor = osclass[:attrs]['vendor']
1683
+ @osfamily = osclass[:attrs]['osfamily']
1684
+ @osgen = osclass[:attrs]['osgen']
1685
+ @accuracy = osclass[:attrs]['accuracy'].to_i
1450
1686
  end
1687
+
1688
+ # :startdoc:
1451
1689
  end
1452
1690
 
1453
1691
  # Holds information for an individual OS match record
@@ -1464,13 +1702,142 @@ class Nmap::Parser::Host::OS::OSMatch
1464
1702
 
1465
1703
  private
1466
1704
 
1705
+ # :stopdoc:
1706
+
1467
1707
  def initialize(os)
1468
1708
  parse(os)
1469
1709
  end
1470
1710
 
1471
1711
  def parse(os)
1472
- @name = os.attributes['name']
1473
- @accuracy = os.attributes['accuracy'].to_i
1712
+ @name = os[:attrs]['name']
1713
+ @accuracy = os[:attrs]['accuracy'].to_i
1714
+ end
1715
+
1716
+ # :startdoc:
1717
+ end
1718
+
1719
+ # :stopdoc:
1720
+
1721
+ #
1722
+ # Now for the actual XML parsing stuff for Nmap::Parser ...
1723
+ #
1724
+
1725
+ class Nmap::XmlParsing::TagGroup < Array
1726
+ def each_tag(name)
1727
+ self.each { |tag| yield tag if match(tag, name) }
1728
+ end
1729
+
1730
+ def collect_tags(name)
1731
+ self.map { |tag| yield tag if match(tag, name) }.compact
1732
+ end
1733
+
1734
+ def find_tag(name)
1735
+ self.find { |tag| match(tag, name) }
1736
+ end
1737
+
1738
+ private
1739
+
1740
+ def match(tag, name)
1741
+ tag[:name] == name.to_sym
1742
+ end
1743
+ end
1744
+
1745
+ # Super simple!
1746
+ class Nmap::XmlParsing::Tag < Hash
1747
+ private
1748
+
1749
+ def initialize(name, attrs)
1750
+ self[:name] = name
1751
+ self[:attrs] = attrs
1752
+ self[:kids] = Nmap::XmlParsing::TagGroup.new
1753
+ end
1754
+ end
1755
+
1756
+ class Nmap::XmlParsing::MyParser
1757
+ include Nmap
1758
+
1759
+ attr_reader :session
1760
+ attr_reader :hosts
1761
+
1762
+ def tag_start(tag, attrs)
1763
+ name = tag.to_sym
1764
+
1765
+ return if ignored(name)
1766
+
1767
+ kv = XmlParsing::Tag.new(name, attrs)
1768
+
1769
+ if @data.empty?
1770
+ @data[name] = kv
1771
+ else
1772
+ @loc.last[:kids].push(kv)
1773
+ end
1774
+
1775
+ @loc.push(kv)
1776
+ end
1777
+
1778
+ def tag_end(tag)
1779
+ name = tag.to_sym
1780
+
1781
+ return if ignored(name)
1782
+
1783
+ last = @loc.pop
1784
+
1785
+ case name
1786
+ when :host
1787
+ @hosts << Parser::Host.new(last)
1788
+ @loc[@loc.size - 2][:kids].pop
1789
+
1790
+ if @callback
1791
+ Thread.new(@hosts.last) do |host|
1792
+ Thread.current[:cb] = true
1793
+ @callback.call(host)
1794
+ end
1795
+ end
1796
+ when :nmaprun
1797
+ @session = Parser::Session.new(last)
1798
+
1799
+ if @callback and Thread.list.size > 1
1800
+ Thread.list.reject { |t|
1801
+ not t[:cb]
1802
+ }.each { |t|
1803
+ t.join
1804
+ }
1805
+ end
1806
+ end
1807
+ end
1808
+
1809
+ def method_missing(sym, *args)
1810
+ end
1811
+
1812
+ def completed?
1813
+ # @session becomes non-nil when <nmaprun> closes, which means
1814
+ # we're done
1815
+ @session ? true : false
1816
+ end
1817
+
1818
+ private
1819
+
1820
+ # We don't want to store anything we don't care about!
1821
+ IGNORED = [
1822
+ :taskbegin,
1823
+ :taskprogress,
1824
+ :taskend,
1825
+ # Zenmap uses this for screen output
1826
+ :output
1827
+ ]
1828
+
1829
+ def ignored(name)
1830
+ IGNORED.find { |ent| ent == name }
1831
+ end
1832
+
1833
+ def initialize(callback)
1834
+ @data = {}
1835
+ @loc = []
1836
+
1837
+ @session = nil
1838
+ @hosts = []
1839
+
1840
+ @callback = callback
1474
1841
  end
1475
1842
  end
1476
1843