nmap-parser 0.3

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.
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
+