nmap-parser 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/BUGS +2 -0
  2. data/LICENSE +20 -0
  3. data/README +43 -0
  4. data/lib/nmap/parser.rb +1602 -0
  5. metadata +52 -0
data/BUGS ADDED
@@ -0,0 +1,2 @@
1
+ If you find any bugs, please report them to me: katterjohn@gmail.com
2
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007, 2008 Kris Katterjohn
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README ADDED
@@ -0,0 +1,43 @@
1
+ ===============================================================================
2
+ == Ruby Nmap::Parser http://rubynmap.sourceforge.net ==
3
+ == Kris Katterjohn katterjohn@gmail.com ==
4
+ ===============================================================================
5
+
6
+ $Id: README 70 2008-04-22 23:13:01Z kjak $
7
+
8
+
9
+ OVERVIEW
10
+ ========
11
+
12
+ This library provides a Ruby interface to Nmap's scan data. It can run Nmap
13
+ and parse it's 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 storing and manipulating.
17
+
18
+
19
+ REQUIREMENTS and RECOMMENDATIONS
20
+ ================================
21
+
22
+ Things Required:
23
+
24
+ - Nmap (http://nmap.org) [Only required for parsescan()]
25
+
26
+ - REXML Ruby Library [In standard library with Ruby >= 1.8]
27
+
28
+ Things Recommended:
29
+
30
+ - Open3 Ruby Library [In standard library]
31
+
32
+
33
+ HELP
34
+ ====
35
+
36
+ The website has detailed RDoc documentation available. You can create a quick
37
+ set of these docs yourself by issuing the following command from the base
38
+ directory:
39
+
40
+ $ rdoc lib
41
+
42
+ A "doc" directory will be created. Just point your browser to doc/index.html
43
+
@@ -0,0 +1,1602 @@
1
+ # Ruby library for parsing Nmap's XML output
2
+ #
3
+ # http://rubynmap.sourceforge.net
4
+ #
5
+ # Author: Kris Katterjohn <katterjohn@gmail.com>
6
+ #
7
+ # Copyright (c) 2007, 2008 Kris Katterjohn
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ # of this software and associated documentation files (the "Software"), to deal
11
+ # in the Software without restriction, including without limitation the rights
12
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the Software is
14
+ # furnished to do so, subject to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in
17
+ # all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ # THE SOFTWARE.
26
+
27
+ # $Id: parser.rb 77 2008-04-24 22:19:29Z kjak $
28
+ # https://rubynmap.svn.sourceforge.net/svnroot/rubynmap
29
+
30
+ require 'rexml/document'
31
+
32
+ begin
33
+ require 'open3'
34
+ rescue LoadError
35
+ # We'll resort to IO.popen()
36
+ end
37
+
38
+ # Just holds the big Parser class.
39
+ module Nmap
40
+ end
41
+
42
+ =begin rdoc
43
+
44
+ == What Is This Library For?
45
+
46
+ This library provides a Ruby interface to Nmap's scan data. It can run Nmap
47
+ and parse it's 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 storing and manipulating.
51
+
52
+ The Nmap Security Scanner is a great program written and maintained by
53
+ Fyodor <fyodor@insecure.org>. Its main function is port scanning, but it also
54
+ has service and operating system detection, it's own scripting engine and a
55
+ whole lot more. One of it's many available output formats is XML, which allows
56
+ machines to handle all of the information instead of us slowly sifting through
57
+ tons of output!
58
+
59
+ If you've ever used Anthony Persaud's Perl Nmap::Parser, you'll notice some
60
+ similarities and should pick up on this API quickly. Just remember, it's far
61
+ from exactly the same! There are more classes, many different methods, and
62
+ blocks are used extensively (or available for use).
63
+
64
+ == Conventions
65
+
66
+ Depending on the data type, unavailable information is presented differently:
67
+
68
+ - Arrays are empty
69
+ - Non-arrays are nil, unless it's a method that returns the size of one of
70
+ the previously mentioned empty arrays. In this case they still return the
71
+ size (which would be 0).
72
+
73
+ All information available as arrays are presented via methods. These methods
74
+ not only return the array, but they also yield each element to a block if one
75
+ is given.
76
+
77
+ == Module Hierarchy
78
+
79
+ Nmap::Parser
80
+ |
81
+ + ::Session <- Scan session information
82
+ |
83
+ + ::Host <- General host information
84
+ |
85
+ + ::ExtraPorts <- Ports consolidated in an "ignored" state
86
+ |
87
+ + ::Port <- General port information
88
+ | |
89
+ | + ::Service <- Port Service information
90
+ |
91
+ + ::Script <- NSE Script information (both host and port)
92
+ |
93
+ + ::Times <- Timimg information (srtt, etc)
94
+ |
95
+ + ::Traceroute <- General Traceroute information
96
+ | |
97
+ | + ::Hop <- Individual Hop information
98
+ |
99
+ + ::OS <- OS Detection information
100
+ |
101
+ + ::OSClass <- OS Class information
102
+ |
103
+ + ::OSMatch <- OS Match information
104
+
105
+ == Parsing XML Data Already Available
106
+
107
+ require 'nmap/parser'
108
+
109
+ parser = Nmap::Parser.new(xml) # String of XML data
110
+
111
+ == Reading and Parsing from a File
112
+
113
+ require 'nmap/parser'
114
+
115
+ parser = Nmap::Parser.parsefile("log.xml")
116
+
117
+ == Reading and Parsing from an Object
118
+
119
+ The parseread() function isn't limited to IO objects like $stdin is
120
+ in the example: it can read from any object that responds to a read()
121
+ method that returns a String.
122
+
123
+ require 'nmap/parser'
124
+
125
+ parser = Nmap::Parser.parseread($stdin)
126
+
127
+ == Scanning and Parsing
128
+
129
+ This is the only parser option that requires Nmap to be available
130
+
131
+ require 'nmap/parser'
132
+
133
+ parser = Nmap::Parser.parsescan("sudo nmap", "--script all 192.168.1.0/24")
134
+
135
+ == Actually Doing Something
136
+
137
+ After printing a little session information, this example will cycle
138
+ through all of the up hosts, printing service information on the
139
+ open TCP ports, along with the name and output of any scripts that
140
+ ran (on the host and individual ports).
141
+
142
+ puts "Nmap args: #{parser.session.scan_args}"
143
+
144
+ puts "Runtime: #{parser.session.scan_time} seconds"
145
+
146
+ puts
147
+
148
+ parser.hosts("up") do |host|
149
+ puts "** Host #{host.addr} is up **"
150
+ puts
151
+
152
+ host.tcp_ports("open") do |port|
153
+ srv = port.service
154
+
155
+ puts "Port ##{port.num} is open (#{port.reason})"
156
+ puts "\tService: #{srv.name}" if srv.name
157
+ puts "\tProduct: #{srv.product}" if srv.product
158
+ puts "\tVersion: #{srv.product}" if srv.version
159
+ puts
160
+
161
+ port.scripts do |script|
162
+ puts "\tScript Name: #{script.id}"
163
+ puts "\tScript Output: #{script.output}"
164
+ puts
165
+ end
166
+ end
167
+
168
+ if host.scripts.any?
169
+ puts "Host Scripts Run:"
170
+ puts
171
+
172
+ host.scripts do |script|
173
+ puts "\tScript Name: #{script.id}"
174
+ puts "\tScript Output: #{script.output}"
175
+ puts
176
+ end
177
+ end
178
+
179
+ puts
180
+ end
181
+
182
+ =end
183
+ class Nmap::Parser
184
+ # Holds the raw XML output from Nmap
185
+ attr_reader :rawxml
186
+ # ::Session object for this scan
187
+ attr_reader :session
188
+
189
+ # Read and parse XML from the +obj+. +obj+ can be any object type
190
+ # that responds to a read() method that returns a String. IO and
191
+ # File are just a couple of examples.
192
+ #
193
+ # Returns a new Nmap::Parser object, and passes it to a block if
194
+ # one is given
195
+ def self.parseread(obj) # :yields: parser
196
+ if not obj.respond_to?("read")
197
+ raise "Object must respond to read()"
198
+ end
199
+
200
+ r = obj.read
201
+
202
+ raise "read() returned #{r.class} type!" if not r.is_a?String
203
+
204
+ p = new(r)
205
+
206
+ yield p if block_given?
207
+
208
+ p
209
+ end
210
+
211
+ # Read and parse contents of the Nmap XML file +filename+
212
+ #
213
+ # Returns a new Nmap::Parser object, and passes it to a block if
214
+ # one is given
215
+ def self.parsefile(filename) # :yields: parser
216
+ begin
217
+ p = parseread(File.open(filename))
218
+ rescue
219
+ raise "Error parsing \"#{filename}\": #{$!}"
220
+ end
221
+
222
+ yield p if block_given?
223
+
224
+ p
225
+ end
226
+
227
+ # Runs "+nmap+ -d +args+ +targets+"; returns a new Nmap::Parser object,
228
+ # and passes it to a block if one is given.
229
+ #
230
+ # +nmap+ is here to allow you to do things like:
231
+ #
232
+ # parser = Nmap::Parser.parsescan("sudo ./nmap", ....)
233
+ #
234
+ # and still make it easy for me to inject the options for XML
235
+ # output and debugging.
236
+ #
237
+ # +args+ can't contain arguments like -oA, -oX, etc. as these can
238
+ # interfere with Parser's processing. If you need that other output,
239
+ # just run Nmap yourself and pass -oX output to Parser via new. Or,
240
+ # you can use rawxml to grab the whole XML (as a String) and save it
241
+ # to a different file.
242
+ #
243
+ # +targets+ is an array of targets which will be split and appended to
244
+ # the command. It's optional and only for convenience because you can
245
+ # put any targets you want scanned in +args+.
246
+ def self.parsescan(nmap, args, targets = []) # :yields: parser
247
+ if args =~ /[^-]-o|^-o/
248
+ raise "Output option (-o*) passed to parsescan()"
249
+ end
250
+
251
+ # Enable debugging, give us our XML output, pass args
252
+ command = nmap + " -d -oX - " + args + " "
253
+
254
+ command += targets.join(" ") if targets.any?
255
+
256
+ p = nil
257
+
258
+ begin
259
+ # First try popen3() if it loaded successfully..
260
+ Open3.popen3(command) do |sin, sout, serr|
261
+ p = parseread(sout)
262
+ end
263
+ rescue NameError
264
+ # ..but fall back to popen() if not
265
+ IO.popen(command) do |io|
266
+ p = parseread(io)
267
+ end
268
+ end
269
+
270
+ yield p if block_given?
271
+
272
+ p
273
+ end
274
+
275
+ # Returns an array of Host objects, and passes them each to a block if
276
+ # one is given
277
+ #
278
+ # If an argument is given, only hosts matching +status+ are given
279
+ def hosts(status = "") # :yields: host
280
+ shosts = []
281
+
282
+ @hosts.each do |host|
283
+ if status.empty? or host.status == status
284
+ shosts << host
285
+ yield host if block_given?
286
+ end
287
+ end
288
+
289
+ shosts
290
+ end
291
+
292
+ # Returns a Host object for the host with the specified IP address
293
+ # or hostname +hostip+
294
+ def host(hostip)
295
+ @hosts.find do |host|
296
+ host.addr == hostip or host.hostname == hostip
297
+ end
298
+ end
299
+
300
+ alias get_host host
301
+
302
+ # Deletes host with the specified IP address or hostname +hostip+
303
+ #
304
+ # Note: From inside of a block given to a method like hosts() or
305
+ # get_ips(), calling this method on a host passed to the block may
306
+ # lead to adverse effects:
307
+ #
308
+ # parser.hosts { |x| puts x.addr; del_host(x) } # Don't do this!
309
+ def del_host(hostip)
310
+ @hosts.delete_if do |host|
311
+ host.addr == hostip or host.hostname == hostip
312
+ end
313
+ end
314
+
315
+ alias delete_host del_host
316
+
317
+ # Returns an array of IPs scanned, and passes them each to a
318
+ # block if one is given
319
+ #
320
+ # If an argument is given, only hosts matching +status+ are given
321
+ def get_ips(status = "") # :yields: host.addr
322
+ ips = []
323
+
324
+ @hosts.each do |host|
325
+ if status.empty? or host.status == status
326
+ ips << host.addr
327
+ yield host.addr if block_given?
328
+ end
329
+ end
330
+
331
+ ips
332
+ end
333
+
334
+ # This operator compares the rawxml members
335
+ def ==(parser)
336
+ @rawxml == parser.rawxml
337
+ end
338
+
339
+ private
340
+
341
+ def initialize(xml) # :yields: parser
342
+ if not xml.is_a?String
343
+ raise "Invalid input: new() should be passed a String"
344
+ end
345
+
346
+ parse(xml)
347
+
348
+ yield self if block_given?
349
+ end
350
+
351
+ def parse(xml)
352
+ @rawxml = xml
353
+
354
+ begin
355
+ root = REXML::Document.new(xml).root
356
+ rescue
357
+ raise "Error parsing XML: #{$!}"
358
+ end
359
+
360
+ raise "Error in XML data" if root.nil?
361
+
362
+ @session = Session.new(root)
363
+
364
+ @hosts = []
365
+
366
+ root.each_element('host') do |host|
367
+ @hosts << Host.new(host)
368
+ end
369
+ end
370
+ end
371
+
372
+ # This holds session information, such as runtime, Nmap's arguments,
373
+ # and verbosity/debugging
374
+ class Nmap::Parser::Session
375
+ # Holds the command run to initiate the scan
376
+ attr_reader :scan_args
377
+ # The Nmap version number used to scan
378
+ attr_reader :nmap_version
379
+ # XML version of Nmap's output
380
+ attr_reader :xml_version
381
+ # Starting time
382
+ attr_reader :start_str, :start_time
383
+ # Ending time
384
+ attr_reader :stop_str, :stop_time
385
+ # How long the scan took (in seconds). Same as stop_time - start_time
386
+ attr_reader :scan_time
387
+ # Amount of verbosity (-v) used while scanning
388
+ attr_reader :verbose
389
+ # Amount of debugging (-d) used while scanning
390
+ attr_reader :debug
391
+
392
+ alias verbosity verbose
393
+ alias debugging debug
394
+
395
+ # Returns the total number of services that were scanned or, if an
396
+ # argument is given, returns the number of services scanned for +type+
397
+ # (e.g. "syn")
398
+ def numservices(type = "")
399
+ total = 0
400
+
401
+ @scaninfo.each do |info|
402
+ if type.empty?
403
+ total += info.numservices
404
+ elsif info.type == type
405
+ return info.numservices
406
+ end
407
+ end
408
+
409
+ total
410
+ end
411
+
412
+ # Returns the protocol associated with the specified scan +type+
413
+ # (e.g. "tcp" for type "syn")
414
+ def scan_type_proto(type)
415
+ @scaninfo.each do |info|
416
+ return info.proto if info.type == type
417
+ end
418
+
419
+ nil
420
+ end
421
+
422
+ # Returns an array of all the scan types performed, and passes them
423
+ # each to a block if one if given
424
+ def scan_types() # :yields: scantype
425
+ types = []
426
+
427
+ @scaninfo.each do |info|
428
+ types << info.type
429
+ yield info.type if block_given?
430
+ end
431
+
432
+ types
433
+ end
434
+
435
+ private
436
+
437
+ def initialize(root)
438
+ parse(root)
439
+ end
440
+
441
+ def parse(root)
442
+ @scan_args = root.attributes['args']
443
+
444
+ @nmap_version = root.attributes['version']
445
+
446
+ @xml_version = root.attributes['xmloutputversion'].to_f
447
+
448
+ @start_str = root.attributes['startstr']
449
+ @start_time = root.attributes['start'].to_i
450
+
451
+ @stop_str = root.elements['runstats/finished'].attributes['timestr']
452
+ @stop_time = root.elements['runstats/finished'].attributes['time'].to_i
453
+
454
+ @scan_time = @stop_time - @start_time
455
+
456
+ @verbose = root.elements['verbose'].attributes['level'].to_i
457
+ @debug = root.elements['debugging'].attributes['level'].to_i
458
+
459
+ @scaninfo = []
460
+
461
+ root.each_element('scaninfo') do |info|
462
+ @scaninfo << ScanInfo.new(info)
463
+ end
464
+ end
465
+ end
466
+
467
+ class Nmap::Parser::Session::ScanInfo # :nodoc: all
468
+ attr_reader :type, :proto, :numservices
469
+
470
+ private
471
+
472
+ def initialize(info)
473
+ parse(info)
474
+ end
475
+
476
+ def parse(info)
477
+ @type = info.attributes['type']
478
+ @proto = info.attributes['protocol']
479
+ @numservices = info.attributes['numservices'].to_i
480
+ end
481
+ end
482
+
483
+ # This holds all of the information about a target host.
484
+ #
485
+ # Status, IP/MAC addresses, hostnames, all that. Port information is
486
+ # available in this class; either accessed through here or directly
487
+ # from a Port object.
488
+ class Nmap::Parser::Host
489
+ # The status of the host, typically "up" or "down"
490
+ attr_reader :status
491
+ # The reason for the status
492
+ attr_reader :reason
493
+ # The IPv4 address
494
+ attr_reader :ip4_addr
495
+ # The IPv6 address
496
+ attr_reader :ip6_addr
497
+ # The MAC address
498
+ attr_reader :mac_addr
499
+ # The MAC vendor
500
+ attr_reader :mac_vendor
501
+ # ::OS object holding Operating System information
502
+ attr_reader :os
503
+ # The number of "weird responses"
504
+ attr_reader :smurf
505
+ # TCP Sequence Number information
506
+ attr_reader :tcpsequence_index, :tcpsequence_class
507
+ # TCP Sequence Number information
508
+ attr_reader :tcpsequence_values, :tcpsequence_difficulty
509
+ # IPID Sequence Number information
510
+ attr_reader :ipidsequence_class, :ipidsequence_values
511
+ # TCP Timestamp Sequence Number information
512
+ attr_reader :tcptssequence_class, :tcptssequence_values
513
+ # Uptime information
514
+ attr_reader :uptime_seconds, :uptime_lastboot
515
+ # ::Traceroute object
516
+ attr_reader :traceroute
517
+ # Network distance (not necessarily the same as from traceroute)
518
+ attr_reader :distance
519
+ # ::Times object holding timing information
520
+ attr_reader :times
521
+
522
+ alias ipv4_addr ip4_addr
523
+ alias ipv6_addr ip6_addr
524
+
525
+ # Returns the IPv4 or IPv6 address of host
526
+ def addr
527
+ @ip4_addr or @ip6_addr
528
+ end
529
+
530
+ # Returns an array containing all of the hostnames for this host,
531
+ # and passes them each to a block if one is given
532
+ def all_hostnames
533
+ if block_given?
534
+ @hostnames.each do |hostname|
535
+ yield hostname
536
+ end
537
+ end
538
+
539
+ @hostnames
540
+ end
541
+
542
+ alias hostnames all_hostnames
543
+
544
+ # Returns the first hostname, or nil if unavailable
545
+ def hostname
546
+ @hostnames[0]
547
+ end
548
+
549
+ # Returns an array of ExtraPorts objects, and passes them each to a
550
+ # block if one if given
551
+ def extraports
552
+ if block_given?
553
+ @extraports.each do |extraports|
554
+ yield extraports
555
+ end
556
+ end
557
+
558
+ @extraports
559
+ end
560
+
561
+ # Returns the Port object for the TCP port +portnum+, and passes it to
562
+ # a block if one is given
563
+ def tcp_port(portnum) # :yields: port
564
+ yield @tcpPorts[portnum.to_s] if block_given?
565
+
566
+ @tcpPorts[portnum.to_s]
567
+ end
568
+
569
+ # Returns an array of Port objects for each TCP port, and passes them
570
+ # each to a block if one is given
571
+ #
572
+ # If an argument is given, only ports matching +state+ are given.
573
+ # Note: "open" will also match "open|filtered", but not vice versa.
574
+ def tcp_ports(state = "")
575
+ list = []
576
+
577
+ @tcpPorts.invert.each_key do |port|
578
+ list << port if state.empty? or port.state >= state
579
+ end
580
+
581
+ list.sort!
582
+
583
+ if block_given?
584
+ list.each do |port|
585
+ yield port
586
+ end
587
+ end
588
+
589
+ list
590
+ end
591
+
592
+ # Returns an array of TCP port numbers, and passes them each to a block
593
+ # if one given
594
+ #
595
+ # If an argument is given, only ports matching +state+ are given.
596
+ # Note: "open" will also match "open|filtered", but not vice versa.
597
+ def tcp_port_list(state = "")
598
+ list = []
599
+
600
+ @tcpPorts.invert.each_key do |port|
601
+ list << port.num if state.empty? or port.state >= state
602
+ end
603
+
604
+ list.sort!
605
+
606
+ if block_given?
607
+ list.each do |port|
608
+ yield port
609
+ end
610
+ end
611
+
612
+ list
613
+ end
614
+
615
+ # Returns state reason of TCP port +portnum+
616
+ def tcp_reason(portnum)
617
+ return nil if @tcpPorts[portnum.to_s].nil?
618
+
619
+ @tcpPorts[portnum.to_s].reason
620
+ end
621
+
622
+ # Returns the Script object for the script +name+ run against the
623
+ # TCP port +portnum+
624
+ def tcp_script(portnum, name)
625
+ return nil if @tcpPorts[portnum.to_s].nil?
626
+
627
+ @tcpPorts[portnum.to_s].script(name)
628
+ end
629
+
630
+ # Returns an array of Script objects for each script run on the
631
+ # TCP port +portnum+, and passes them each to a block if given
632
+ def tcp_scripts(portnum)
633
+ return nil if @tcpPorts[portnum.to_s].nil?
634
+
635
+ if block_given?
636
+ @tcpPorts[portnum.to_s].scripts do |script|
637
+ yield script
638
+ end
639
+ end
640
+
641
+ @tcpPorts[portnum.to_s].scripts
642
+ end
643
+
644
+ # Returns the output of the script +name+ on the TCP port +portnum+
645
+ def tcp_script_output(portnum, name)
646
+ return nil if @tcpPorts[portnum.to_s].nil?
647
+
648
+ @tcpPorts[portnum.to_s].script_output(name)
649
+ end
650
+
651
+ # Returns a Port::Service object for TCP port +portnum+
652
+ def tcp_service(portnum)
653
+ return nil if @tcpPorts[portnum.to_s].nil?
654
+
655
+ @tcpPorts[portnum.to_s].service
656
+ end
657
+
658
+ # Returns state of TCP port +portnum+
659
+ def tcp_state(portnum)
660
+ return nil if @tcpPorts[portnum.to_s].nil?
661
+
662
+ @tcpPorts[portnum.to_s].state
663
+ end
664
+
665
+ # Returns the Port object for the UDP port +portnum+, and passes it to
666
+ # a block if one is given
667
+ def udp_port(portnum) # :yields: port
668
+ yield @udpPorts[portnum.to_s] if block_given?
669
+
670
+ @udpPorts[portnum.to_s]
671
+ end
672
+
673
+ # Returns an array of Port objects for each UDP port, and passes them
674
+ # each to a block if one is given
675
+ #
676
+ # If an argument is given, only ports matching +state+ are given.
677
+ # Note: "open" will also match "open|filtered", but not vice versa.
678
+ def udp_ports(state = "")
679
+ list = []
680
+
681
+ @udpPorts.invert.each_key do |port|
682
+ list << port if state.empty? or port.state >= state
683
+ end
684
+
685
+ list.sort!
686
+
687
+ if block_given?
688
+ list.each do |port|
689
+ yield port
690
+ end
691
+ end
692
+
693
+ list
694
+ end
695
+
696
+ # Returns an array of UDP port numbers, and passes them each to a block
697
+ # if one is given
698
+ #
699
+ # If an argument is given, only ports matching +state+ are given.
700
+ # Note: "open" will also match "open|filtered", but not vice versa.
701
+ def udp_port_list(state = "")
702
+ list = []
703
+
704
+ @udpPorts.invert.each_key do |port|
705
+ list << port.num if state.empty? or port.state >= state
706
+ end
707
+
708
+ list.sort!
709
+
710
+ if block_given?
711
+ list.each do |port|
712
+ yield port
713
+ end
714
+ end
715
+
716
+ list
717
+ end
718
+
719
+ # Returns state reason of UDP port +portnum+
720
+ def udp_reason(portnum)
721
+ return nil if @udpPorts[portnum.to_s].nil?
722
+
723
+ @udpPorts[portnum.to_s].reason
724
+ end
725
+
726
+ # Returns the Script object for the script +name+ run against the
727
+ # UDP port +portnum+
728
+ def udp_script(portnum, name)
729
+ return nil if @udpPorts[portnum.to_s].nil?
730
+
731
+ @udpPorts[portnum.to_s].script(name)
732
+ end
733
+
734
+ # Returns an array of Script objects for each script run on the
735
+ # UDP port +portnum+, and passes them each to a block if given
736
+ def udp_scripts(portnum)
737
+ return nil if @udpPorts[portnum.to_s].nil?
738
+
739
+ if block_given?
740
+ @udpPorts[portnum.to_s].scripts do |script|
741
+ yield script
742
+ end
743
+ end
744
+
745
+ @udpPorts[portnum.to_s].scripts
746
+ end
747
+
748
+ # Returns the output of the script +name+ on the UDP port +portnum+
749
+ def udp_script_output(portnum, name)
750
+ return nil if @udpPorts[portnum.to_s].nil?
751
+
752
+ @udpPorts[portnum.to_s].script_output(name)
753
+ end
754
+
755
+ # Returns a Port::Service object for UDP port +portnum+
756
+ def udp_service(portnum)
757
+ return nil if @udpPorts[portnum.to_s].nil?
758
+
759
+ @udpPorts[portnum.to_s].service
760
+ end
761
+
762
+ # Returns state of UDP port +portnum+
763
+ def udp_state(portnum)
764
+ return nil if @udpPorts[portnum.to_s].nil?
765
+
766
+ @udpPorts[portnum.to_s].state
767
+ end
768
+
769
+ # Returns the Port object for the IP protocol +protonum+, and passes it
770
+ # to a block if one is given
771
+ def ip_proto(protonum) # :yields: proto
772
+ yield @ipProtos[protonum.to_s] if block_given?
773
+
774
+ @ipProtos[protonum.to_s]
775
+ end
776
+
777
+ # Returns an array of Port objects for each IP protocol, and passes
778
+ # them each to a block if one is given
779
+ #
780
+ # If an argument is given, only protocols matching +state+ are given.
781
+ # Note: "open" will also match "open|filtered", but not vice versa.
782
+ def ip_protos(state = "")
783
+ list = []
784
+
785
+ @ipProtos.invert.each_key do |proto|
786
+ list << proto if state.empty? or proto.state >= state
787
+ end
788
+
789
+ list.sort!
790
+
791
+ if block_given?
792
+ list.each do |proto|
793
+ yield proto
794
+ end
795
+ end
796
+
797
+ list
798
+ end
799
+
800
+ # Returns an array of IP protocol numbers, and passes them each to a
801
+ # block if one given
802
+ #
803
+ # If an argument is given, only protocols matching +state+ are given.
804
+ # Note: "open" will also match "open|filtered", but not vice versa.
805
+ def ip_proto_list(state = "")
806
+ list = []
807
+
808
+ @ipProtos.invert.each_key do |proto|
809
+ list << proto.num if state.empty? or proto.state >= state
810
+ end
811
+
812
+ list.sort!
813
+
814
+ if block_given?
815
+ list.each do |proto|
816
+ yield proto
817
+ end
818
+ end
819
+
820
+ list
821
+ end
822
+
823
+ # Returns state reason of IP protocol +protonum+
824
+ def ip_reason(protonum)
825
+ return nil if @ipProtos[protonum.to_s].nil?
826
+
827
+ @ipProtos[protonum.to_s].reason
828
+ end
829
+
830
+ # Returns a Port::Service object for IP protocol +protonum+
831
+ def ip_service(protonum)
832
+ return nil if @ipProtos[protonum.to_s].nil?
833
+
834
+ @ipProtos[protonum.to_s].service
835
+ end
836
+
837
+ # Returns state of IP protocol +protonum+
838
+ def ip_state(protonum)
839
+ return nil if @ipProtos[protonum.to_s].nil?
840
+
841
+ @ipProtos[protonum.to_s].state
842
+ end
843
+
844
+ # Returns the Script object for the specified host script +name+
845
+ def script(name)
846
+ @scripts.find do |script|
847
+ script.id == name
848
+ end
849
+ end
850
+
851
+ # Returns an array of Script objects for each host script run, and
852
+ # passes them each to a block if given
853
+ def scripts
854
+ if block_given?
855
+ @scripts.each do |script|
856
+ yield script
857
+ end
858
+ end
859
+
860
+ @scripts
861
+ end
862
+
863
+ # Returns the output of the specified host script +name+
864
+ def script_output(name)
865
+ @scripts.each do |script|
866
+ return script.output if script.id == name
867
+ end
868
+
869
+ nil
870
+ end
871
+
872
+ private
873
+
874
+ def initialize(hostinfo)
875
+ parse(hostinfo)
876
+ end
877
+
878
+ # Convert a string like '22-25,80,110-900' into
879
+ # an array with all the uncondensed elements, e.g.
880
+ # [ '22', '23', '24', '25, '80', '110', '111' ... ]
881
+ def parsePortlist(ports)
882
+ list = []
883
+
884
+ ports.split(",").each do |set|
885
+ pa = set.split("-")
886
+
887
+ if pa.length > 1
888
+ pa[0].upto(pa[1]) do |p|
889
+ list << p
890
+ end
891
+ else
892
+ list << portc
893
+ end
894
+ end
895
+
896
+ list
897
+ end
898
+
899
+ def parseAddr(elem)
900
+ case elem.attributes['addrtype']
901
+ when "mac"
902
+ @mac_addr = elem.attributes['addr']
903
+ @mac_vendor = elem.attributes['vendor']
904
+ when "ipv4"
905
+ @ip4_addr = elem.attributes['addr']
906
+ when "ipv6"
907
+ @ip6_addr = elem.attributes['addr']
908
+ end
909
+ end
910
+
911
+ def parseHostnames(elem)
912
+ @hostnames = []
913
+
914
+ return nil if elem.nil?
915
+
916
+ elem.each_element('hostname') do |name|
917
+ @hostnames << name.attributes['name']
918
+ end
919
+ end
920
+
921
+ def parsePorts(ports)
922
+ @tcpPorts = {}
923
+ @udpPorts = {}
924
+ @ipProtos = {}
925
+
926
+ return nil if ports.nil?
927
+
928
+ ports.each_element('port') do |port|
929
+ num = port.attributes['portid']
930
+ proto = port.attributes['protocol']
931
+
932
+ if proto == "tcp"
933
+ @tcpPorts[num] = Port.new(port)
934
+ elsif proto == "udp"
935
+ @udpPorts[num] = Port.new(port)
936
+ elsif proto == "ip"
937
+ @ipProtos[num] = Port.new(port)
938
+ end
939
+ end
940
+ end
941
+
942
+ def parseExtraPorts(ports)
943
+ @extraports = []
944
+
945
+ return nil if ports.nil?
946
+
947
+ ports.each_element('extraports') do |e|
948
+ @extraports << ExtraPorts.new(e)
949
+ end
950
+
951
+ @extraports.sort! do |x, y|
952
+ x.count <=> y.count
953
+ end
954
+ end
955
+
956
+ def parseScripts(scriptlist)
957
+ @scripts = []
958
+
959
+ return nil if scriptlist.nil?
960
+
961
+ scriptlist.each_element('script') do |script|
962
+ @scripts << Script.new(script)
963
+ end
964
+ end
965
+
966
+ def tcpseq(seq)
967
+ return nil if seq.nil?
968
+
969
+ @tcpsequence_index = seq.attributes['index']
970
+ @tcpsequence_class = seq.attributes['class']
971
+ @tcpsequence_values = seq.attributes['values']
972
+ @tcpsequence_difficulty = seq.attributes['difficulty']
973
+ end
974
+
975
+ def ipidseq(seq)
976
+ return nil if seq.nil?
977
+
978
+ @ipidsequence_class = seq.attributes['class']
979
+ @ipidsequence_values = seq.attributes['values']
980
+ end
981
+
982
+ def tcptsseq(seq)
983
+ return nil if seq.nil?
984
+
985
+ @tcptssequence_class = seq.attributes['class']
986
+ @tcptssequence_values = seq.attributes['values']
987
+ end
988
+
989
+ def uptime(time)
990
+ return nil if time.nil?
991
+
992
+ @uptime_seconds = time.attributes['seconds'].to_i
993
+ @uptime_lastboot = time.attributes['lastboot']
994
+ end
995
+
996
+ def parse(host)
997
+ status = host.elements['status']
998
+
999
+ @status = status.attributes['state']
1000
+
1001
+ @reason = status.attributes['reason']
1002
+
1003
+ @os = OS.new(host.elements['os'])
1004
+
1005
+ host.each_element('address') do |elem|
1006
+ parseAddr(elem)
1007
+ end
1008
+
1009
+ parseHostnames(host.elements['hostnames'])
1010
+
1011
+ smurf = host.elements['smurf']
1012
+ @smurf = smurf.attributes['responses'] if smurf
1013
+
1014
+ ports = host.elements['ports']
1015
+
1016
+ parsePorts(ports)
1017
+
1018
+ parseExtraPorts(ports)
1019
+
1020
+ parseScripts(host.elements['hostscript'])
1021
+
1022
+ if trace = host.elements['trace']
1023
+ @traceroute = Traceroute.new(trace)
1024
+ end
1025
+
1026
+ tcpseq(host.elements['tcpsequence'])
1027
+
1028
+ ipidseq(host.elements['ipidsequence'])
1029
+
1030
+ tcptsseq(host.elements['tcptssequence'])
1031
+
1032
+ uptime(host.elements['uptime'])
1033
+
1034
+ distance = host.elements['distance']
1035
+ @distance = distance.attributes['value'].to_i if distance
1036
+
1037
+ @times = Times.new(host.elements['times'])
1038
+ end
1039
+ end
1040
+
1041
+ # This holds information on the time statistics for this host
1042
+ class Nmap::Parser::Host::Times
1043
+ # Smoothed round-trip time
1044
+ attr_reader :srtt
1045
+ # Round-trip time variance / deviation
1046
+ attr_reader :rttvar
1047
+ # How long before giving up on a probe (timeout)
1048
+ attr_reader :to
1049
+
1050
+ private
1051
+
1052
+ def initialize(times)
1053
+ parse(times)
1054
+ end
1055
+
1056
+ def parse(times)
1057
+ return nil if times.nil?
1058
+
1059
+ @srtt = times.attributes['srtt'].to_i
1060
+ @rttvar = times.attributes['rttvar'].to_i
1061
+ @to = times.attributes['to'].to_i
1062
+ end
1063
+ end
1064
+
1065
+ # This holds the information about an NSE script run against a host or port
1066
+ class Nmap::Parser::Host::Script
1067
+ # NSE Script name
1068
+ attr_reader :id
1069
+ # NSE Script output
1070
+ attr_reader :output
1071
+
1072
+ alias name id
1073
+
1074
+ private
1075
+
1076
+ def initialize(script)
1077
+ parse(script)
1078
+ end
1079
+
1080
+ def parse(script)
1081
+ return nil if script.nil?
1082
+
1083
+ @id = script.attributes['id']
1084
+ @output = script.attributes['output']
1085
+ end
1086
+ end
1087
+
1088
+ # This holds the information about an individual port or protocol
1089
+ class Nmap::Parser::Host::Port
1090
+ # Port number
1091
+ attr_reader :num
1092
+ # ::Service object for this port
1093
+ attr_reader :service
1094
+ # Port state ("open", "closed", "filtered", etc)
1095
+ attr_reader :state
1096
+ # Why the port is in the state
1097
+ attr_reader :reason
1098
+ # The host that responded, if different than the target
1099
+ attr_reader :reason_ip
1100
+ # TTL from the responding host
1101
+ attr_reader :reason_ttl
1102
+
1103
+ # Returns the Script object with the specified +name+
1104
+ def script(name)
1105
+ @scripts.find do |script|
1106
+ script.id == name
1107
+ end
1108
+ end
1109
+
1110
+ # Returns an array of Script objects associated with this port, and
1111
+ # passes them each to a block if one is given
1112
+ def scripts
1113
+ if block_given?
1114
+ @scripts.each do |script|
1115
+ yield script
1116
+ end
1117
+ end
1118
+
1119
+ @scripts
1120
+ end
1121
+
1122
+ # Returns the output of the script +name+
1123
+ def script_output(name)
1124
+ @scripts.each do |script|
1125
+ return script.output if script.id == name
1126
+ end
1127
+
1128
+ nil
1129
+ end
1130
+
1131
+ # Compares port numbers
1132
+ def <=>(port)
1133
+ @num <=> port.num
1134
+ end
1135
+
1136
+ private
1137
+
1138
+ def initialize(portinfo)
1139
+ parse(portinfo)
1140
+ end
1141
+
1142
+ def parse(port)
1143
+ @num = port.attributes['portid'].to_i
1144
+
1145
+ state = port.elements['state']
1146
+ @state = state.attributes['state']
1147
+ @reason = state.attributes['reason']
1148
+ @reason_ttl = state.attributes['reason_ttl'].to_i
1149
+ @reason_ip = state.attributes['reason_ip']
1150
+
1151
+ @service = Service.new(port)
1152
+
1153
+ @scripts = []
1154
+
1155
+ port.each_element('script') do |script|
1156
+ @scripts << Nmap::Parser::Host::Script.new(script)
1157
+ end
1158
+ end
1159
+ end
1160
+
1161
+ # This holds the information about "extra ports": groups of ports which have
1162
+ # the same state.
1163
+ class Nmap::Parser::Host::ExtraPorts
1164
+ # Total number of ports in this state
1165
+ attr_reader :count
1166
+ # What state the ports are in
1167
+ attr_reader :state
1168
+
1169
+ # Returns an array of arrays, each of which are in the form of:
1170
+ #
1171
+ # [ <port count>, reason ]
1172
+ #
1173
+ # for each set of reasons, and passes them each to a block if one is
1174
+ # given
1175
+ def reasons
1176
+ if block_given?
1177
+ @reasons.each do |reason|
1178
+ yield reason
1179
+ end
1180
+ end
1181
+
1182
+ @reasons
1183
+ end
1184
+
1185
+ private
1186
+
1187
+ def initialize(extraports)
1188
+ parse(extraports)
1189
+ end
1190
+
1191
+ def parse(extraports)
1192
+ @count = extraports.attributes['count'].to_i
1193
+ @state = extraports.attributes['state']
1194
+
1195
+ @reasons = []
1196
+
1197
+ extraports.each_element('extrareasons') do |extra|
1198
+ ecount = extra.attributes['count'].to_i
1199
+ ereason = extra.attributes['reason']
1200
+ @reasons << [ ecount, ereason ]
1201
+ end
1202
+ end
1203
+ end
1204
+
1205
+ # This holds information on a traceroute, such as the port and protocol used
1206
+ # and an array of responsive hops
1207
+ class Nmap::Parser::Host::Traceroute
1208
+ # The port used during traceroute
1209
+ attr_reader :port
1210
+ # The protocol used during traceroute
1211
+ attr_reader :proto
1212
+
1213
+ # Returns the Hop object for the given TTL
1214
+ def hop(ttl)
1215
+ @hops.find do |h|
1216
+ h.ttl == ttl.to_i
1217
+ end
1218
+ end
1219
+
1220
+ # Returns an array of Hop objects, which are each a responsive hop,
1221
+ # and passes them each to a block if one if given.
1222
+ def hops
1223
+ if block_given?
1224
+ @hops.each do |h|
1225
+ yield h
1226
+ end
1227
+ end
1228
+
1229
+ @hops
1230
+ end
1231
+
1232
+ private
1233
+
1234
+ def initialize(trace)
1235
+ parse(trace)
1236
+ end
1237
+
1238
+ def parse(trace)
1239
+ @port = trace.attributes['port'].to_i
1240
+ @proto = trace.attributes['proto']
1241
+
1242
+ @hops = []
1243
+
1244
+ trace.each_element('hop') do |hop|
1245
+ @hops << Hop.new(hop)
1246
+ end
1247
+ end
1248
+ end
1249
+
1250
+ # This holds information on an individual traceroute hop
1251
+ class Nmap::Parser::Host::Traceroute::Hop
1252
+ # How many hops away the host is
1253
+ attr_reader :ttl
1254
+ # The round-trip time of the host
1255
+ attr_reader :rtt
1256
+ # The IP address of the host
1257
+ attr_reader :addr
1258
+ # The hostname of the host
1259
+ attr_reader :hostname
1260
+
1261
+ alias host hostname
1262
+ alias ipaddr addr
1263
+
1264
+ private
1265
+
1266
+ def initialize(hop)
1267
+ parse(hop)
1268
+ end
1269
+
1270
+ def parse(hop)
1271
+ @ttl = hop.attributes['ttl'].to_i
1272
+ @rtt = hop.attributes['rtt'].to_f
1273
+ @addr = hop.attributes['ipaddr']
1274
+ @hostname = hop.attributes['host']
1275
+ end
1276
+ end
1277
+
1278
+ # This holds the service information for a port
1279
+ class Nmap::Parser::Host::Port::Service
1280
+ # The name of the service
1281
+ attr_reader :name
1282
+ # Vendor name
1283
+ attr_reader :product
1284
+ # Version number
1285
+ attr_reader :version
1286
+ # How this information was obtained, such as "table" or "probed"
1287
+ attr_reader :method
1288
+ # Service owner
1289
+ attr_reader :owner
1290
+ # Any tunnelling used, like "ssl"
1291
+ attr_reader :tunnel
1292
+ # RPC program number
1293
+ attr_reader :rpcnum
1294
+ # Range of RPC version numbers
1295
+ attr_reader :lowver, :highver
1296
+ # How confident the version detection is
1297
+ attr_reader :confidence
1298
+ # Protocol, such as "rpc"
1299
+ attr_reader :proto
1300
+ # Extra misc. information about the service
1301
+ attr_reader :extra
1302
+ # The type of device the service is running on
1303
+ attr_reader :devicetype
1304
+ # The OS the service is running on
1305
+ attr_reader :ostype
1306
+ # The service fingerprint
1307
+ attr_reader :fingerprint
1308
+
1309
+ alias extrainfo extra
1310
+
1311
+ private
1312
+
1313
+ def initialize(port)
1314
+ parse(port)
1315
+ end
1316
+
1317
+ def parse(port)
1318
+ return nil if port.nil?
1319
+
1320
+ service = port.elements['service']
1321
+
1322
+ return nil if service.nil?
1323
+
1324
+ @name = service.attributes['name']
1325
+ @product = service.attributes['product']
1326
+ @version = service.attributes['version']
1327
+ @method = service.attributes['method']
1328
+ owner = port.elements['owner']
1329
+ @owner = owner.attributes['name'] if owner
1330
+ @tunnel = service.attributes['tunnel']
1331
+ rpcnum = service.attributes['rpcnum']
1332
+ @rpcnum = rpcnum.to_i if rpcnum
1333
+ lowver = service.attributes['lowver']
1334
+ @lowver = lowver.to_i if lowver
1335
+ highver = service.attributes['highver']
1336
+ @highver = highver.to_i if highver
1337
+ conf = service.attributes['conf']
1338
+ @confidence = conf.to_i if conf
1339
+ @proto = service.attributes['proto']
1340
+ @extra = service.attributes['extrainfo']
1341
+ @devicetype = service.attributes['devicetype']
1342
+ @ostype = service.attributes['ostype']
1343
+ @fingerprint = service.attributes['servicefp']
1344
+ end
1345
+ end
1346
+
1347
+ # This holds the OS information from OS Detection
1348
+ class Nmap::Parser::Host::OS
1349
+ # OS fingerprint
1350
+ attr_reader :fingerprint
1351
+
1352
+ # Returns an array of OSClass objects, and passes them each to a
1353
+ # block if one is given
1354
+ def osclasses
1355
+ if block_given?
1356
+ @osclasses.each do |osclass|
1357
+ yield osclass
1358
+ end
1359
+ end
1360
+
1361
+ @osclasses
1362
+ end
1363
+
1364
+ # Returns an array of OSMatch objects, and passes them each to a
1365
+ # block if one is given
1366
+ def osmatches
1367
+ if block_given?
1368
+ @osmatches.each do |osmatch|
1369
+ yield osmatch
1370
+ end
1371
+ end
1372
+
1373
+ @osmatches
1374
+ end
1375
+
1376
+ # Returns the number of OS class records
1377
+ def class_count
1378
+ @osclasses.size
1379
+ end
1380
+
1381
+ # Returns OS class accuracy of the first OS class record, or Nth record
1382
+ # as specified by +index+
1383
+ def class_accuracy(index = 0)
1384
+ return nil if @osclasses.empty?
1385
+
1386
+ @osclasses[index.to_i].accuracy
1387
+ end
1388
+
1389
+ # Returns OS family information of first OS class record, or Nth record
1390
+ # as specified by +index+
1391
+ def osfamily(index = 0)
1392
+ return nil if @osclasses.empty?
1393
+
1394
+ @osclasses[index.to_i].osfamily
1395
+ end
1396
+
1397
+ # Returns OS generation information of first OS class record, or Nth
1398
+ # record as specified by +index+
1399
+ def osgen(index = 0)
1400
+ return nil if @osclasses.empty?
1401
+
1402
+ @osclasses[index.to_i].osgen
1403
+ end
1404
+
1405
+ # Returns OS type information of the first OS class record, or Nth
1406
+ # record as specified by +index+
1407
+ def ostype(index = 0)
1408
+ return nil if @osclasses.empty?
1409
+
1410
+ @osclasses[index.to_i].ostype
1411
+ end
1412
+
1413
+ # Returns OS vendor information of the first OS class record, or Nth
1414
+ # record as specified by +index+
1415
+ def osvendor(index = 0)
1416
+ return nil if @osclasses.empty?
1417
+
1418
+ @osclasses[index.to_i].osvendor
1419
+ end
1420
+
1421
+ # Returns the number of OS match records
1422
+ def name_count
1423
+ @osmatches.size
1424
+ end
1425
+
1426
+ # Returns name of first OS match record, or Nth record as specified by
1427
+ # +index+
1428
+ def name(index = 0)
1429
+ return nil if @osmatches.empty?
1430
+
1431
+ @osmatches[index.to_i].name
1432
+ end
1433
+
1434
+ # Returns OS name accuracy of the first OS match record, or Nth record
1435
+ # as specified by +index+
1436
+ def name_accuracy(index = 0)
1437
+ return nil if @osmatches.empty?
1438
+
1439
+ @osmatches[index.to_i].accuracy
1440
+ end
1441
+
1442
+ # Returns an array of names from all OS records, and passes them each
1443
+ # to a block if one is given
1444
+ def all_names() # :yields: name
1445
+ names = []
1446
+
1447
+ @osmatches.each do |match|
1448
+ names << match.name
1449
+ yield match.name if block_given?
1450
+ end
1451
+
1452
+ names
1453
+ end
1454
+
1455
+ alias names all_names
1456
+
1457
+ # Returns the closed TCP port used for this OS Detection run
1458
+ def tcpport_closed
1459
+ return nil if @portsused.nil?
1460
+
1461
+ @portsused.each do |port|
1462
+ if port.proto == "tcp" and port.state == "closed"
1463
+ return port.num
1464
+ end
1465
+ end
1466
+
1467
+ nil
1468
+ end
1469
+
1470
+ # Returns the open TCP port used for this OS Detection run
1471
+ def tcpport_open
1472
+ return nil if @portsused.nil?
1473
+
1474
+ @portsused.each do |port|
1475
+ if port.proto == "tcp" and port.state == "open"
1476
+ return port.num
1477
+ end
1478
+ end
1479
+
1480
+ nil
1481
+ end
1482
+
1483
+ # Returns the closed UDP port used for this OS Detection run
1484
+ def udpport_closed
1485
+ return nil if @portsused.nil?
1486
+
1487
+ @portsused.each do |port|
1488
+ if port.proto == "udp" and port.state == "closed"
1489
+ return port.num
1490
+ end
1491
+ end
1492
+
1493
+ nil
1494
+ end
1495
+
1496
+ private
1497
+
1498
+ def initialize(os)
1499
+ parse(os)
1500
+ end
1501
+
1502
+ def parse(os)
1503
+ @portsused = []
1504
+ @osclasses = []
1505
+ @osmatches = []
1506
+
1507
+ return nil if os.nil?
1508
+
1509
+ os.each_element('portused') do |port|
1510
+ @portsused << PortUsed.new(port)
1511
+ end
1512
+
1513
+ os.each_element('osclass') do |osclass|
1514
+ @osclasses << OSClass.new(osclass)
1515
+ end
1516
+
1517
+ @osclasses.sort!.reverse!
1518
+
1519
+ os.each_element('osmatch') do |match|
1520
+ @osmatches << OSMatch.new(match)
1521
+ end
1522
+
1523
+ @osmatches.sort!.reverse!
1524
+
1525
+ fp = os.elements['osfingerprint']
1526
+ @fingerprint = fp.attributes['fingerprint'] if fp
1527
+ end
1528
+ end
1529
+
1530
+ class Nmap::Parser::Host::OS::PortUsed # :nodoc: all
1531
+ attr_reader :state, :proto, :num
1532
+
1533
+ private
1534
+
1535
+ def initialize(ports)
1536
+ parse(ports)
1537
+ end
1538
+
1539
+ def parse(ports)
1540
+ @state = ports.attributes['state']
1541
+ @proto = ports.attributes['proto']
1542
+ @num = ports.attributes['portid'].to_i
1543
+ end
1544
+ end
1545
+
1546
+ # Holds information for an individual OS class record
1547
+ class Nmap::Parser::Host::OS::OSClass
1548
+ # Device type, like "router" or "general purpose"
1549
+ attr_reader :ostype
1550
+ # Company that makes the OS, like "Apple" or "Microsoft"
1551
+ attr_reader :osvendor
1552
+ # Product name, like "Linux" or "Windows"
1553
+ attr_reader :osfamily
1554
+ # A more precise description, like "2.6.X" for Linux
1555
+ attr_reader :osgen
1556
+ # Accuracy of this information
1557
+ attr_reader :accuracy
1558
+
1559
+ # Compares accuracy
1560
+ def <=>(osclass)
1561
+ @accuracy <=> osclass.accuracy
1562
+ end
1563
+
1564
+ private
1565
+
1566
+ def initialize(osclass)
1567
+ parse(osclass)
1568
+ end
1569
+
1570
+ def parse(osclass)
1571
+ @ostype = osclass.attributes['type']
1572
+ @osvendor = osclass.attributes['vendor']
1573
+ @osfamily = osclass.attributes['osfamily']
1574
+ @osgen = osclass.attributes['osgen']
1575
+ @accuracy = osclass.attributes['accuracy'].to_i
1576
+ end
1577
+ end
1578
+
1579
+ # Holds information for an individual OS match record
1580
+ class Nmap::Parser::Host::OS::OSMatch
1581
+ # Operating System name
1582
+ attr_reader :name
1583
+ # Accuracy of this match
1584
+ attr_reader :accuracy
1585
+
1586
+ # Compares accuracy
1587
+ def <=>(osmatch)
1588
+ @accuracy <=> osmatch.accuracy
1589
+ end
1590
+
1591
+ private
1592
+
1593
+ def initialize(os)
1594
+ parse(os)
1595
+ end
1596
+
1597
+ def parse(os)
1598
+ @name = os.attributes['name']
1599
+ @accuracy = os.attributes['accuracy'].to_i
1600
+ end
1601
+ end
1602
+