nmap-parser 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/BUGS +3 -2
- data/ChangeLog.rdoc +249 -0
- data/LICENSE +1 -1
- data/README +23 -19
- data/TODO +4 -0
- data/examples/listopentcp.rb +3 -3
- data/examples/services.rb +17 -18
- data/lib/nmap/parser.rb +948 -581
- metadata +23 -15
data/BUGS
CHANGED
data/ChangeLog.rdoc
ADDED
@@ -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
data/README
CHANGED
@@ -1,41 +1,45 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
-------------------------------------------------------------------------------
|
2
|
+
-- Ruby Nmap::Parser http://rubynmap.sourceforge.net --
|
3
|
+
-- Kris Katterjohn katterjohn@gmail.com --
|
4
|
+
-------------------------------------------------------------------------------
|
5
5
|
|
6
|
-
$Id: README
|
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
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
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
data/examples/listopentcp.rb
CHANGED
@@ -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.
|
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}
|
15
|
+
puts "Got #{port.num}/#{port.proto}"
|
16
16
|
end
|
17
17
|
|
18
18
|
puts
|
data/examples/services.rb
CHANGED
@@ -1,29 +1,28 @@
|
|
1
|
-
#!/usr/bin/ruby
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
#
|
3
|
-
# A
|
4
|
-
#
|
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
|
6
|
+
# Kris Katterjohn 02/16/2009
|
7
7
|
|
8
8
|
require 'nmap/parser'
|
9
9
|
|
10
|
-
|
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
|
-
|
26
|
-
|
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
|
|
data/lib/nmap/parser.rb
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
-
#
|
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
|
9
|
+
# = Author
|
10
|
+
#
|
11
|
+
# Kris Katterjohn (katterjohn@gmail.com)
|
6
12
|
#
|
7
|
-
#
|
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
|
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
|
-
#
|
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
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
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
|
57
|
-
Fyodor
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
==
|
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.
|
109
|
-
parser
|
151
|
+
parser = Nmap::Parser.new
|
152
|
+
parser.parsestring(xml)
|
110
153
|
|
111
|
-
|
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
|
-
|
164
|
+
=== Reading and Parsing From an Object
|
118
165
|
|
119
|
-
This method can read from any object
|
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
|
-
|
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
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
-
|
150
|
-
|
218
|
+
[:tcp, :udp].each do |type|
|
219
|
+
host.getports(type, "open") do |port|
|
220
|
+
srv = port.service
|
151
221
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
#
|
255
|
+
# Raw XML output from the scan
|
164
256
|
attr_reader :rawxml
|
165
|
-
# Session object for
|
257
|
+
# Session object for the scan
|
166
258
|
attr_reader :session
|
167
259
|
|
168
|
-
#
|
169
|
-
|
170
|
-
#
|
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
|
173
|
-
#
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
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
|
-
|
182
|
-
|
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
|
-
|
316
|
+
parsestring(obj.read)
|
186
317
|
end
|
187
318
|
|
188
319
|
# Read and parse the contents of the Nmap XML file +filename+
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
203
|
-
#
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
-
#
|
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
|
340
|
+
# parser.parsescan("sudo ./nmap", arguments, targets)
|
216
341
|
#
|
217
|
-
# and still make it easy for me to inject the options for XML
|
218
|
-
#
|
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
|
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
|
-
#
|
223
|
-
# you
|
224
|
-
# to a
|
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
|
227
|
-
#
|
228
|
-
#
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
235
|
-
command = nmap
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
#
|
288
|
-
#
|
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
|
301
|
-
#
|
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
|
-
|
428
|
+
hosts(status).map do |host|
|
429
|
+
yield host.addr if block_given?
|
430
|
+
host.addr
|
431
|
+
end
|
432
|
+
end
|
306
433
|
|
307
|
-
|
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
|
-
|
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
|
-
|
313
|
-
|
314
|
-
|
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
|
320
|
-
if
|
321
|
-
|
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
|
-
|
493
|
+
raise TypeError, "Bad callback type: must be a Proc, Method or UnboundMethod"
|
494
|
+
end
|
325
495
|
|
326
|
-
|
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
|
-
|
333
|
-
|
334
|
-
rescue
|
335
|
-
raise "Error parsing XML: #{$!}"
|
336
|
-
end
|
511
|
+
# :)
|
512
|
+
lthr = Thread.new { @blinken.call } if @blinken
|
337
513
|
|
338
|
-
|
514
|
+
parser = Nmap::XmlParsing::MyParser.new(@callback)
|
515
|
+
REXML::Document.parse_stream(xml, parser)
|
339
516
|
|
340
|
-
|
517
|
+
lthr.kill if @blinken
|
341
518
|
|
342
|
-
|
343
|
-
|
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
|
-
#
|
556
|
+
# Command run to initiate the scan
|
352
557
|
attr_reader :scan_args
|
353
|
-
#
|
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 (
|
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
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
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
|
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
|
-
|
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
|
642
|
+
@scan_args = root[:attrs]['args']
|
643
|
+
|
644
|
+
@nmap_version = root[:attrs]['version']
|
429
645
|
|
430
|
-
@
|
646
|
+
@xml_version = root[:attrs]['xmloutputversion'].to_f
|
431
647
|
|
432
|
-
@
|
648
|
+
@start_str = root[:attrs]['startstr']
|
649
|
+
@start_time = root[:attrs]['start'].to_i
|
433
650
|
|
434
|
-
|
435
|
-
|
651
|
+
runstats = root[:kids].find_tag(:runstats)
|
652
|
+
finished = runstats[:kids].find_tag(:finished)
|
436
653
|
|
437
|
-
@stop_str =
|
438
|
-
@stop_time =
|
654
|
+
@stop_str = finished[:attrs]['timestr']
|
655
|
+
@stop_time = finished[:attrs]['time'].to_i
|
439
656
|
|
440
|
-
|
657
|
+
elapsed = finished[:attrs]['elapsed']
|
658
|
+
@scan_time = elapsed ? elapsed.to_f : (@stop_time - @start_time).to_f
|
441
659
|
|
442
|
-
@
|
443
|
-
@
|
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.
|
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
|
462
|
-
@scanflags = info
|
463
|
-
@proto = info
|
464
|
-
@numservices = info
|
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
|
-
#
|
702
|
+
# Status of the host, typically "up" or "down"
|
475
703
|
attr_reader :status
|
476
|
-
#
|
704
|
+
# Reason for the status
|
477
705
|
attr_reader :reason
|
478
|
-
#
|
706
|
+
# IPv4 address
|
479
707
|
attr_reader :ip4_addr
|
480
|
-
#
|
708
|
+
# IPv6 address
|
481
709
|
attr_reader :ip6_addr
|
482
|
-
#
|
710
|
+
# MAC address
|
483
711
|
attr_reader :mac_addr
|
484
|
-
#
|
712
|
+
# MAC vendor
|
485
713
|
attr_reader :mac_vendor
|
486
714
|
# OS object holding Operating System information
|
487
715
|
attr_reader :os
|
488
|
-
#
|
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
|
-
#
|
519
|
-
def
|
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
|
752
|
+
alias all_hostnames hostnames
|
526
753
|
|
527
|
-
# Returns the first hostname
|
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
|
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
|
541
|
-
# a block if one is given
|
542
|
-
def
|
543
|
-
|
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
|
549
|
-
# each to a block if one is
|
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
|
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
|
555
|
-
|
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
|
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
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
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
|
-
#
|
592
|
-
#
|
593
|
-
|
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
|
-
#
|
608
|
-
|
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
|
-
#
|
615
|
-
|
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
|
-
#
|
622
|
-
#
|
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
|
-
#
|
630
|
-
#
|
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
|
-
#
|
673
|
-
#
|
674
|
-
|
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
|
-
#
|
689
|
-
|
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
|
-
#
|
696
|
-
|
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
|
-
#
|
703
|
-
#
|
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
|
-
#
|
711
|
-
#
|
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
|
-
|
913
|
+
# :method: sctp_reason(portnum)
|
914
|
+
# Returns the state reason of SCTP port +portnum+
|
724
915
|
|
725
|
-
|
726
|
-
|
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
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
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
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
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
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
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
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
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
|
765
|
-
#
|
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
|
-
|
784
|
-
parse(hostinfo)
|
785
|
-
end
|
1024
|
+
# :stopdoc:
|
786
1025
|
|
787
1026
|
def parseAddr(elem)
|
788
|
-
case elem
|
1027
|
+
case elem[:attrs]['addrtype']
|
789
1028
|
when "mac"
|
790
|
-
@mac_addr = elem
|
791
|
-
@mac_vendor = elem
|
1029
|
+
@mac_addr = elem[:attrs]['addr']
|
1030
|
+
@mac_vendor = elem[:attrs]['vendor']
|
792
1031
|
when "ipv4"
|
793
|
-
@ip4_addr = elem
|
1032
|
+
@ip4_addr = elem[:attrs]['addr']
|
794
1033
|
when "ipv6"
|
795
|
-
@ip6_addr = elem
|
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
|
1041
|
+
return if elem.nil?
|
803
1042
|
|
804
|
-
@hostnames = elem.
|
805
|
-
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
|
-
@
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
@
|
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
|
1072
|
+
return if ports.nil?
|
834
1073
|
|
835
|
-
@extraports = ports.
|
1074
|
+
@extraports = ports[:kids].collect_tags(:extraports) { |e|
|
836
1075
|
ExtraPorts.new(e)
|
837
|
-
|
838
|
-
|
839
|
-
@extraports.sort!
|
1076
|
+
}.sort
|
840
1077
|
end
|
841
1078
|
|
842
1079
|
def parseScripts(scriptlist)
|
843
1080
|
@scripts = []
|
844
1081
|
|
845
|
-
return
|
1082
|
+
return if scriptlist.nil?
|
846
1083
|
|
847
|
-
@scripts = scriptlist.
|
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
|
1090
|
+
return if seq.nil?
|
854
1091
|
|
855
|
-
@tcpsequence_index = seq
|
856
|
-
@tcpsequence_class = seq
|
857
|
-
@tcpsequence_values = seq
|
858
|
-
@tcpsequence_difficulty = seq
|
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
|
1099
|
+
return if seq.nil?
|
863
1100
|
|
864
|
-
@ipidsequence_class = seq
|
865
|
-
@ipidsequence_values = seq
|
1101
|
+
@ipidsequence_class = seq[:attrs]['class']
|
1102
|
+
@ipidsequence_values = seq[:attrs]['values']
|
866
1103
|
end
|
867
1104
|
|
868
1105
|
def tcptsseq(seq)
|
869
|
-
return
|
1106
|
+
return if seq.nil?
|
870
1107
|
|
871
|
-
@tcptssequence_class = seq
|
872
|
-
@tcptssequence_values = seq
|
1108
|
+
@tcptssequence_class = seq[:attrs]['class']
|
1109
|
+
@tcptssequence_values = seq[:attrs]['values']
|
873
1110
|
end
|
874
1111
|
|
875
1112
|
def uptime(time)
|
876
|
-
return
|
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
|
-
|
879
|
-
|
1119
|
+
def initialize(hostinfo)
|
1120
|
+
parse(hostinfo)
|
880
1121
|
end
|
881
1122
|
|
882
1123
|
def parse(host)
|
883
|
-
status = host.
|
1124
|
+
status = host[:kids].find_tag(:status)
|
884
1125
|
|
885
|
-
@status = status
|
1126
|
+
@status = status[:attrs]['state']
|
886
1127
|
|
887
|
-
@reason = status
|
1128
|
+
@reason = status[:attrs]['reason']
|
888
1129
|
|
889
|
-
@os = OS.new(host.
|
1130
|
+
@os = OS.new(host[:kids].find_tag(:os))
|
890
1131
|
|
891
|
-
host.
|
1132
|
+
host[:kids].each_tag(:address) do |elem|
|
892
1133
|
parseAddr(elem)
|
893
1134
|
end
|
894
1135
|
|
895
|
-
parseHostnames(host.
|
1136
|
+
parseHostnames(host[:kids].find_tag(:hostnames))
|
896
1137
|
|
897
|
-
smurf = host.
|
898
|
-
@smurf = smurf
|
1138
|
+
smurf = host[:kids].find_tag(:smurf)
|
1139
|
+
@smurf = smurf[:attrs]['responses'] if smurf
|
899
1140
|
|
900
|
-
ports = host.
|
1141
|
+
ports = host[:kids].find_tag(:ports)
|
901
1142
|
|
902
1143
|
parsePorts(ports)
|
903
1144
|
|
904
1145
|
parseExtraPorts(ports)
|
905
1146
|
|
906
|
-
parseScripts(host.
|
1147
|
+
parseScripts(host[:kids].find_tag(:hostscript))
|
907
1148
|
|
908
|
-
|
909
|
-
|
910
|
-
end
|
1149
|
+
trace = host[:kids].find_tag(:trace)
|
1150
|
+
@traceroute = Traceroute.new(trace) if trace
|
911
1151
|
|
912
|
-
tcpseq(host.
|
1152
|
+
tcpseq(host[:kids].find_tag(:tcpsequence))
|
913
1153
|
|
914
|
-
ipidseq(host.
|
1154
|
+
ipidseq(host[:kids].find_tag(:ipidsequence))
|
915
1155
|
|
916
|
-
tcptsseq(host.
|
1156
|
+
tcptsseq(host[:kids].find_tag(:tcptssequence))
|
917
1157
|
|
918
|
-
uptime(host.
|
1158
|
+
uptime(host[:kids].find_tag(:uptime))
|
919
1159
|
|
920
|
-
distance = host.
|
921
|
-
@distance = distance
|
1160
|
+
distance = host[:kids].find_tag(:distance)
|
1161
|
+
@distance = distance[:attrs]['value'].to_i if distance
|
922
1162
|
|
923
|
-
@times = Times.new(host.
|
1163
|
+
@times = Times.new(host[:kids].find_tag(:times))
|
924
1164
|
|
925
|
-
stime = host
|
1165
|
+
stime = host[:attrs]['starttime']
|
926
1166
|
@starttime = stime.to_i if stime
|
927
1167
|
|
928
|
-
etime = host
|
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
|
1193
|
+
return if times.nil?
|
950
1194
|
|
951
|
-
@srtt = times
|
952
|
-
@rttvar = times
|
953
|
-
@to = times
|
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
|
1221
|
+
return if script.nil?
|
974
1222
|
|
975
|
-
@id = script
|
976
|
-
@output = script
|
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
|
-
#
|
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
|
1001
|
-
#
|
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
|
1030
|
-
|
1031
|
-
state = port.
|
1032
|
-
@state = state
|
1033
|
-
@reason = state
|
1034
|
-
@reason_ttl = state
|
1035
|
-
@reason_ip = state
|
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.
|
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
|
-
# [
|
1310
|
+
# [ port count, reason ]
|
1056
1311
|
#
|
1057
|
-
# for each set of reasons
|
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
|
1078
|
-
@state = extraports
|
1333
|
+
@count = extraports[:attrs]['count'].to_i
|
1334
|
+
@state = extraports[:attrs]['state']
|
1079
1335
|
|
1080
1336
|
@reasons = []
|
1081
1337
|
|
1082
|
-
extraports.
|
1083
|
-
ecount = extra
|
1084
|
-
ereason = extra
|
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
|
-
#
|
1351
|
+
# Port number used during traceroute
|
1094
1352
|
attr_reader :port
|
1095
|
-
#
|
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
|
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
|
1119
|
-
@proto = trace
|
1377
|
+
@port = trace[:attrs]['port'].to_i
|
1378
|
+
@proto = trace[:attrs]['proto']
|
1120
1379
|
|
1121
|
-
@hops = trace.
|
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
|
-
#
|
1392
|
+
# Round-trip time of the host
|
1132
1393
|
attr_reader :rtt
|
1133
|
-
#
|
1394
|
+
# IP address of the host
|
1134
1395
|
attr_reader :addr
|
1135
|
-
#
|
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
|
1154
|
-
@rtt = hop
|
1155
|
-
@addr = hop
|
1156
|
-
@hostname = hop
|
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
|
-
#
|
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
|
-
#
|
1449
|
+
# Type of device the service is running on
|
1185
1450
|
attr_reader :devicetype
|
1186
|
-
#
|
1451
|
+
# OS the service is running on
|
1187
1452
|
attr_reader :ostype
|
1188
|
-
#
|
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
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
@
|
1207
|
-
@
|
1208
|
-
|
1209
|
-
@
|
1210
|
-
|
1211
|
-
|
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
|
1480
|
+
lowver = service[:attrs]['lowver']
|
1216
1481
|
@lowver = lowver.to_i if lowver
|
1217
|
-
highver = service
|
1482
|
+
highver = service[:attrs]['highver']
|
1218
1483
|
@highver = highver.to_i if highver
|
1219
|
-
conf = service
|
1484
|
+
conf = service[:attrs]['conf']
|
1220
1485
|
@confidence = conf.to_i if conf
|
1221
|
-
@proto = service
|
1222
|
-
@extra = service
|
1223
|
-
@devicetype = service
|
1224
|
-
@ostype = service
|
1225
|
-
@fingerprint = service
|
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
|
1235
|
-
#
|
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
|
1243
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
1317
|
-
#
|
1318
|
-
def
|
1319
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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 ==
|
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
|
1617
|
+
return if os.nil?
|
1382
1618
|
|
1383
|
-
@portsused = os.
|
1619
|
+
@portsused = os[:kids].collect_tags(:portused) do |port|
|
1384
1620
|
PortUsed.new(port)
|
1385
1621
|
end
|
1386
1622
|
|
1387
|
-
@osclasses = os.
|
1623
|
+
@osclasses = os[:kids].collect_tags(:osclass) { |osclass|
|
1388
1624
|
OSClass.new(osclass)
|
1389
|
-
|
1390
|
-
|
1391
|
-
@osclasses.sort!.reverse!
|
1625
|
+
}.sort.reverse
|
1392
1626
|
|
1393
|
-
@osmatches = os.
|
1627
|
+
@osmatches = os[:kids].collect_tags(:osmatch) { |match|
|
1394
1628
|
OSMatch.new(match)
|
1395
|
-
|
1629
|
+
}.sort.reverse
|
1396
1630
|
|
1397
|
-
|
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
|
1415
|
-
@proto = ports
|
1416
|
-
@num = ports
|
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
|
1446
|
-
@osvendor = osclass
|
1447
|
-
@osfamily = osclass
|
1448
|
-
@osgen = osclass
|
1449
|
-
@accuracy = osclass
|
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
|
1473
|
-
@accuracy = os
|
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
|
|