raktr 0.0.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE.md +29 -0
- data/README.md +77 -0
- data/Rakefile +53 -0
- data/lib/raktr/connection/callbacks.rb +71 -0
- data/lib/raktr/connection/error.rb +120 -0
- data/lib/raktr/connection/peer_info.rb +90 -0
- data/lib/raktr/connection/tls.rb +164 -0
- data/lib/raktr/connection.rb +339 -0
- data/lib/raktr/global.rb +24 -0
- data/lib/raktr/iterator.rb +249 -0
- data/lib/raktr/queue.rb +89 -0
- data/lib/raktr/tasks/base.rb +57 -0
- data/lib/raktr/tasks/delayed.rb +33 -0
- data/lib/raktr/tasks/one_off.rb +30 -0
- data/lib/raktr/tasks/periodic.rb +58 -0
- data/lib/raktr/tasks/persistent.rb +29 -0
- data/lib/raktr/tasks.rb +105 -0
- data/lib/raktr/version.rb +13 -0
- data/lib/raktr.rb +707 -0
- data/spec/raktr/connection/tls_spec.rb +348 -0
- data/spec/raktr/connection_spec.rb +74 -0
- data/spec/raktr/iterator_spec.rb +203 -0
- data/spec/raktr/queue_spec.rb +91 -0
- data/spec/raktr/tasks/base.rb +8 -0
- data/spec/raktr/tasks/delayed_spec.rb +71 -0
- data/spec/raktr/tasks/one_off_spec.rb +66 -0
- data/spec/raktr/tasks/periodic_spec.rb +57 -0
- data/spec/raktr/tasks/persistent_spec.rb +54 -0
- data/spec/raktr/tasks_spec.rb +155 -0
- data/spec/raktr_spec.rb +20 -0
- data/spec/raktr_tls_spec.rb +20 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/fixtures/handlers/echo_client.rb +34 -0
- data/spec/support/fixtures/handlers/echo_client_tls.rb +10 -0
- data/spec/support/fixtures/handlers/echo_server.rb +12 -0
- data/spec/support/fixtures/handlers/echo_server_tls.rb +8 -0
- data/spec/support/fixtures/pems/cacert.pem +37 -0
- data/spec/support/fixtures/pems/client/cert.pem +37 -0
- data/spec/support/fixtures/pems/client/foo-cert.pem +39 -0
- data/spec/support/fixtures/pems/client/foo-key.pem +51 -0
- data/spec/support/fixtures/pems/client/key.pem +51 -0
- data/spec/support/fixtures/pems/server/cert.pem +37 -0
- data/spec/support/fixtures/pems/server/key.pem +51 -0
- data/spec/support/helpers/paths.rb +23 -0
- data/spec/support/helpers/utilities.rb +135 -0
- data/spec/support/lib/server_option_parser.rb +29 -0
- data/spec/support/lib/servers/runner.rb +13 -0
- data/spec/support/lib/servers.rb +133 -0
- data/spec/support/servers/echo.rb +14 -0
- data/spec/support/servers/echo_tls.rb +22 -0
- data/spec/support/servers/echo_unix.rb +14 -0
- data/spec/support/servers/echo_unix_tls.rb +22 -0
- data/spec/support/shared/connection.rb +696 -0
- data/spec/support/shared/raktr.rb +834 -0
- data/spec/support/shared/task.rb +21 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2aa060f18df13540e73b42a5e9c68bdf86eaf03ae21104da0e827e57252dffe3
|
4
|
+
data.tar.gz: 5f1dd3fa7626e764f5212424c8e25b26a16bb52d4d00a2d2e6e4e5684575123a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8889f36f41d78a38dbd5afb95af6f4c2836e2056bb00451dabb10141c19d34c1ce02a7809c54e3c807713ab0b3bb94e6d969eb423878ab2c8514db703c868f85
|
7
|
+
data.tar.gz: fa3426f952f1980e9916aeaa1af050da734fec2c8de94a8e352c5223271e4309f66abd9fba46399d5470394ad9b4d65801620c4b060e9b4db1c0775d23a8859e
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# ChangeLog
|
data/LICENSE.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# License
|
2
|
+
|
3
|
+
Copyright (C) 2022, Ecsypno <https://ecsypno.com/>
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without modification,
|
7
|
+
are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer.
|
11
|
+
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
14
|
+
and/or other materials provided with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of the copyright holder nor the names of its contributors
|
17
|
+
may be used to endorse or promote products derived from this software
|
18
|
+
without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
21
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
22
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
24
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
25
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
26
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
27
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
29
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Raktr
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Version</th>
|
6
|
+
<td>0.0.1</td>
|
7
|
+
</tr>
|
8
|
+
<tr>
|
9
|
+
<th>Github page</th>
|
10
|
+
<td><a href="http://github.com/qadron/raktr">http://github.com/qadron/raktr</a></td>
|
11
|
+
<tr/>
|
12
|
+
<tr>
|
13
|
+
<th>Code Documentation</th>
|
14
|
+
<td><a href="http://rubydoc.info/github/qadron/raktr/">http://rubydoc.info/github/qadron/raktr/</a></td>
|
15
|
+
</tr>
|
16
|
+
<tr>
|
17
|
+
<th>Author</th>
|
18
|
+
<td><a href="http://twitter.com/Zap0tek">Tasos Laskos</a></td>
|
19
|
+
</tr>
|
20
|
+
<tr>
|
21
|
+
<th>Copyright</th>
|
22
|
+
<td>2022 <a href="https://ecsypno.com">Ecsypno</a></td>
|
23
|
+
</tr>
|
24
|
+
<tr>
|
25
|
+
<th>License</th>
|
26
|
+
<td><a href="file.LICENSE.html">3-clause BSD</a></td>
|
27
|
+
</tr>
|
28
|
+
</table>
|
29
|
+
|
30
|
+
## Synopsis
|
31
|
+
|
32
|
+
Raktr is a simple, lightweight, pure-Ruby implementation of the
|
33
|
+
[Reactor](http://en.wikipedia.org/wiki/Reactor_pattern) pattern, mainly focused
|
34
|
+
on network connections -- and less so on generic tasks.
|
35
|
+
|
36
|
+
## Features
|
37
|
+
|
38
|
+
- Extremely lightweight.
|
39
|
+
- Very simple design.
|
40
|
+
- Support for TCP/IP and UNIX-domain sockets.
|
41
|
+
- TLS encryption.
|
42
|
+
- Pure-Ruby.
|
43
|
+
- Multi-platform.
|
44
|
+
|
45
|
+
## Supported platforms
|
46
|
+
|
47
|
+
- Rubies:
|
48
|
+
- MRI >= 1.9
|
49
|
+
- Rubinius
|
50
|
+
- JRuby
|
51
|
+
- Operating Systems:
|
52
|
+
- Linux
|
53
|
+
- OSX
|
54
|
+
- Windows
|
55
|
+
|
56
|
+
## Examples
|
57
|
+
|
58
|
+
For examples please see the `examples/` directory.
|
59
|
+
|
60
|
+
## Installation
|
61
|
+
|
62
|
+
gem install raktr
|
63
|
+
|
64
|
+
## Running the Specs
|
65
|
+
|
66
|
+
rake spec
|
67
|
+
|
68
|
+
## Bug reports/Feature requests
|
69
|
+
|
70
|
+
Please send your feedback using GitHub's issue system at
|
71
|
+
[http://github.com/qadron/raktr/issues](http://github.com/qadron/raktr/issues).
|
72
|
+
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
Raktr is provided under the 3-clause BSD license.
|
77
|
+
See the [LICENSE](https://github.com/qadron/raktr/blob/master/LICENSE.md) file for more information.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require File.expand_path( File.dirname( __FILE__ ) ) + '/lib/raktr/version'
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'rspec'
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
|
16
|
+
RSpec::Core::RakeTask.new
|
17
|
+
rescue
|
18
|
+
end
|
19
|
+
|
20
|
+
task default: [ :build, :spec ]
|
21
|
+
|
22
|
+
desc 'Generate docs'
|
23
|
+
task :docs do
|
24
|
+
outdir = '../raktr-docs'
|
25
|
+
sh "rm -rf #{outdir}"
|
26
|
+
sh "mkdir -p #{outdir}"
|
27
|
+
|
28
|
+
sh "yardoc -o #{outdir}"
|
29
|
+
|
30
|
+
sh 'rm -rf .yardoc'
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'Clean up'
|
34
|
+
task :clean do
|
35
|
+
sh 'rm *.gem || true'
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'Build the gem.'
|
39
|
+
task build: [ :clean ] do
|
40
|
+
sh "gem build raktr.gemspec"
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Build and install the gem.'
|
44
|
+
task install: [ :build ] do
|
45
|
+
sh "gem install raktr-#{Raktr::VERSION}.gem"
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Push a new version to Rubygems'
|
49
|
+
task publish: [ :build ] do
|
50
|
+
sh "git tag -a v#{Raktr::VERSION} -m 'Version #{Raktr::VERSION}'"
|
51
|
+
sh "gem push raktr-#{Raktr::VERSION}.gem"
|
52
|
+
end
|
53
|
+
task release: [ :publish ]
|
@@ -0,0 +1,71 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
class Raktr
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
# Callbacks to be invoked per event.
|
13
|
+
#
|
14
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
15
|
+
module Callbacks
|
16
|
+
|
17
|
+
# Called after the connection has been established.
|
18
|
+
#
|
19
|
+
# @abstract
|
20
|
+
def on_connect
|
21
|
+
end
|
22
|
+
|
23
|
+
# Called after the connection has been attached to a {#raktr}.
|
24
|
+
#
|
25
|
+
# @abstract
|
26
|
+
def on_attach
|
27
|
+
end
|
28
|
+
|
29
|
+
# Called right the connection is detached from the {#raktr}.
|
30
|
+
#
|
31
|
+
# @abstract
|
32
|
+
def on_detach
|
33
|
+
end
|
34
|
+
|
35
|
+
# @note If a connection could not be established no {#socket} may be
|
36
|
+
# available.
|
37
|
+
#
|
38
|
+
# Called when the connection gets closed.
|
39
|
+
#
|
40
|
+
# @param [Exception] reason
|
41
|
+
# Reason for the close.
|
42
|
+
#
|
43
|
+
# @abstract
|
44
|
+
def on_close( reason )
|
45
|
+
end
|
46
|
+
|
47
|
+
# Called when data are available.
|
48
|
+
#
|
49
|
+
# @param [String] data
|
50
|
+
# Incoming data.
|
51
|
+
#
|
52
|
+
# @abstract
|
53
|
+
def on_read( data )
|
54
|
+
end
|
55
|
+
|
56
|
+
# Called after each {#write} call.
|
57
|
+
#
|
58
|
+
# @abstract
|
59
|
+
def on_write
|
60
|
+
end
|
61
|
+
|
62
|
+
# Called after the {#write buffered data} have all been sent to the peer.
|
63
|
+
#
|
64
|
+
# @abstract
|
65
|
+
def on_flush
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
class Raktr
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
# {Connection} error namespace.
|
13
|
+
#
|
14
|
+
# All {Connection} errors inherit from and live under it.
|
15
|
+
#
|
16
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
17
|
+
class Error < Raktr::Error
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Captures Ruby exceptions and converts them to an appropriate
|
21
|
+
# subclass of {Error}.
|
22
|
+
#
|
23
|
+
# @param [Block] block Block to run.
|
24
|
+
def translate( &block )
|
25
|
+
block.call
|
26
|
+
rescue IOError, Errno::ENOTCONN, Errno::ENOTSOCK => e
|
27
|
+
raise_with_proper_backtrace( e, Closed )
|
28
|
+
rescue SocketError, Errno::ENOENT => e
|
29
|
+
raise_with_proper_backtrace( e, HostNotFound )
|
30
|
+
rescue Errno::EPIPE => e
|
31
|
+
raise_with_proper_backtrace( e, BrokenPipe )
|
32
|
+
rescue Errno::ECONNREFUSED,
|
33
|
+
# JRuby throws Errno::EADDRINUSE when trying to connect to a
|
34
|
+
# non-existent server.
|
35
|
+
Errno::EADDRINUSE => e
|
36
|
+
raise_with_proper_backtrace( e, Refused )
|
37
|
+
rescue Errno::ECONNRESET, Errno::ECONNABORTED => e
|
38
|
+
raise_with_proper_backtrace( e, Reset )
|
39
|
+
rescue Errno::EACCES => e
|
40
|
+
raise_with_proper_backtrace( e, Permission )
|
41
|
+
rescue Errno::EBADF => e
|
42
|
+
raise_with_proper_backtrace( e, BadSocket )
|
43
|
+
|
44
|
+
# Catch and forward these before handling OpenSSL::OpenSSLError because
|
45
|
+
# all SSL errors inherit from it, including OpenSSL::SSL::SSLErrorWaitReadable
|
46
|
+
# and OpenSSL::SSL::SSLErrorWaitWritable which also inherit from
|
47
|
+
# IO::WaitReadable and IO::WaitWritable and need special treatment.
|
48
|
+
rescue IO::WaitReadable, IO::WaitWritable, Errno::EINPROGRESS
|
49
|
+
raise
|
50
|
+
|
51
|
+
# We're mainly interested in translating SSL handshake errors but there
|
52
|
+
# aren't any specific exceptions for these.
|
53
|
+
#
|
54
|
+
# Why make things easy and clean, right?
|
55
|
+
rescue OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError => e
|
56
|
+
raise_with_proper_backtrace( e, SSL )
|
57
|
+
end
|
58
|
+
|
59
|
+
def raise_with_proper_backtrace( ruby, raktr )
|
60
|
+
e = raktr.new( ruby.to_s )
|
61
|
+
e.set_backtrace ruby.backtrace
|
62
|
+
raise e
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Like a `SocketError.getaddrinfo` exception.
|
67
|
+
#
|
68
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
69
|
+
class HostNotFound < Error
|
70
|
+
end
|
71
|
+
|
72
|
+
# Like a `Errno::EACCES` exception.
|
73
|
+
#
|
74
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
75
|
+
class Permission < Error
|
76
|
+
end
|
77
|
+
|
78
|
+
# Like a `Errno::ECONNREFUSED` exception.
|
79
|
+
#
|
80
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
81
|
+
class Refused < Error
|
82
|
+
end
|
83
|
+
|
84
|
+
# Like a `Errno::ECONNRESET` exception.
|
85
|
+
#
|
86
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
87
|
+
class Reset < Error
|
88
|
+
end
|
89
|
+
|
90
|
+
# Like a `Errno::EPIPE` exception.
|
91
|
+
#
|
92
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
93
|
+
class BrokenPipe < Error
|
94
|
+
end
|
95
|
+
|
96
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
97
|
+
class Timeout < Error
|
98
|
+
end
|
99
|
+
|
100
|
+
# Like a `IOError` exception.
|
101
|
+
#
|
102
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
103
|
+
class Closed < Error
|
104
|
+
end
|
105
|
+
|
106
|
+
# Like a `Errno::EBADF` exception.
|
107
|
+
#
|
108
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
109
|
+
class BadSocket < Error
|
110
|
+
end
|
111
|
+
|
112
|
+
# Like a `OpenSSL::OpenSSLError` exception.
|
113
|
+
#
|
114
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
115
|
+
class SSL < Error
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
class Raktr
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
13
|
+
module PeerInfo
|
14
|
+
|
15
|
+
# @param [Bool] resolve
|
16
|
+
# Resolve IP address to hostname.
|
17
|
+
# @return [Hash]
|
18
|
+
# Peer address information:
|
19
|
+
#
|
20
|
+
# * IP socket:
|
21
|
+
# * Without `resolve`:
|
22
|
+
#
|
23
|
+
# {
|
24
|
+
# protocol: 'AF_INET',
|
25
|
+
# port: 10314,
|
26
|
+
# hostname: '127.0.0.1',
|
27
|
+
# ip_address: '127.0.0.1'
|
28
|
+
# }
|
29
|
+
#
|
30
|
+
# * With `resolve`:
|
31
|
+
#
|
32
|
+
# {
|
33
|
+
# protocol: 'AF_INET',
|
34
|
+
# port: 10314,
|
35
|
+
# hostname: 'localhost',
|
36
|
+
# ip_address: '127.0.0.1'
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# * UNIX-domain socket:
|
40
|
+
#
|
41
|
+
# {
|
42
|
+
# protocol: 'AF_UNIX',
|
43
|
+
# path: '/tmp/my-socket'
|
44
|
+
# }
|
45
|
+
def peer_address_info( resolve = false )
|
46
|
+
if Raktr.supports_unix_sockets? && to_io.is_a?( UNIXSocket )
|
47
|
+
{
|
48
|
+
protocol: to_io.peeraddr.first,
|
49
|
+
path: to_io.path
|
50
|
+
}
|
51
|
+
else
|
52
|
+
protocol, port, hostname, ip_address = to_io.peeraddr( resolve )
|
53
|
+
|
54
|
+
{
|
55
|
+
protocol: protocol,
|
56
|
+
port: port,
|
57
|
+
hostname: hostname,
|
58
|
+
ip_address: ip_address
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String]
|
64
|
+
# Peer's IP address or socket path.
|
65
|
+
def peer_address
|
66
|
+
peer_ip_address || peer_address_info[:path]
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [String]
|
70
|
+
# Peer's IP address.
|
71
|
+
def peer_ip_address
|
72
|
+
peer_address_info[:ip_address]
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [String]
|
76
|
+
# Peer's hostname.
|
77
|
+
def peer_hostname
|
78
|
+
peer_address_info(true)[:hostname]
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [String]
|
82
|
+
# Peer's port.
|
83
|
+
def peer_port
|
84
|
+
peer_address_info[:port]
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
class Raktr
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
13
|
+
module TLS
|
14
|
+
|
15
|
+
# Converts the {#socket} to an SSL one.
|
16
|
+
#
|
17
|
+
# @param [Hash] options
|
18
|
+
# @option [String] :certificate
|
19
|
+
# Path to a PEM certificate.
|
20
|
+
# @option [String] :private_key
|
21
|
+
# Path to a PEM private key.
|
22
|
+
# @option [String] :ca
|
23
|
+
# Path to a PEM CA.
|
24
|
+
def start_tls( options = {} )
|
25
|
+
if @socket.is_a? OpenSSL::SSL::SSLSocket
|
26
|
+
@ssl_context = @socket.context
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new
|
31
|
+
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
32
|
+
|
33
|
+
if options[:certificate] && options[:private_key]
|
34
|
+
|
35
|
+
@ssl_context.cert =
|
36
|
+
OpenSSL::X509::Certificate.new( File.open( options[:certificate] ) )
|
37
|
+
@ssl_context.key =
|
38
|
+
OpenSSL::PKey::RSA.new( File.open( options[:private_key] ) )
|
39
|
+
|
40
|
+
@ssl_context.ca_file = options[:ca]
|
41
|
+
@ssl_context.verify_mode =
|
42
|
+
OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
43
|
+
|
44
|
+
elsif @role == :server
|
45
|
+
@ssl_context.key = OpenSSL::PKey::RSA.new( 2048 )
|
46
|
+
@ssl_context.cert = OpenSSL::X509::Certificate.new
|
47
|
+
@ssl_context.cert.subject = OpenSSL::X509::Name.new( [['CN', 'localhost']] )
|
48
|
+
@ssl_context.cert.issuer = @ssl_context.cert.subject
|
49
|
+
@ssl_context.cert.public_key = @ssl_context.key
|
50
|
+
@ssl_context.cert.not_before = Time.now
|
51
|
+
@ssl_context.cert.not_after = Time.now + 60 * 60 * 24
|
52
|
+
@ssl_context.cert.version = 2
|
53
|
+
@ssl_context.cert.serial = 1
|
54
|
+
|
55
|
+
@ssl_context.cert.sign( @ssl_context.key, OpenSSL::Digest::SHA1.new )
|
56
|
+
end
|
57
|
+
|
58
|
+
if @role == :server
|
59
|
+
@socket = OpenSSL::SSL::SSLServer.new( @socket, @ssl_context )
|
60
|
+
else
|
61
|
+
@socket = OpenSSL::SSL::SSLSocket.new( @socket, @ssl_context )
|
62
|
+
@socket.sync_close = true
|
63
|
+
|
64
|
+
# We've switched to SSL, a connection needs to be re-established
|
65
|
+
# via the SSL handshake.
|
66
|
+
@connected = false
|
67
|
+
|
68
|
+
_connect if unix?
|
69
|
+
end
|
70
|
+
|
71
|
+
@socket
|
72
|
+
end
|
73
|
+
|
74
|
+
# Performs an SSL handshake in addition to a plaintext connect operation.
|
75
|
+
#
|
76
|
+
# @private
|
77
|
+
def _connect
|
78
|
+
return if @ssl_connected
|
79
|
+
|
80
|
+
Error.translate do
|
81
|
+
@plaintext_connected ||= super
|
82
|
+
return if !@plaintext_connected
|
83
|
+
|
84
|
+
# Mark the connection as not connected due to the pending SSL handshake.
|
85
|
+
@connected = false
|
86
|
+
|
87
|
+
@socket.connect_nonblock
|
88
|
+
@ssl_connected = @connected = true
|
89
|
+
end
|
90
|
+
rescue IO::WaitReadable, IO::WaitWritable, Errno::EINPROGRESS
|
91
|
+
rescue Error => e
|
92
|
+
close e
|
93
|
+
end
|
94
|
+
|
95
|
+
# First checks if there's a pending SSL #accept operation when this
|
96
|
+
# connection is a server handler which has been passed an accepted
|
97
|
+
# plaintext connection.
|
98
|
+
#
|
99
|
+
# @private
|
100
|
+
def _write( *args )
|
101
|
+
return ssl_accept if accept?
|
102
|
+
|
103
|
+
super( *args )
|
104
|
+
end
|
105
|
+
|
106
|
+
# First checks if there's a pending SSL #accept operation when this
|
107
|
+
# connection is a server handler which has been passed an accepted
|
108
|
+
# plaintext connection.
|
109
|
+
#
|
110
|
+
# @private
|
111
|
+
def _read
|
112
|
+
return ssl_accept if accept?
|
113
|
+
|
114
|
+
super
|
115
|
+
rescue OpenSSL::SSL::SSLErrorWaitReadable
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def ssl_accept
|
121
|
+
Error.translate do
|
122
|
+
@accepted = !!@socket.accept_nonblock
|
123
|
+
end
|
124
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
125
|
+
rescue Error => e
|
126
|
+
close e
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
def accept?
|
131
|
+
return false if @accepted
|
132
|
+
return false if role != :server || !@socket.is_a?( OpenSSL::SSL::SSLSocket )
|
133
|
+
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
# Accepts a new SSL client connection.
|
138
|
+
#
|
139
|
+
# @return [OpenSSL::SSL::SSLSocket, nil]
|
140
|
+
# New connection or `nil` if the socket isn't ready to accept new
|
141
|
+
# connections yet.
|
142
|
+
#
|
143
|
+
# @private
|
144
|
+
def socket_accept
|
145
|
+
Error.translate do
|
146
|
+
socket = to_io.accept_nonblock
|
147
|
+
|
148
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(
|
149
|
+
socket,
|
150
|
+
@ssl_context
|
151
|
+
)
|
152
|
+
ssl_socket.sync_close = true
|
153
|
+
ssl.accept if @start_immediately
|
154
|
+
ssl_socket
|
155
|
+
end
|
156
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
157
|
+
rescue Error => e
|
158
|
+
close e
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|