ronin-scanners 0.1.4 → 1.0.0.pre1

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 (63) hide show
  1. data/.document +4 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +2 -0
  6. data/COPYING.txt +339 -0
  7. data/{History.txt → ChangeLog.md} +7 -7
  8. data/Gemfile +45 -0
  9. data/README.md +125 -0
  10. data/Rakefile +39 -14
  11. data/bin/ronin-scan-dork +20 -0
  12. data/bin/ronin-scan-nmap +20 -0
  13. data/bin/ronin-scan-proxies +20 -0
  14. data/bin/ronin-scan-spider +20 -0
  15. data/bin/ronin-scanner +20 -0
  16. data/bin/ronin-scanners +13 -5
  17. data/gemspec.yml +31 -0
  18. data/lib/ronin/database/migrations/scanners.rb +25 -0
  19. data/lib/ronin/database/migrations/scanners/1.0.0.rb +51 -0
  20. data/lib/ronin/scanners.rb +7 -5
  21. data/lib/ronin/scanners/dork.rb +173 -0
  22. data/lib/ronin/scanners/host_name_scanner.rb +67 -0
  23. data/lib/ronin/scanners/http_scanner.rb +195 -0
  24. data/lib/ronin/scanners/ip_scanner.rb +75 -0
  25. data/lib/ronin/scanners/nmap.rb +303 -5
  26. data/lib/ronin/scanners/{nikto/nikto.rb → proxies.rb} +11 -26
  27. data/lib/ronin/scanners/resolv_scanner.rb +73 -0
  28. data/lib/ronin/scanners/reverse_lookup_scanner.rb +76 -0
  29. data/lib/ronin/scanners/scanner.rb +371 -0
  30. data/lib/ronin/scanners/{nikto.rb → scanners.rb} +8 -5
  31. data/lib/ronin/scanners/site_map.rb +62 -0
  32. data/lib/ronin/scanners/spider.rb +117 -0
  33. data/lib/ronin/scanners/tcp_port_scanner.rb +72 -0
  34. data/lib/ronin/scanners/udp_port_scanner.rb +72 -0
  35. data/lib/ronin/scanners/url_scanner.rb +79 -0
  36. data/lib/ronin/scanners/version.rb +3 -4
  37. data/lib/ronin/ui/cli/commands/scan/dork.rb +39 -0
  38. data/lib/ronin/ui/cli/commands/scan/nmap.rb +105 -0
  39. data/lib/ronin/ui/cli/commands/scan/proxies.rb +82 -0
  40. data/lib/ronin/ui/cli/commands/scan/spider.rb +71 -0
  41. data/lib/ronin/ui/cli/commands/scanner.rb +43 -0
  42. data/lib/ronin/ui/cli/scanner_command.rb +118 -0
  43. data/ronin-scanners.gemspec +60 -0
  44. data/spec/scanners/host_name_scanner_spec.rb +24 -0
  45. data/spec/scanners/ip_scanner_spec.rb +24 -0
  46. data/spec/scanners/resolv_scanner_spec.rb +26 -0
  47. data/spec/scanners/reverse_lookup_scanner_spec.rb +26 -0
  48. data/spec/scanners/scanner_spec.rb +89 -0
  49. data/spec/scanners/scanners_spec.rb +9 -0
  50. data/spec/scanners/tcp_port_scanner_spec.rb +27 -0
  51. data/spec/scanners/udp_port_scanner_spec.rb +27 -0
  52. data/spec/scanners/url_scanner_spec.rb +37 -0
  53. data/spec/spec_helper.rb +4 -3
  54. metadata +261 -116
  55. data.tar.gz.sig +0 -1
  56. data/Manifest.txt +0 -16
  57. data/README.txt +0 -106
  58. data/lib/ronin/scanners/nikto/nikto_task.rb +0 -183
  59. data/lib/ronin/scanners/nmap/nmap.rb +0 -74
  60. data/lib/ronin/scanners/nmap/nmap_task.rb +0 -290
  61. data/spec/scanners_spec.rb +0 -11
  62. data/tasks/spec.rb +0 -9
  63. metadata.gz.sig +0 -0
@@ -0,0 +1,67 @@
1
+ #
2
+ # Ronin Scanners - A Ruby library for Ronin that provides Ruby interfaces to
3
+ # various third-party security scanners.
4
+ #
5
+ # Copyright (c) 2008-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # This program is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation; either version 2 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
+ #
21
+
22
+ require 'ronin/scanners/scanner'
23
+ require 'ronin/host_name'
24
+
25
+ module Ronin
26
+ module Scanners
27
+ #
28
+ # The {HostNameScanner} class represents scanners that yield host-name
29
+ # results and `HostName` resources.
30
+ #
31
+ class HostNameScanner < Scanner
32
+
33
+ protected
34
+
35
+ #
36
+ # Normalizes the host name.
37
+ #
38
+ # @param [Object] result
39
+ # The incoming host name.
40
+ #
41
+ # @return [String]
42
+ # The normalized host name.
43
+ #
44
+ # @since 1.0.0
45
+ #
46
+ def normalize_result(result)
47
+ result.to_s
48
+ end
49
+
50
+ #
51
+ # Queries or creates a new HostName resource for the result.
52
+ #
53
+ # @param [String] result
54
+ # The host name.
55
+ #
56
+ # @return [HostName]
57
+ # The HostName resource from the Database.
58
+ #
59
+ # @since 1.0.0
60
+ #
61
+ def new_resource(result)
62
+ HostName.first_or_new(:address => result)
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,195 @@
1
+ #
2
+ # Ronin Scanners - A Ruby library for Ronin that provides Ruby interfaces to
3
+ # various third-party security scanners.
4
+ #
5
+ # Copyright (c) 2008-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # This program is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation; either version 2 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
+ #
21
+
22
+ require 'ronin/scanners/url_scanner'
23
+ require 'ronin/network/http/http'
24
+
25
+ require 'net/http/persistent'
26
+
27
+ module Ronin
28
+ module Scanners
29
+ #
30
+ # The {HTTPScanner} class represents a scanner that performs
31
+ # HTTP Requests inorder to discover URLs.
32
+ #
33
+ # @since 1.0.0
34
+ #
35
+ class HTTPScanner < URLScanner
36
+
37
+ protected
38
+
39
+ #
40
+ # The [net-http-persistent](http://seattlerb.rubyforge.org/net-http-persistent/)
41
+ # client.
42
+ #
43
+ # @return [Net::HTTP::Persistent]
44
+ # The client.
45
+ #
46
+ # @see http://seattlerb.rubyforge.org/net-http-persistent/
47
+ #
48
+ # @api semipublic
49
+ #
50
+ def http
51
+ @http ||= Net::HTTP::Persistent.new
52
+ end
53
+
54
+ #
55
+ # Performs an HTTP Request through a persistent connection.
56
+ #
57
+ # @param [Hash] options
58
+ # HTTP options.
59
+ #
60
+ # @return [Net::HTTPResponse]
61
+ #
62
+ # @api semipublic
63
+ #
64
+ def http_request(options={})
65
+ options = Network::HTTP.expand_options(options)
66
+ uri_class = if options[:ssl]
67
+ URI::HTTPS
68
+ else
69
+ URI::HTTP
70
+ end
71
+
72
+ uri = uri_class.build(
73
+ :host => options[:host],
74
+ :port => options[:port],
75
+ )
76
+ request = Network::HTTP.request(options)
77
+
78
+ return http.request(uri,request)
79
+ end
80
+
81
+ #
82
+ # @see #http_request
83
+ #
84
+ # @api semipublic
85
+ #
86
+ def http_copy(options={})
87
+ http_request(options.merge(:method => :copy))
88
+ end
89
+
90
+ #
91
+ # @see #http_request
92
+ #
93
+ # @api semipublic
94
+ #
95
+ def http_delete(url,options={})
96
+ http_request(options.merge(:method => :delete))
97
+ end
98
+
99
+ def http_get(url,options={})
100
+ http_request(options.merge(:method => :get))
101
+ end
102
+
103
+ #
104
+ # @see #http_request
105
+ #
106
+ # @api semipublic
107
+ #
108
+ def http_head(url,options={})
109
+ http_request(options.merge(:method => :head))
110
+ end
111
+
112
+ #
113
+ # @see #http_request
114
+ #
115
+ # @api semipublic
116
+ #
117
+ def http_lock(url,options={})
118
+ http_request(options.merge(:method => :lock))
119
+ end
120
+
121
+ #
122
+ # @see #http_request
123
+ #
124
+ # @api semipublic
125
+ #
126
+ def http_mkcol(url,options={})
127
+ http_request(options.merge(:method => :mkcol))
128
+ end
129
+
130
+ #
131
+ # @see #http_request
132
+ #
133
+ # @api semipublic
134
+ #
135
+ def http_move(url,options={})
136
+ http_request(options.merge(:method => :move))
137
+ end
138
+
139
+ #
140
+ # @see #http_request
141
+ #
142
+ # @api semipublic
143
+ #
144
+ def http_options(url,options={})
145
+ http_request(options.merge(:method => :options))
146
+ end
147
+
148
+ #
149
+ # @see #http_request
150
+ #
151
+ # @api semipublic
152
+ #
153
+ def http_post(url,options={})
154
+ http_request(options.merge(:method => :post))
155
+ end
156
+
157
+ #
158
+ # @see #http_request
159
+ #
160
+ # @api semipublic
161
+ #
162
+ def http_prop_find(url,options={})
163
+ http_request(options.merge(:method => :prop_find))
164
+ end
165
+
166
+ #
167
+ # @see #http_request
168
+ #
169
+ # @api semipublic
170
+ #
171
+ def http_prop_patch(url,options={})
172
+ http_request(options.merge(:method => :prop_patch))
173
+ end
174
+
175
+ #
176
+ # @see #http_request
177
+ #
178
+ # @api semipublic
179
+ #
180
+ def http_trace(url,options={})
181
+ http_request(options.merge(:method => :trace))
182
+ end
183
+
184
+ #
185
+ # @see #http_request
186
+ #
187
+ # @api semipublic
188
+ #
189
+ def http_unlock(url,options={})
190
+ http_request(options.merge(:method => :unlock))
191
+ end
192
+
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,75 @@
1
+ #
2
+ # Ronin Scanners - A Ruby library for Ronin that provides Ruby interfaces to
3
+ # various third-party security scanners.
4
+ #
5
+ # Copyright (c) 2008-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # This program is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation; either version 2 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
+ #
21
+
22
+ require 'ronin/scanners/scanner'
23
+ require 'ronin/extensions/ip_addr'
24
+ require 'ronin/ip_address'
25
+
26
+ module Ronin
27
+ module Scanners
28
+ #
29
+ # The {IPScanner} class represents scanners that yield `IPAddr` results
30
+ # and `IPAddress` resources.
31
+ #
32
+ class IPScanner < Scanner
33
+
34
+ protected
35
+
36
+ #
37
+ # Normalizes the result.
38
+ #
39
+ # @param [String, IPAddr] result
40
+ # The incoming result.
41
+ #
42
+ # @return [IPAddr]
43
+ # The normalized IP Address.
44
+ #
45
+ # @since 1.0.0
46
+ #
47
+ def normalize_result(result)
48
+ unless result.kind_of?(IPAddr)
49
+ begin
50
+ IPAddr.new(result.to_s)
51
+ rescue ArgumentError
52
+ end
53
+ else
54
+ result
55
+ end
56
+ end
57
+
58
+ #
59
+ # Queries or creates a new IPAddress resource for the given result.
60
+ #
61
+ # @param [IPAddr] result
62
+ # The ip address.
63
+ #
64
+ # @return [IPAddress]
65
+ # The IPAddress resource from the Database.
66
+ #
67
+ # @since 1.0.0
68
+ #
69
+ def new_resource(result)
70
+ IPAddress.first_or_new(:address => result)
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -1,9 +1,8 @@
1
1
  #
2
- #--
3
2
  # Ronin Scanners - A Ruby library for Ronin that provides Ruby interfaces to
4
3
  # various third-party security scanners.
5
4
  #
6
- # Copyright (c) 2008-2009 Hal Brodigan (postmodern.mod3 at gmail.com)
5
+ # Copyright (c) 2008-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
7
6
  #
8
7
  # This program is free software; you can redistribute it and/or modify
9
8
  # it under the terms of the GNU General Public License as published by
@@ -18,8 +17,307 @@
18
17
  # You should have received a copy of the GNU General Public License
19
18
  # along with this program; if not, write to the Free Software
20
19
  # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
- #++
22
20
  #
23
21
 
24
- require 'ronin/scanners/nmap/nmap_task'
25
- require 'ronin/scanners/nmap/nmap'
22
+ require 'ronin/scanners/scanner'
23
+ require 'ronin/ip_address'
24
+ require 'ronin/port'
25
+ require 'ronin/service'
26
+
27
+ require 'nmap/task'
28
+ require 'nmap/program'
29
+ require 'nmap/xml'
30
+ require 'tempfile'
31
+
32
+ module Ronin
33
+ module Scanners
34
+ #
35
+ # {Nmap} scans the open-ports found on the targeted IP addresses,
36
+ # using the Nmap security / port scanner.
37
+ #
38
+ class Nmap < Scanner
39
+
40
+ # The hosts which will be scanned.
41
+ parameter :targets, :default => [],
42
+ :description => 'The hosts to scan with Nmap'
43
+
44
+ # The hosts or ranges to exclude from the scan.
45
+ parameter :exclude, :description => 'The hosts to exclude'
46
+
47
+ # The ports or port ranges which will be scanned.
48
+ parameter :ports, :description => 'The ports to scan'
49
+
50
+ # Specifies that a Ping Scan will be performed.
51
+ parameter :ping_scan, :default => false
52
+
53
+ # Specifies that a Connect Scan will be performed.
54
+ parameter :connect_scan, :default => true
55
+
56
+ # Specifies that a TCP SYN scan will be performed.
57
+ parameter :syn_scan, :default => false
58
+
59
+ # Specifies that a TCP ACK scan will be performed.
60
+ parameter :ack_scan, :default => false
61
+
62
+ # Specifies that a TCP NULL scan will be performed.
63
+ parameter :null_scan, :default => false
64
+
65
+ # Specifies that a TCP FIN scan will be performed.
66
+ parameter :fin_scan, :default => false
67
+
68
+ # Specifies that a TCP XMAS scan will be performed.
69
+ parameter :xmas_scan, :default => false
70
+
71
+ # Specifies that a UDP scan will be performed.
72
+ parameter :udp_scan, :default => false
73
+
74
+ # Specifies that a Service scan will be performed.
75
+ parameter :service_scan, :default => true
76
+
77
+ # Specifies that an Idle Scan will be performed.
78
+ parameter :idle_scan, :default => false
79
+
80
+ # Specifies that a Window Scan will be performed.
81
+ parameter :window_scan, :default => false
82
+
83
+ # Enables paranoid-timing for nmap (`-T0`)
84
+ parameter :paranoid_timing, :default => false
85
+
86
+ # Enables sneaky-timing for nmap (`-T1`)
87
+ parameter :sneaky_timing, :default => false
88
+
89
+ # Enables polite-timing for nmap (`-T2`)
90
+ parameter :polite_timing, :default => false
91
+
92
+ # Enables normal-timing for nmap (`-T3`)
93
+ parameter :normal_timing, :default => false
94
+
95
+ # Enables aggressive-timing for nmap (`-T4`)
96
+ parameter :aggressive_timing, :default => false
97
+
98
+ # Enables insane-timing for nmap (`-T5`)
99
+ parameter :insane_timing, :default => false
100
+
101
+ # Specifies whether to resolve the IP Addresses.
102
+ parameter :dns, :default => true
103
+
104
+ # Specifies whether to enable verbose output
105
+ parameter :verbose, :default => false
106
+
107
+ # The input file to read hosts/ports from
108
+ parameter :import_xml, :description => 'XML Scan file to import'
109
+
110
+ # The output file to write hosts/ports to
111
+ parameter :output, :description => 'XML Scan output file'
112
+
113
+ protected
114
+
115
+ #
116
+ # Populates options to call `nmap` with.
117
+ #
118
+ # @yield [nmap]
119
+ # If a block is given, it will be passed the nmap options.
120
+ #
121
+ # @yieldparam [Nmap::Task] nmap
122
+ # The nmap options.
123
+ #
124
+ # @return [Nmap::Task]
125
+ # The populated nmap options.
126
+ #
127
+ # @since 1.0.0
128
+ #
129
+ def nmap_options
130
+ nmap = ::Nmap::Task.new
131
+ nmap.targets = self.targets
132
+
133
+ nmap.exclude = self.exclude if self.exclude
134
+ nmap.ports = self.ports if self.ports
135
+
136
+ nmap.paranoid_timing = self.paranoid_timing
137
+ nmap.sneaky_timing = self.sneaky_timing
138
+ nmap.polite_timing = self.polite_timing
139
+ nmap.normal_timing = self.normal_timing
140
+ nmap.aggressive_timing = self.aggressive_timing
141
+ nmap.insane_timing = self.insane_timing
142
+
143
+ if self.ping_scan
144
+ nmap.ping = self.ping_scan
145
+ else
146
+ nmap.connect_scan = self.connect_scan
147
+ nmap.syn_scan = self.syn_scan
148
+ nmap.ack_scan = self.ack_scan
149
+ nmap.fin_scan = self.fin_scan
150
+ nmap.null_scan = self.null_scan
151
+ nmap.xmas_scan = self.xmas_scan
152
+ nmap.udp_scan = self.udp_scan
153
+ nmap.service_scan = self.service_scan
154
+ nmap.idle_scan = self.idle_scan
155
+ nmap.window_scan = self.window_scan
156
+ end
157
+
158
+ if self.dns? then nmap.enable_dns = true
159
+ else nmap.disable_dns = true
160
+ end
161
+
162
+ nmap.verbose = self.verbose
163
+
164
+ return nmap
165
+ end
166
+
167
+ #
168
+ # Sets up the scan output file for nmap.
169
+ #
170
+ # @yield [output]
171
+ # The block will be passed the output file.
172
+ #
173
+ # @yieldparam [String] output
174
+ # The path of the output file.
175
+ #
176
+ # @since 1.0.0
177
+ #
178
+ def nmap_output
179
+ if self.output
180
+ yield self.output
181
+ else
182
+ Tempfile.open('ronin_scanners_nmap') do |tempfile|
183
+ yield tempfile.path
184
+ end
185
+ end
186
+ end
187
+
188
+ #
189
+ # Performs a nmap scan and passes the scanned hosts to scanner rules.
190
+ #
191
+ # @yield [host]
192
+ # Every host that nmap scanned, will be passed to the given block.
193
+ #
194
+ # @yieldparam [Nmap::Host] host
195
+ # A host from the nmap scan.
196
+ #
197
+ # @see http://rubydoc.info/gems/ruby-nmap/Nmap/Host
198
+ #
199
+ # @since 1.0.0
200
+ #
201
+ def scan(&block)
202
+ each_host = lambda { |path|
203
+ ::Nmap::XML.new(path).each_host(&block)
204
+ }
205
+
206
+ if self.import_xml
207
+ each_host.call(self.import_xml)
208
+ else
209
+ nmap_output do |path|
210
+ options = nmap_options
211
+ options.xml = path
212
+
213
+ nmap = ::Nmap::Program.find()
214
+ nmap.run_task(options)
215
+
216
+ each_host.call(path)
217
+ end
218
+ end
219
+ end
220
+
221
+ #
222
+ # Creates a new IP Address from a scanned host result.
223
+ #
224
+ # @param [Nmap::Host] host
225
+ # The scanned host.
226
+ #
227
+ # @return [IPAddress]
228
+ # The IP Address resource.
229
+ #
230
+ # @since 1.0.0
231
+ #
232
+ def new_ip(host)
233
+ # if the host does not have an ip, then skip it
234
+ return nil unless host.ip
235
+
236
+ ip_version, ip_address = if host.ipv6 then [6, host.ipv6]
237
+ elsif host.ipv4 then [4, host.ipv4]
238
+ end
239
+
240
+ ip = IPAddress.first_or_new(
241
+ :version => ip_version,
242
+ :address => ip_address
243
+ )
244
+
245
+ if host.mac
246
+ # fill in the MAC address
247
+ ip.mac_addresses << MACAddress.first_or_new(:address => host.mac)
248
+ end
249
+
250
+ # fill in the host names
251
+ host.each_hostname do |name|
252
+ ip.host_names << HostName.first_or_new(:address => name)
253
+ end
254
+
255
+ return ip
256
+ end
257
+
258
+ #
259
+ # Creates a new port from a scanned open port.
260
+ #
261
+ # @param [Nmap::Port] open_port
262
+ # The scanned open port.
263
+ #
264
+ # @return [Port]
265
+ # The port resource.
266
+ #
267
+ # @since 1.0.0
268
+ #
269
+ def new_port(open_port)
270
+ Port.first_or_new(
271
+ :protocol => open_port.protocol.to_s,
272
+ :number => open_port.number
273
+ )
274
+ end
275
+
276
+ #
277
+ # Creates a new service from the scanned open port.
278
+ #
279
+ # @param [Nmap::Port] open_port
280
+ # The scanned open port.
281
+ #
282
+ # @return [Service]
283
+ # The new service.
284
+ #
285
+ # @since 1.0.0
286
+ #
287
+ def new_service(open_port)
288
+ if open_port.service
289
+ Service.first_or_new(:name => open_port.service)
290
+ end
291
+ end
292
+
293
+ #
294
+ # Queries or creates an IPAddress resource from the given host.
295
+ #
296
+ # @param [Nmap::Host] result
297
+ # The host scanned by `nmap`.
298
+ #
299
+ # @return [IPAddress]
300
+ # The IPAddress resource from the Database.
301
+ #
302
+ # @since 1.0.0
303
+ #
304
+ def new_resource(result)
305
+ return nil unless (ip = new_ip(result))
306
+
307
+ # fill in the open ports
308
+ result.each_open_port do |open_port|
309
+ port = new_port(open_port)
310
+ service = new_service(open_port)
311
+
312
+ # find or create a new open port
313
+ new_open_port = ip.open_ports.first_or_new(:port => port)
314
+ new_open_port.last_scanned_at = Time.now
315
+ new_open_port.service = service
316
+ end
317
+
318
+ return ip
319
+ end
320
+
321
+ end
322
+ end
323
+ end