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,302 @@
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_relative 'connection/error'
10
+ require_relative 'connection/peer_info'
11
+ require_relative 'connection/callbacks'
12
+ require_relative 'connection/tls'
13
+
14
+ module Arachni
15
+ class Reactor
16
+
17
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
18
+ class Connection
19
+ include PeerInfo
20
+ include Callbacks
21
+
22
+ # Maximum amount of data to be written or read at a time.
23
+ #
24
+ # We set this to the same max block size as the OpenSSL buffers because more
25
+ # than this tends to cause SSL errors and broken #select behavior --
26
+ # 1024 * 16 at the time of writing.
27
+ BLOCK_SIZE = OpenSSL::Buffering::BLOCK_SIZE
28
+
29
+ # @return [Socket]
30
+ # Ruby `Socket` associated with this connection.
31
+ attr_reader :socket
32
+
33
+ # @return [Reactor]
34
+ # Reactor associated with this connection.
35
+ attr_accessor :reactor
36
+
37
+ # @return [Symbol]
38
+ # `:client` or `:server`
39
+ attr_reader :role
40
+
41
+ # @return [Bool, nil]
42
+ # `true` when using a UNIX-domain socket, `nil` if no {#socket} is
43
+ # available, `false` otherwise.
44
+ def unix?
45
+ return if !to_io
46
+ return false if !Arachni::Reactor.supports_unix_sockets?
47
+ to_io.is_a?( UNIXServer ) || to_io.is_a?( UNIXSocket )
48
+ end
49
+
50
+ # @return [Bool]
51
+ # `true` when using an Internet socket, `nil` if no {#socket} is
52
+ # available, `false` otherwise.
53
+ def inet?
54
+ return if !to_io
55
+ to_io.is_a?( TCPServer ) || to_io.is_a?( TCPSocket ) || to_io.is_a?( Socket )
56
+ end
57
+
58
+ # @return [IO, nil]
59
+ # IO stream or `nil` if no {#socket} is available.
60
+ def to_io
61
+ return if !@socket
62
+ @socket.to_io
63
+ end
64
+
65
+ # @return [Bool]
66
+ # `true` if the connection is a server listener.
67
+ def listener?
68
+ return if !to_io
69
+ to_io.is_a?( TCPServer ) || (unix? && to_io.is_a?( UNIXServer ))
70
+ end
71
+
72
+ # @note The data will be buffered and sent in future {Reactor} ticks.
73
+ #
74
+ # @param [String] data
75
+ # Data to send to the peer.
76
+ def write( data )
77
+ @reactor.schedule do
78
+ write_buffer << data
79
+ end
80
+ end
81
+
82
+ # @return [Bool]
83
+ # `true` if the connection is {Reactor#attached?} to a {#reactor},
84
+ # `false` otherwise.
85
+ def attached?
86
+ @reactor && @reactor.attached?( self )
87
+ end
88
+
89
+ # @return [Bool]
90
+ # `true` if the connection is not {Reactor#attached?} to a {#reactor},
91
+ # `false` otherwise.
92
+ def detached?
93
+ !attached?
94
+ end
95
+
96
+ # @note Will first detach if already {#attached?}.
97
+ # @note Sets {#reactor}.
98
+ # @note Calls {#on_attach}.
99
+ #
100
+ # @param [Reactor] reactor
101
+ # {Reactor} to which to attach {Reactor#attach}.
102
+ #
103
+ # @return [Bool]
104
+ # `true` if the connection was attached, `nil` if the connection was
105
+ # already attached.
106
+ def attach( reactor )
107
+ return if reactor.attached?( self )
108
+ detach if attached?
109
+
110
+ reactor.attach self
111
+
112
+ true
113
+ end
114
+
115
+ # @note Removes {#reactor}.
116
+ # @note Calls {#on_detach}.
117
+ #
118
+ # {Reactor#detach Detaches} `self` from the {#reactor}.
119
+ #
120
+ # @return [Bool]
121
+ # `true` if the connection was detached, `nil` if the connection was
122
+ # already detached.
123
+ def detach
124
+ return if detached?
125
+
126
+ @reactor.detach self
127
+
128
+ true
129
+ end
130
+
131
+ # @note Will not call {#on_close}.
132
+ #
133
+ # Closes the connection and {#detach detaches} it from the {Reactor}.
134
+ def close_without_callback
135
+ return if closed?
136
+ @closed = true
137
+
138
+ if listener? && unix? && (path = to_io.path) && File.exist?( path )
139
+ File.delete( path )
140
+ end
141
+
142
+ if @socket
143
+ @socket.close rescue nil
144
+ end
145
+
146
+ detach
147
+
148
+ nil
149
+ end
150
+
151
+ # @return [Bool]
152
+ # `true` if the connection has been {#close closed}, `false` otherwise.
153
+ def closed?
154
+ !!@closed
155
+ end
156
+
157
+ # @return [Bool]
158
+ # `true` if the connection has {#write outgoing data} that have not
159
+ # yet been {#write written}, `false` otherwise.
160
+ def has_outgoing_data?
161
+ !write_buffer.empty?
162
+ end
163
+
164
+ # @note Will call {#on_close} right before closing the socket and detaching
165
+ # from the Reactor.
166
+ #
167
+ # Closes the connection and {Reactor#detach detaches} it from the {Reactor}.
168
+ #
169
+ # @param [Exception] reason
170
+ # Reason for the close.
171
+ def close( reason = nil )
172
+ return if closed?
173
+
174
+ on_close reason
175
+ close_without_callback
176
+ nil
177
+ end
178
+
179
+ # @note Will call {#on_write} every time any of the buffer is consumed,
180
+ # can be multiple times when performing partial writes.
181
+ # @note Will call {#on_flush} once all of the buffer has been consumed.
182
+ #
183
+ # Processes a `write` event for this connection.
184
+ #
185
+ # Consumes and writes {BLOCK_SIZE} amount of data from the the beginning of
186
+ # the {#write} buffer to the socket.
187
+ #
188
+ # @return [Integer]
189
+ # Amount of the buffer consumed.
190
+ #
191
+ # @private
192
+ def _write
193
+ chunk = write_buffer.slice( 0, BLOCK_SIZE )
194
+ total_written = 0
195
+
196
+ begin
197
+ Error.translate do
198
+ # Send out the buffer, **all** of it, or at least try to.
199
+ loop do
200
+ total_written += written = @socket.write_nonblock( chunk )
201
+ write_buffer.slice!( 0, written )
202
+
203
+ # Call #on_write every time any of the buffer is consumed.
204
+ on_write
205
+
206
+ break if written == chunk.size
207
+ chunk.slice!( 0, written )
208
+ end
209
+ end
210
+
211
+ # Not ready to read or write yet, we'll catch it on future Reactor ticks.
212
+ rescue IO::WaitReadable, IO::WaitWritable
213
+ end
214
+
215
+ if write_buffer.empty?
216
+ @socket.flush
217
+ on_flush
218
+ end
219
+
220
+ total_written
221
+ rescue Error => e
222
+ close e
223
+ end
224
+
225
+ # @note If this is a server {#listener?} it will delegate to {#accept}.
226
+ # @note If this is a normal socket it will read {BLOCK_SIZE} amount of data.
227
+ # and pass it to {#on_read}.
228
+ #
229
+ # Processes a `read` event for this connection.
230
+ #
231
+ # @private
232
+ def _read
233
+ return accept if listener?
234
+
235
+ Error.translate do
236
+ on_read @socket.read_nonblock( BLOCK_SIZE )
237
+ end
238
+
239
+ # Not ready to read or write yet, we'll catch it on future Reactor ticks.
240
+ rescue IO::WaitReadable, IO::WaitWritable
241
+ rescue Error => e
242
+ close e
243
+ end
244
+
245
+ # Accepts a new client connection.
246
+ #
247
+ # @return [Connection, nil]
248
+ # New connection or `nil` if the socket isn't ready to accept new
249
+ # connections yet.
250
+ #
251
+ # @private
252
+ def accept
253
+ return if !(accepted = socket_accept)
254
+
255
+ connection = @server_handler.call
256
+ connection.configure accepted, :server
257
+ @reactor.attach connection
258
+ connection
259
+ end
260
+
261
+ # @param [Socket] socket
262
+ # Ruby `Socket` associated with this connection.
263
+ # @param [Symbol] role
264
+ # `:server` or `:client`.
265
+ # @param [Block] server_handler
266
+ # Block that generates a handler as specified in {Reactor#listen}.
267
+ #
268
+ # @private
269
+ def configure( socket, role, server_handler = nil )
270
+ @socket = socket
271
+ @role = role
272
+ @server_handler = server_handler
273
+
274
+ on_connect
275
+
276
+ nil
277
+ end
278
+
279
+ private
280
+
281
+ def write_buffer
282
+ @write_buffer ||= ''
283
+ end
284
+
285
+ # Accepts a new client connection.
286
+ #
287
+ # @return [Socket, nil]
288
+ # New connection or `nil` if the socket isn't ready to accept new
289
+ # connections yet.
290
+ #
291
+ # @private
292
+ def socket_accept
293
+ begin
294
+ @socket.accept_nonblock
295
+ rescue IO::WaitReadable, IO::WaitWritable
296
+ end
297
+ end
298
+
299
+ end
300
+
301
+ end
302
+ end
@@ -0,0 +1,73 @@
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
+ # Callbacks to be invoked per event.
14
+ #
15
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
16
+ module Callbacks
17
+
18
+ # Called after the connection has been established.
19
+ #
20
+ # @abstract
21
+ def on_connect
22
+ end
23
+
24
+ # Called after the connection has been attached to a {#reactor}.
25
+ #
26
+ # @abstract
27
+ def on_attach
28
+ end
29
+
30
+ # Called right the connection is detached from the {#reactor}.
31
+ #
32
+ # @abstract
33
+ def on_detach
34
+ end
35
+
36
+ # @note If a connection could not be established no {#socket} may be
37
+ # available.
38
+ #
39
+ # Called when the connection gets closed.
40
+ #
41
+ # @param [Exception] reason
42
+ # Reason for the close.
43
+ #
44
+ # @abstract
45
+ def on_close( reason )
46
+ end
47
+
48
+ # Called when data are available.
49
+ #
50
+ # @param [String] data
51
+ # Incoming data.
52
+ #
53
+ # @abstract
54
+ def on_read( data )
55
+ end
56
+
57
+ # Called after each {#write} call.
58
+ #
59
+ # @abstract
60
+ def on_write
61
+ end
62
+
63
+ # Called after the {#write buffered data} have all been sent to the peer.
64
+ #
65
+ # @abstract
66
+ def on_flush
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,114 @@
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
+ # {Connection} error namespace.
14
+ #
15
+ # All {Connection} errors inherit from and live under it.
16
+ #
17
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
18
+ class Error < Arachni::Reactor::Error
19
+
20
+ class << self
21
+ # Captures Ruby exceptions and converts them to an appropriate
22
+ # subclass of {Error}.
23
+ #
24
+ # @param [Block] block Block to run.
25
+ def translate( &block )
26
+ block.call
27
+ rescue IOError, Errno::ENOTCONN => e
28
+ raise_with_proper_backtrace( e, Closed )
29
+ rescue SocketError, Errno::ENOENT => e
30
+ raise_with_proper_backtrace( e, HostNotFound )
31
+ rescue Errno::EPIPE => e
32
+ raise_with_proper_backtrace( e, BrokenPipe )
33
+ rescue Errno::ECONNREFUSED,
34
+ # JRuby throws Errno::EADDRINUSE when trying to connect to a
35
+ # non-existent server.
36
+ Errno::EADDRINUSE => e
37
+ raise_with_proper_backtrace( e, Refused )
38
+ rescue Errno::ECONNRESET => e
39
+ raise_with_proper_backtrace( e, Reset )
40
+ rescue Errno::EACCES => e
41
+ raise_with_proper_backtrace( e, Permission )
42
+
43
+ # Catch and forward these before handling OpenSSL::OpenSSLError because
44
+ # all SSL errors inherit from it, including OpenSSL::SSL::SSLErrorWaitReadable
45
+ # and OpenSSL::SSL::SSLErrorWaitWritable which also inherit from
46
+ # IO::WaitReadable and IO::WaitWritable and need special treatment.
47
+ rescue IO::WaitReadable, IO::WaitWritable, Errno::EINPROGRESS
48
+ raise
49
+
50
+ # We're mainly interested in translating SSL handshake errors but there
51
+ # aren't any specific exceptions for these.
52
+ #
53
+ # Why make things easy and clean, right?
54
+ rescue OpenSSL::OpenSSLError => e
55
+ raise_with_proper_backtrace( e, SSL )
56
+ end
57
+
58
+ def raise_with_proper_backtrace( ruby, arachni )
59
+ e = arachni.new( ruby.to_s )
60
+ e.set_backtrace ruby.backtrace
61
+ raise e
62
+ end
63
+ end
64
+
65
+ # Like a `SocketError.getaddrinfo` exception.
66
+ #
67
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
68
+ class HostNotFound < Error
69
+ end
70
+
71
+ # Like a `Errno::EACCES` exception.
72
+ #
73
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
74
+ class Permission < Error
75
+ end
76
+
77
+ # Like a `Errno::ECONNREFUSED` exception.
78
+ #
79
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
80
+ class Refused < Error
81
+ end
82
+
83
+ # Like a `Errno::ECONNRESET` exception.
84
+ #
85
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
86
+ class Reset < Error
87
+ end
88
+
89
+ # Like a `Errno::EPIPE` exception.
90
+ #
91
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
92
+ class BrokenPipe < Error
93
+ end
94
+
95
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
96
+ class Timeout < Error
97
+ end
98
+
99
+ # Like a `IOError` exception.
100
+ #
101
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
102
+ class Closed < Error
103
+ end
104
+
105
+ # Like a `OpenSSL::OpenSSLError` exception.
106
+ #
107
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
108
+ class SSL < Error
109
+ end
110
+
111
+ end
112
+ end
113
+ end
114
+ end