ronin-scanners 0.1.4 → 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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