crussh 0.1.0
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 +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +371 -0
- data/ext/poly1305/Cargo.toml +13 -0
- data/ext/poly1305/extconf.rb +6 -0
- data/ext/poly1305/src/lib.rs +75 -0
- data/lib/crussh/auth.rb +46 -0
- data/lib/crussh/channel/key_parser.rb +125 -0
- data/lib/crussh/channel.rb +381 -0
- data/lib/crussh/cipher/algorithm.rb +31 -0
- data/lib/crussh/cipher/chacha20poly1305.rb +98 -0
- data/lib/crussh/cipher.rb +25 -0
- data/lib/crussh/compression.rb +42 -0
- data/lib/crussh/gatekeeper.rb +50 -0
- data/lib/crussh/handler/line_buffer.rb +131 -0
- data/lib/crussh/handler.rb +128 -0
- data/lib/crussh/heartbeat.rb +68 -0
- data/lib/crussh/kex/algorithm.rb +86 -0
- data/lib/crussh/kex/curve25519.rb +30 -0
- data/lib/crussh/kex/exchange.rb +234 -0
- data/lib/crussh/kex.rb +42 -0
- data/lib/crussh/keys/key_pair.rb +61 -0
- data/lib/crussh/keys/public_key.rb +35 -0
- data/lib/crussh/keys.rb +70 -0
- data/lib/crussh/limits.rb +45 -0
- data/lib/crussh/logger.rb +95 -0
- data/lib/crussh/mac/algorithm.rb +23 -0
- data/lib/crussh/mac/crypto.rb +60 -0
- data/lib/crussh/mac/none.rb +9 -0
- data/lib/crussh/mac.rb +28 -0
- data/lib/crussh/negotiator.rb +41 -0
- data/lib/crussh/preferred.rb +16 -0
- data/lib/crussh/protocol/channel_close.rb +11 -0
- data/lib/crussh/protocol/channel_data.rb +12 -0
- data/lib/crussh/protocol/channel_eof.rb +11 -0
- data/lib/crussh/protocol/channel_extended_data.rb +13 -0
- data/lib/crussh/protocol/channel_failure.rb +11 -0
- data/lib/crussh/protocol/channel_open.rb +69 -0
- data/lib/crussh/protocol/channel_open_confirmation.rb +15 -0
- data/lib/crussh/protocol/channel_open_failure.rb +14 -0
- data/lib/crussh/protocol/channel_request.rb +146 -0
- data/lib/crussh/protocol/channel_success.rb +11 -0
- data/lib/crussh/protocol/channel_window_adjust.rb +12 -0
- data/lib/crussh/protocol/debug.rb +15 -0
- data/lib/crussh/protocol/disconnect.rb +39 -0
- data/lib/crussh/protocol/ext_info.rb +48 -0
- data/lib/crussh/protocol/global_request.rb +46 -0
- data/lib/crussh/protocol/ignore.rb +11 -0
- data/lib/crussh/protocol/kex_ecdh_init.rb +11 -0
- data/lib/crussh/protocol/kex_ecdh_reply.rb +13 -0
- data/lib/crussh/protocol/kex_init.rb +38 -0
- data/lib/crussh/protocol/new_keys.rb +9 -0
- data/lib/crussh/protocol/ping.rb +11 -0
- data/lib/crussh/protocol/pong.rb +11 -0
- data/lib/crussh/protocol/request_failure.rb +9 -0
- data/lib/crussh/protocol/request_success.rb +11 -0
- data/lib/crussh/protocol/service_accept.rb +11 -0
- data/lib/crussh/protocol/service_request.rb +11 -0
- data/lib/crussh/protocol/unimplemented.rb +11 -0
- data/lib/crussh/protocol/userauth_banner.rb +12 -0
- data/lib/crussh/protocol/userauth_failure.rb +12 -0
- data/lib/crussh/protocol/userauth_pk_ok.rb +12 -0
- data/lib/crussh/protocol/userauth_request.rb +52 -0
- data/lib/crussh/protocol/userauth_success.rb +9 -0
- data/lib/crussh/protocol.rb +135 -0
- data/lib/crussh/server/auth_handler.rb +18 -0
- data/lib/crussh/server/config.rb +157 -0
- data/lib/crussh/server/layers/connection.rb +363 -0
- data/lib/crussh/server/layers/transport.rb +49 -0
- data/lib/crussh/server/layers/userauth.rb +232 -0
- data/lib/crussh/server/request_rule.rb +76 -0
- data/lib/crussh/server/session.rb +192 -0
- data/lib/crussh/server.rb +214 -0
- data/lib/crussh/ssh_id.rb +44 -0
- data/lib/crussh/transport/packet_stream.rb +245 -0
- data/lib/crussh/transport/reader.rb +98 -0
- data/lib/crussh/transport/version_exchange.rb +26 -0
- data/lib/crussh/transport/writer.rb +72 -0
- data/lib/crussh/version.rb +5 -0
- data/lib/crussh.rb +61 -0
- data/sig/crussh.rbs +4 -0
- metadata +249 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
class Server
|
|
5
|
+
module Layers
|
|
6
|
+
class Transport
|
|
7
|
+
def initialize(session)
|
|
8
|
+
@session = session
|
|
9
|
+
@client_version = nil
|
|
10
|
+
@algorithms = nil
|
|
11
|
+
@session_id = nil
|
|
12
|
+
@client_kexinit_payload = nil
|
|
13
|
+
@server_kexinit_payload = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :client_version, :algorithms, :session_id
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
version_exchange
|
|
20
|
+
key_exchange
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def config = @session.config
|
|
26
|
+
def socket = @session.socket
|
|
27
|
+
|
|
28
|
+
def version_exchange
|
|
29
|
+
exchange = ::Crussh::Transport::VersionExchange.new(socket, server_id: config.server_id)
|
|
30
|
+
|
|
31
|
+
@client_version = exchange.exchange
|
|
32
|
+
|
|
33
|
+
Logger.info(
|
|
34
|
+
self,
|
|
35
|
+
"Version exchange complete",
|
|
36
|
+
client_version: @client_version.software_version,
|
|
37
|
+
comments: @client_version.comments,
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def key_exchange
|
|
42
|
+
kex = Kex::Exchange.new(@session)
|
|
43
|
+
kex.initial(client_version: @client_version)
|
|
44
|
+
@session_id = kex.session_id
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
class Server
|
|
5
|
+
module Layers
|
|
6
|
+
class Userauth
|
|
7
|
+
def initialize(session)
|
|
8
|
+
@session = session
|
|
9
|
+
@authenticated_user = nil
|
|
10
|
+
@attempts = 0
|
|
11
|
+
@first_attempt = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :authenticated_user
|
|
15
|
+
|
|
16
|
+
def run(task: Async::Task.current)
|
|
17
|
+
timeout = config.auth_timeout || config.connection_timeout
|
|
18
|
+
|
|
19
|
+
task.with_timeout(timeout) do
|
|
20
|
+
service_request
|
|
21
|
+
send_banner
|
|
22
|
+
authenticate
|
|
23
|
+
end
|
|
24
|
+
rescue Async::TimeoutError
|
|
25
|
+
Logger.warn(self, "Authentication timeout")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def config = @session.config
|
|
31
|
+
def server = @session.server
|
|
32
|
+
def session_id = @session.id
|
|
33
|
+
|
|
34
|
+
def supported_methods
|
|
35
|
+
server.auth_methods.map(&:to_s)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def service_request
|
|
39
|
+
packet = @session.read_packet
|
|
40
|
+
request = Protocol::ServiceRequest.parse(packet)
|
|
41
|
+
|
|
42
|
+
Logger.debug(self, "Service request", service: request.service_name)
|
|
43
|
+
|
|
44
|
+
unless request.service_name == "ssh-userauth"
|
|
45
|
+
raise ProtocolError, "Unknown service: #{request.service_name}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
accept = Protocol::ServiceAccept.new(service_name: "ssh-userauth")
|
|
49
|
+
@session.write_packet(accept)
|
|
50
|
+
|
|
51
|
+
Logger.debug(self, "Service accepted", service: "ssh-userauth")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def send_banner
|
|
55
|
+
banner = server.banner
|
|
56
|
+
|
|
57
|
+
return if banner.nil?
|
|
58
|
+
|
|
59
|
+
packet = Protocol::UserauthBanner.new(message: banner)
|
|
60
|
+
@session.write_packet(packet)
|
|
61
|
+
Logger.debug(self, "Banner sent")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def authenticate
|
|
65
|
+
loop do
|
|
66
|
+
packet = @session.read_packet
|
|
67
|
+
message_type = packet.getbyte(0)
|
|
68
|
+
|
|
69
|
+
case message_type
|
|
70
|
+
when Protocol::USERAUTH_REQUEST
|
|
71
|
+
handle_auth_request(packet)&.then { return }
|
|
72
|
+
when Protocol::DISCONNECT
|
|
73
|
+
Logger.debug(self, "Client disconnected during auth")
|
|
74
|
+
@session.close
|
|
75
|
+
return
|
|
76
|
+
else
|
|
77
|
+
Logger.warn(self, "Unknown message type during authentication", message_type:)
|
|
78
|
+
unimplemented = Protocol::Unimplemented.new(sequence_number: @session.last_read_sequence)
|
|
79
|
+
@session.write_packet(unimplemented)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def handle_auth_request(packet)
|
|
85
|
+
request = Protocol::UserauthRequest.parse(packet)
|
|
86
|
+
|
|
87
|
+
Logger.debug(
|
|
88
|
+
self,
|
|
89
|
+
"Auth request",
|
|
90
|
+
user: request.username,
|
|
91
|
+
service: request.service_name,
|
|
92
|
+
method: request.method_name,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
result = dispatch_auth(request)
|
|
96
|
+
|
|
97
|
+
case result
|
|
98
|
+
when :success
|
|
99
|
+
handle_successful_auth(request)
|
|
100
|
+
true
|
|
101
|
+
when :pk_ok, :partial
|
|
102
|
+
nil
|
|
103
|
+
when :failure
|
|
104
|
+
handle_failed_auth(request)
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def dispatch_auth(request)
|
|
110
|
+
method = request.method_name.to_sym
|
|
111
|
+
|
|
112
|
+
result = case method
|
|
113
|
+
when :none
|
|
114
|
+
server.handle_auth(:none, request.username)
|
|
115
|
+
when :password
|
|
116
|
+
server.handle_auth(:password, request.username, request.password)
|
|
117
|
+
when :publickey
|
|
118
|
+
handle_publickey(request)
|
|
119
|
+
when :keyboard_interactive
|
|
120
|
+
Auth.reject
|
|
121
|
+
else
|
|
122
|
+
Logger.warn(self, "Unknown auth method", method: request.method_name)
|
|
123
|
+
Auth.reject
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
case result
|
|
127
|
+
when lambda(&:success?)
|
|
128
|
+
:success
|
|
129
|
+
when lambda(&:partial?)
|
|
130
|
+
handle_partial_success(request, result)
|
|
131
|
+
:partial
|
|
132
|
+
else
|
|
133
|
+
:failure
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def handle_publickey(request)
|
|
138
|
+
pk_data = request.public_key_data
|
|
139
|
+
public_key = Keys.parse_public_blob(pk_data.key_blob)
|
|
140
|
+
|
|
141
|
+
unless pk_data.has_signature?
|
|
142
|
+
acceptable = server.handle_auth(:publickey, request.username, public_key)
|
|
143
|
+
|
|
144
|
+
if acceptable
|
|
145
|
+
pk_ok = Protocol::UserauthPkOk.new(
|
|
146
|
+
algorithm: pk_data.algorithm,
|
|
147
|
+
key_blob: pk_data.key_blob,
|
|
148
|
+
)
|
|
149
|
+
@session.write_packet(pk_ok)
|
|
150
|
+
|
|
151
|
+
Logger.debug(self, "PK_OK sent", algorithm: pk_data.algorithm)
|
|
152
|
+
|
|
153
|
+
:pk_ok
|
|
154
|
+
else
|
|
155
|
+
:failure
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
signed = build_signed_data(request, pk_data)
|
|
160
|
+
|
|
161
|
+
if public_key.verify(signed, pk_data.signature)
|
|
162
|
+
:success
|
|
163
|
+
else
|
|
164
|
+
:failure
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def handle_successful_auth(request)
|
|
169
|
+
@authenticated_user = request.username
|
|
170
|
+
@session.write_packet(Protocol::UserauthSuccess.new)
|
|
171
|
+
server.auth_succeeded(request.username) if server.respond_to?(:auth_succeeded)
|
|
172
|
+
|
|
173
|
+
@session.enable_compression
|
|
174
|
+
|
|
175
|
+
Logger.info(
|
|
176
|
+
self,
|
|
177
|
+
"Authentication successful",
|
|
178
|
+
user: request.username,
|
|
179
|
+
method: request.method_name,
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def handle_partial_success(request, result)
|
|
184
|
+
methods = result.continue_with.map(&:to_s)
|
|
185
|
+
packet = Protocol::UserauthFailure.new(
|
|
186
|
+
authentications: methods,
|
|
187
|
+
partial_success: true,
|
|
188
|
+
)
|
|
189
|
+
@session.write_packet(packet)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def handle_failed_auth(request)
|
|
193
|
+
@attempts += 1
|
|
194
|
+
|
|
195
|
+
rejection_time = if initial?(request)
|
|
196
|
+
config.auth_rejection_time_initial
|
|
197
|
+
else
|
|
198
|
+
config.auth_rejection_time
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
@first_attempt = false
|
|
202
|
+
|
|
203
|
+
sleep(rejection_time) if rejection_time&.positive?
|
|
204
|
+
|
|
205
|
+
packet = Protocol::UserauthFailure.new(authentications: supported_methods)
|
|
206
|
+
@session.write_packet(packet)
|
|
207
|
+
|
|
208
|
+
return if @attempts < config.max_auth_attempts
|
|
209
|
+
|
|
210
|
+
@session.disconnect(:no_more_auth_methods_available, "Too many authentication failures")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def build_signed_data(request, pk_data)
|
|
214
|
+
writer = Crussh::Transport::Writer.new
|
|
215
|
+
writer.string(session_id)
|
|
216
|
+
writer.byte(Protocol::USERAUTH_REQUEST)
|
|
217
|
+
writer.string(request.username)
|
|
218
|
+
writer.string(request.service_name)
|
|
219
|
+
writer.string("publickey")
|
|
220
|
+
writer.boolean(true)
|
|
221
|
+
writer.string(pk_data.algorithm)
|
|
222
|
+
writer.string(pk_data.key_blob)
|
|
223
|
+
writer.to_s
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def initial?(request)
|
|
227
|
+
@first_attempt && request.none?
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
class Server
|
|
5
|
+
class RequestRule
|
|
6
|
+
class << self
|
|
7
|
+
def accept(only: nil, except: nil, if: nil, unless: nil, &block)
|
|
8
|
+
new(
|
|
9
|
+
allow: true,
|
|
10
|
+
only: only,
|
|
11
|
+
except: except,
|
|
12
|
+
if: binding.local_variable_get(:if),
|
|
13
|
+
unless: binding.local_variable_get(:unless),
|
|
14
|
+
&block
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def reject
|
|
19
|
+
new(allow: false, only: nil, except: nil, if: nil, unless: nil, &block)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(allow: true, only: nil, except: nil, if: nil, unless: nil, &block)
|
|
24
|
+
@allow = allow
|
|
25
|
+
@only = only
|
|
26
|
+
@except = except
|
|
27
|
+
@if = binding.local_variable_get(:if)
|
|
28
|
+
@unless = binding.local_variable_get(:unless)
|
|
29
|
+
@block = block
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_reader :allow, :only, :except, :if, :unless, :block
|
|
33
|
+
|
|
34
|
+
def allowed?(channel, **params)
|
|
35
|
+
return false unless allow
|
|
36
|
+
|
|
37
|
+
if only
|
|
38
|
+
value = params[:name] || params[:term]
|
|
39
|
+
return false unless matches_patterns?(only, value)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if except
|
|
43
|
+
value = params[:name] || params[:term]
|
|
44
|
+
return false if matches_patterns?(except, value)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return false if self.if && !self.if.call(channel, **params)
|
|
48
|
+
return false if self.unless&.call(channel, **params)
|
|
49
|
+
return block.call(channel, **params) if block
|
|
50
|
+
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def matches_patterns?(patterns, value)
|
|
57
|
+
return true if value.nil?
|
|
58
|
+
|
|
59
|
+
Array(patterns).any? do |pattern|
|
|
60
|
+
case pattern
|
|
61
|
+
when Regexp
|
|
62
|
+
pattern.match?(value)
|
|
63
|
+
when String
|
|
64
|
+
if pattern.include?("*")
|
|
65
|
+
File.fnmatch?(pattern, value)
|
|
66
|
+
else
|
|
67
|
+
pattern == value
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
pattern == value
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
class Server
|
|
5
|
+
class Session
|
|
6
|
+
def initialize(socket, server:)
|
|
7
|
+
@socket = socket
|
|
8
|
+
@server = server
|
|
9
|
+
|
|
10
|
+
@packet_stream = Transport::PacketStream.new(socket, max_packet_size: config.max_packet_size)
|
|
11
|
+
|
|
12
|
+
@bytes_read = 0
|
|
13
|
+
@bytes_written = 0
|
|
14
|
+
@last_kex_time = Time.now
|
|
15
|
+
@algorithms = nil
|
|
16
|
+
|
|
17
|
+
@heartbeat = nil
|
|
18
|
+
|
|
19
|
+
@strict_kex = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :client_version, :socket, :server, :user, :id
|
|
23
|
+
attr_accessor :algorithms
|
|
24
|
+
attr_writer :strict_kex
|
|
25
|
+
|
|
26
|
+
def config = @server.config
|
|
27
|
+
def strict_kex? = @strict_kex
|
|
28
|
+
|
|
29
|
+
def start
|
|
30
|
+
transport = run_layer(Layers::Transport)
|
|
31
|
+
@id = transport.session_id
|
|
32
|
+
|
|
33
|
+
userauth = run_layer(Layers::Userauth)
|
|
34
|
+
@user = userauth.authenticated_user
|
|
35
|
+
|
|
36
|
+
@server.gatekeeper.authenticate!
|
|
37
|
+
|
|
38
|
+
start_heartbeat
|
|
39
|
+
|
|
40
|
+
run_layer(Layers::Connection)
|
|
41
|
+
|
|
42
|
+
Logger.info(self, "Session established", user: @user)
|
|
43
|
+
rescue NegotiationError => e
|
|
44
|
+
Logger.error(self, "Negotation Error", e)
|
|
45
|
+
disconnect(:key_exchange_failed, e.message)
|
|
46
|
+
rescue ProtocolError => e
|
|
47
|
+
Logger.error(self, "Protocol Error", e)
|
|
48
|
+
disconnect(:protocol_error, e.message)
|
|
49
|
+
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, ConnectionClosed => e
|
|
50
|
+
Logger.debug(self, "Connection closed", reason: e.class.name)
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
Logger.error(self, "Internal Server Error", e)
|
|
53
|
+
disconnect(:by_application, "Internal error")
|
|
54
|
+
ensure
|
|
55
|
+
stop_heartbeat
|
|
56
|
+
close
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def disconnect(reason, description = "")
|
|
60
|
+
message = Protocol::Disconnect.build(reason, description)
|
|
61
|
+
write_raw_packet(message)
|
|
62
|
+
stop_heartbeat
|
|
63
|
+
close
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def close
|
|
67
|
+
return if socket.closed?
|
|
68
|
+
|
|
69
|
+
@socket.close
|
|
70
|
+
rescue StandardError => e
|
|
71
|
+
Logger.error(self, "Error", e)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def read_packet
|
|
75
|
+
start_rekey if rekey?
|
|
76
|
+
|
|
77
|
+
@heartbeat&.record_activity!
|
|
78
|
+
|
|
79
|
+
loop do
|
|
80
|
+
packet = read_raw_packet
|
|
81
|
+
message_type = packet.getbyte(0)
|
|
82
|
+
|
|
83
|
+
case message_type
|
|
84
|
+
when Protocol::IGNORE, Protocol::EXT_INFO
|
|
85
|
+
next
|
|
86
|
+
when Protocol::DEBUG
|
|
87
|
+
message = Protocol::Debug.parse(packet)
|
|
88
|
+
Logger.debug(self, "Client debug", message: message.message) if message.always_display?
|
|
89
|
+
next
|
|
90
|
+
when Protocol::PING
|
|
91
|
+
pong(packet)
|
|
92
|
+
next
|
|
93
|
+
when Protocol::KEXINIT
|
|
94
|
+
rekey(packet)
|
|
95
|
+
next
|
|
96
|
+
else
|
|
97
|
+
@bytes_read += packet.bytesize
|
|
98
|
+
return packet
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def write_packet(message)
|
|
104
|
+
start_rekey if rekey?
|
|
105
|
+
|
|
106
|
+
data = message.serialize
|
|
107
|
+
@bytes_written += data.bytesize
|
|
108
|
+
@packet_stream.write(data)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def read_raw_packet = @packet_stream.read
|
|
112
|
+
def write_raw_packet(message) = @packet_stream.write(message.serialize)
|
|
113
|
+
|
|
114
|
+
def enable_encryption(...) = @packet_stream.enable_encryption(...)
|
|
115
|
+
|
|
116
|
+
def enable_compression
|
|
117
|
+
return if @algorithms.nil?
|
|
118
|
+
|
|
119
|
+
c2s = @algorithms.compression_client_to_server
|
|
120
|
+
s2c = @algorithms.compression_server_to_client
|
|
121
|
+
|
|
122
|
+
return if c2s == Compression::NONE && s2c == Compression::NONE
|
|
123
|
+
|
|
124
|
+
read_compressor = Compression.from_name(c2s)
|
|
125
|
+
write_compressor = Compression.from_name(s2c)
|
|
126
|
+
|
|
127
|
+
@packet_stream.enable_compression(read_compressor, write_compressor)
|
|
128
|
+
Logger.info(self, "Compression enabled", send: s2c, recv: c2s)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def last_read_sequence = @packet_stream.last_read_sequence
|
|
132
|
+
def reset_sequence = @packet_stream.reset_sequence
|
|
133
|
+
def sequence_wrapped? = @packet_stream.sequence_wrapped?
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def rekey?
|
|
138
|
+
limits = config.limits
|
|
139
|
+
|
|
140
|
+
limits.over?(read: @bytes_read, written: @bytes_written, time: @last_kex_time)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def start_rekey
|
|
144
|
+
Logger.info(self, "Initiating rekey to client")
|
|
145
|
+
|
|
146
|
+
kex = Kex::Exchange.new(self)
|
|
147
|
+
kex.start_rekey
|
|
148
|
+
reset_rekey_tracking
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def pong(packet)
|
|
152
|
+
ping = Protocol::Ping.parse(packet)
|
|
153
|
+
pong = Protocol::Pong.new(data: ping.data)
|
|
154
|
+
|
|
155
|
+
write_packet(pong)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def rekey(client_kexinit_payload)
|
|
159
|
+
Logger.info(self, "Client initiated rekey")
|
|
160
|
+
|
|
161
|
+
kex = Kex::Exchange.new(self)
|
|
162
|
+
kex.rekey(client_kexinit_payload: client_kexinit_payload)
|
|
163
|
+
|
|
164
|
+
Logger.info(self, "Rekey complete")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def reset_rekey_tracking
|
|
168
|
+
@bytes_read = 0
|
|
169
|
+
@bytes_written = 0
|
|
170
|
+
@last_kex_time = Time.now
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def start_heartbeat
|
|
174
|
+
return if config.keepalive_interval.nil?
|
|
175
|
+
|
|
176
|
+
@heartbeat = Heartbeat.new(self, interval: config.keepalive_interval, max: config.keepalive_max)
|
|
177
|
+
|
|
178
|
+
@heartbeat.start
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def stop_heartbeat
|
|
182
|
+
@heartbeat&.stop
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def run_layer(layer)
|
|
186
|
+
instance = layer.new(self)
|
|
187
|
+
instance.run
|
|
188
|
+
instance
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|