arachni-reactor 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.md +29 -0
  4. data/README.md +79 -0
  5. data/Rakefile +53 -0
  6. data/lib/arachni/reactor.rb +679 -0
  7. data/lib/arachni/reactor/connection.rb +302 -0
  8. data/lib/arachni/reactor/connection/callbacks.rb +73 -0
  9. data/lib/arachni/reactor/connection/error.rb +114 -0
  10. data/lib/arachni/reactor/connection/peer_info.rb +92 -0
  11. data/lib/arachni/reactor/connection/tls.rb +107 -0
  12. data/lib/arachni/reactor/global.rb +26 -0
  13. data/lib/arachni/reactor/iterator.rb +251 -0
  14. data/lib/arachni/reactor/queue.rb +91 -0
  15. data/lib/arachni/reactor/tasks.rb +107 -0
  16. data/lib/arachni/reactor/tasks/base.rb +59 -0
  17. data/lib/arachni/reactor/tasks/delayed.rb +35 -0
  18. data/lib/arachni/reactor/tasks/one_off.rb +30 -0
  19. data/lib/arachni/reactor/tasks/periodic.rb +60 -0
  20. data/lib/arachni/reactor/tasks/persistent.rb +31 -0
  21. data/lib/arachni/reactor/version.rb +15 -0
  22. data/spec/arachni/reactor/connection/tls_spec.rb +332 -0
  23. data/spec/arachni/reactor/connection_spec.rb +58 -0
  24. data/spec/arachni/reactor/iterator_spec.rb +203 -0
  25. data/spec/arachni/reactor/queue_spec.rb +91 -0
  26. data/spec/arachni/reactor/tasks/base.rb +8 -0
  27. data/spec/arachni/reactor/tasks/delayed_spec.rb +54 -0
  28. data/spec/arachni/reactor/tasks/one_off_spec.rb +51 -0
  29. data/spec/arachni/reactor/tasks/periodic_spec.rb +40 -0
  30. data/spec/arachni/reactor/tasks/persistent_spec.rb +39 -0
  31. data/spec/arachni/reactor/tasks_spec.rb +136 -0
  32. data/spec/arachni/reactor_spec.rb +20 -0
  33. data/spec/arachni/reactor_tls_spec.rb +20 -0
  34. data/spec/spec_helper.rb +16 -0
  35. data/spec/support/fixtures/handlers/echo_client.rb +34 -0
  36. data/spec/support/fixtures/handlers/echo_client_tls.rb +10 -0
  37. data/spec/support/fixtures/handlers/echo_server.rb +12 -0
  38. data/spec/support/fixtures/handlers/echo_server_tls.rb +8 -0
  39. data/spec/support/fixtures/pems/cacert.pem +37 -0
  40. data/spec/support/fixtures/pems/client/cert.pem +37 -0
  41. data/spec/support/fixtures/pems/client/foo-cert.pem +39 -0
  42. data/spec/support/fixtures/pems/client/foo-key.pem +51 -0
  43. data/spec/support/fixtures/pems/client/key.pem +51 -0
  44. data/spec/support/fixtures/pems/server/cert.pem +37 -0
  45. data/spec/support/fixtures/pems/server/key.pem +51 -0
  46. data/spec/support/helpers/paths.rb +23 -0
  47. data/spec/support/helpers/utilities.rb +117 -0
  48. data/spec/support/lib/server_option_parser.rb +29 -0
  49. data/spec/support/lib/servers.rb +133 -0
  50. data/spec/support/lib/servers/runner.rb +13 -0
  51. data/spec/support/servers/echo.rb +14 -0
  52. data/spec/support/servers/echo_tls.rb +22 -0
  53. data/spec/support/servers/echo_unix.rb +14 -0
  54. data/spec/support/servers/echo_unix_tls.rb +22 -0
  55. data/spec/support/shared/connection.rb +778 -0
  56. data/spec/support/shared/reactor.rb +785 -0
  57. data/spec/support/shared/task.rb +21 -0
  58. metadata +141 -0
@@ -0,0 +1,92 @@
1
+ =begin
2
+
3
+ This file is part of the Arachni::Reactor project and may be subject to
4
+ redistribution and commercial restrictions. Please see the Arachni::Reactor
5
+ web site for more information on licensing and terms of use.
6
+
7
+ =end
8
+
9
+ module Arachni
10
+ class Reactor
11
+ class Connection
12
+
13
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
14
+ module PeerInfo
15
+
16
+ # @param [Bool] resolve
17
+ # Resolve IP address to hostname.
18
+ # @return [Hash]
19
+ # Peer address information:
20
+ #
21
+ # * IP socket:
22
+ # * Without `resolve`:
23
+ #
24
+ # {
25
+ # protocol: 'AF_INET',
26
+ # port: 10314,
27
+ # hostname: '127.0.0.1',
28
+ # ip_address: '127.0.0.1'
29
+ # }
30
+ #
31
+ # * With `resolve`:
32
+ #
33
+ # {
34
+ # protocol: 'AF_INET',
35
+ # port: 10314,
36
+ # hostname: 'localhost',
37
+ # ip_address: '127.0.0.1'
38
+ # }
39
+ #
40
+ # * UNIX-domain socket:
41
+ #
42
+ # {
43
+ # protocol: 'AF_UNIX',
44
+ # path: '/tmp/my-socket'
45
+ # }
46
+ def peer_address_info( resolve = false )
47
+ if Arachni::Reactor.supports_unix_sockets? && to_io.is_a?( UNIXSocket )
48
+ {
49
+ protocol: to_io.peeraddr.first,
50
+ path: to_io.path
51
+ }
52
+ else
53
+ protocol, port, hostname, ip_address = to_io.peeraddr( resolve )
54
+
55
+ {
56
+ protocol: protocol,
57
+ port: port,
58
+ hostname: hostname,
59
+ ip_address: ip_address
60
+ }
61
+ end
62
+ end
63
+
64
+ # @return [String]
65
+ # Peer's IP address or socket path.
66
+ def peer_address
67
+ peer_ip_address || peer_address_info[:path]
68
+ end
69
+
70
+ # @return [String]
71
+ # Peer's IP address.
72
+ def peer_ip_address
73
+ peer_address_info[:ip_address]
74
+ end
75
+
76
+ # @return [String]
77
+ # Peer's hostname.
78
+ def peer_hostname
79
+ peer_address_info(true)[:hostname]
80
+ end
81
+
82
+ # @return [String]
83
+ # Peer's port.
84
+ def peer_port
85
+ peer_address_info[:port]
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,107 @@
1
+ =begin
2
+
3
+ This file is part of the Arachni::Reactor project and may be subject to
4
+ redistribution and commercial restrictions. Please see the Arachni::Reactor
5
+ web site for more information on licensing and terms of use.
6
+
7
+ =end
8
+
9
+ module Arachni
10
+ class Reactor
11
+ class Connection
12
+
13
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
14
+ module TLS
15
+
16
+ # Converts the {#socket} to an SSL one.
17
+ #
18
+ # @param [Hash] options
19
+ # @option [String] :certificate
20
+ # Path to a PEM certificate.
21
+ # @option [String] :private_key
22
+ # Path to a PEM private key.
23
+ # @option [String] :ca
24
+ # Path to a PEM CA.
25
+ def start_tls( options = {} )
26
+ if @socket.is_a? OpenSSL::SSL::SSLSocket
27
+ @ssl_context = @socket.context
28
+ return
29
+ end
30
+
31
+ @ssl_context = OpenSSL::SSL::SSLContext.new
32
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
33
+
34
+ if options[:certificate] && options[:private_key]
35
+
36
+ @ssl_context.cert =
37
+ OpenSSL::X509::Certificate.new( File.open( options[:certificate] ) )
38
+ @ssl_context.key =
39
+ OpenSSL::PKey::RSA.new( File.open( options[:private_key] ) )
40
+
41
+ @ssl_context.ca_file = options[:ca]
42
+ @ssl_context.verify_mode =
43
+ OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
44
+
45
+ elsif @role == :server
46
+ @ssl_context.key = OpenSSL::PKey::RSA.new( 2048 )
47
+ @ssl_context.cert = OpenSSL::X509::Certificate.new
48
+ @ssl_context.cert.subject = OpenSSL::X509::Name.new( [['CN', 'localhost']] )
49
+ @ssl_context.cert.issuer = @ssl_context.cert.subject
50
+ @ssl_context.cert.public_key = @ssl_context.key
51
+ @ssl_context.cert.not_before = Time.now
52
+ @ssl_context.cert.not_after = Time.now + 60 * 60 * 24
53
+
54
+ @ssl_context.cert.sign( @ssl_context.key, OpenSSL::Digest::SHA1.new )
55
+ end
56
+
57
+ if @role == :server
58
+ @socket = OpenSSL::SSL::SSLServer.new( @socket, @ssl_context )
59
+ else
60
+ @socket = OpenSSL::SSL::SSLSocket.new( @socket, @ssl_context )
61
+ @socket.sync_close = true
62
+
63
+ begin
64
+ @socket.connect_nonblock
65
+ rescue IO::WaitReadable, IO::WaitWritable
66
+ end
67
+ end
68
+
69
+ @socket
70
+ end
71
+
72
+ private
73
+
74
+ # Accepts a new SSL client connection.
75
+ #
76
+ # @return [OpenSSL::SSL::SSLSocket, nil]
77
+ # New connection or `nil` if the socket isn't ready to accept new
78
+ # connections yet.
79
+ #
80
+ # @private
81
+ def socket_accept
82
+ socket = nil
83
+ begin
84
+ socket = to_io.accept_nonblock
85
+ rescue IO::WaitReadable, IO::WaitWritable
86
+ return
87
+ end
88
+
89
+ socket = OpenSSL::SSL::SSLSocket.new(
90
+ socket,
91
+ @ssl_context
92
+ )
93
+ socket.sync_close = true
94
+
95
+ begin
96
+ socket.accept_nonblock
97
+ rescue IO::WaitReadable, IO::WaitWritable
98
+ end
99
+
100
+ socket
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,26 @@
1
+ =begin
2
+
3
+ This file is part of the Arachni::Reactor project and may be subject to
4
+ redistribution and commercial restrictions. Please see the Arachni::Reactor
5
+ web site for more information on licensing and terms of use.
6
+
7
+ =end
8
+
9
+ require 'singleton'
10
+
11
+ module Arachni
12
+ class Reactor
13
+
14
+ # **Do not use directly!**
15
+ #
16
+ # Use the {Reactor} class methods to manage a globally accessible {Reactor}
17
+ # instance.
18
+ #
19
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
20
+ # @private
21
+ class Global < Reactor
22
+ include Singleton
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,251 @@
1
+
2
+ =begin
3
+
4
+ This file is part of the Arachni::Reactor project and may be subject to
5
+ redistribution and commercial restrictions. Please see the Arachni::Reactor
6
+ web site for more information on licensing and terms of use.
7
+
8
+ =end
9
+
10
+ module Arachni
11
+ class Reactor
12
+
13
+ # @note Pretty much an `EventMachine::Iterator` rip-off.
14
+ #
15
+ # A simple iterator for concurrent asynchronous work.
16
+ #
17
+ # Unlike Ruby's built-in iterators, the end of the current iteration cycle is
18
+ # signaled manually, instead of happening automatically after the yielded block
19
+ # finishes executing.
20
+ #
21
+ # @example Direct initialization.
22
+ #
23
+ # Iterator.new( reactor, 0..10 ).each { |num, iterator| iterator.next }
24
+ #
25
+ # @example Reactor factory.
26
+ #
27
+ # reactor.create_iterator( 0..10 ).each { |num, iterator| iterator.next }
28
+ #
29
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
30
+ class Iterator
31
+
32
+ # @return [Reactor]
33
+ attr_reader :reactor
34
+
35
+ # @return [Integer]
36
+ attr_reader :concurrency
37
+
38
+ # @example Create a new parallel async iterator with specified concurrency.
39
+ #
40
+ # i = Iterator.new( reactor, 1..100, 10 )
41
+ #
42
+ # @param [Reactor] reactor
43
+ # @param [#to_a] list
44
+ # List to iterate.
45
+ # @param [Integer] concurrency
46
+ # Parallel workers to spawn.
47
+ def initialize( reactor, list, concurrency = 1 )
48
+ raise ArgumentError, 'argument must be an array' unless list.respond_to?(:to_a)
49
+ raise ArgumentError, 'concurrency must be bigger than zero' unless concurrency > 0
50
+
51
+ @reactor = reactor
52
+ @list = list.to_a.dup
53
+ @concurrency = concurrency
54
+
55
+ @started = false
56
+ @ended = false
57
+ end
58
+
59
+ # Change the concurrency of this iterator. Workers will automatically be
60
+ # spawned or destroyed to accommodate the new concurrency level.
61
+ #
62
+ # @param [Integer] val
63
+ # New concurrency.
64
+ def concurrency=( val )
65
+ old = @concurrency
66
+ @concurrency = val
67
+
68
+ spawn_workers if val > old && @started && !@ended
69
+
70
+ val
71
+ end
72
+
73
+ # @example Iterate over a set of items using the specified block or proc.
74
+ #
75
+ # Iterator.new( reactor, 1..100 ).each do |num, iterator|
76
+ # puts num
77
+ # iterator.next
78
+ # end
79
+ #
80
+ # @example An optional second proc is invoked after the iteration is complete.
81
+ #
82
+ # Iterator.new( reactor, 1..100 ).each(
83
+ # proc { |num, iterator| iterator.next },
84
+ # proc { puts 'all done' }
85
+ # )
86
+ def each( foreach = nil, after = nil, &block )
87
+ raise ArgumentError, 'Proc or Block required for iteration.' unless foreach ||= block
88
+ raise RuntimeError, 'Cannot iterate over an iterator more than once.' if @started or @ended
89
+
90
+ @started = true
91
+ @pending = 0
92
+ @workers = 0
93
+
94
+ all_done = proc do
95
+ after.call if after && @ended && @pending == 0
96
+ end
97
+
98
+ @process_next = proc do
99
+ if @ended || @workers > @concurrency
100
+ @workers -= 1
101
+ else
102
+ if @list.empty?
103
+ @ended = true
104
+ @workers -= 1
105
+
106
+ all_done.call
107
+ else
108
+ item = @list.shift
109
+ @pending += 1
110
+
111
+ is_done = false
112
+ on_done = proc do
113
+ raise RuntimeError, 'Already completed this iteration.' if is_done
114
+ is_done = true
115
+
116
+ @pending -= 1
117
+
118
+ if @ended
119
+ all_done.call
120
+ else
121
+ @reactor.next_tick(&@process_next)
122
+ end
123
+ end
124
+
125
+ class << on_done
126
+ alias :next :call
127
+ end
128
+
129
+ foreach.call(item, on_done)
130
+ end
131
+ end
132
+ end
133
+
134
+ spawn_workers
135
+
136
+ self
137
+ end
138
+
139
+ # @example Collect the results of an asynchronous iteration into an array.
140
+ #
141
+ # Iterator.new( reactor, %w(one two three four), 2 ).map(
142
+ # proc do |string, iterator|
143
+ # iterator.return( string.size )
144
+ # end,
145
+ # proc do |results|
146
+ # p results
147
+ # end
148
+ # )
149
+ #
150
+ # @param [Proc] foreach
151
+ # `Proc` to handle each entry.
152
+ # @param [Proc] after
153
+ # `Proc` to handle the results.
154
+ def map( foreach, after )
155
+ index = 0
156
+
157
+ inject( [],
158
+ proc do |results, item, iter|
159
+ i = index
160
+ index += 1
161
+
162
+ is_done = false
163
+ on_done = proc do |res|
164
+ raise RuntimeError, 'Already returned a value for this iteration.' if is_done
165
+ is_done = true
166
+
167
+ results[i] = res
168
+ iter.return(results)
169
+ end
170
+
171
+ class << on_done
172
+ alias :return :call
173
+ def next
174
+ raise NoMethodError, 'Must call #return on a map iterator.'
175
+ end
176
+ end
177
+
178
+ foreach.call( item, on_done )
179
+ end,
180
+
181
+ proc do |results|
182
+ after.call(results)
183
+ end
184
+ )
185
+ end
186
+
187
+ # @example Inject the results of an asynchronous iteration onto a given object.
188
+ #
189
+ # Iterator.new( reactor, %w(one two three four), 2 ).inject( {},
190
+ # proc do |hash, string, iterator|
191
+ # hash.merge!( string => string.size )
192
+ # iterator.return( hash )
193
+ # end,
194
+ # proc do |results|
195
+ # p results
196
+ # end
197
+ # )
198
+ #
199
+ # @param [Object] object
200
+ # @param [Proc] foreach
201
+ # `Proc` to handle each entry.
202
+ # @param [Proc] after
203
+ # `Proc` to handle the results.
204
+ def inject( object, foreach, after )
205
+ each(
206
+ proc do |item, iter|
207
+ is_done = false
208
+ on_done = proc do |res|
209
+ raise RuntimeError, 'Already returned a value for this iteration.' if is_done
210
+ is_done = true
211
+
212
+ object = res
213
+ iter.next
214
+ end
215
+
216
+ class << on_done
217
+ alias :return :call
218
+ def next
219
+ raise NoMethodError, 'Must call #return on an inject iterator.'
220
+ end
221
+ end
222
+
223
+ foreach.call( object, item, on_done )
224
+ end,
225
+
226
+ proc do
227
+ after.call(object)
228
+ end
229
+ )
230
+ end
231
+
232
+ private
233
+
234
+ # Spawn workers to consume items from the iterator's enumerator based on the
235
+ # current concurrency level.
236
+ def spawn_workers
237
+ @reactor.next_tick( &proc { |task|
238
+ next if @workers >= @concurrency || @ended
239
+
240
+ @workers += 1
241
+ @process_next.call
242
+ @reactor.next_tick(&task.to_proc)
243
+ })
244
+
245
+ nil
246
+ end
247
+
248
+ end
249
+
250
+ end
251
+ end