hrr_rb_ssh 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/.gitignore +27 -0
- data/.rspec +3 -0
- data/.travis.yml +22 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE +201 -0
- data/README.md +47 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/demo/server.rb +134 -0
- data/hrr_rb_ssh.gemspec +27 -0
- data/lib/hrr_rb_ssh/authentication/authenticator.rb +16 -0
- data/lib/hrr_rb_ssh/authentication/method/none/context.rb +28 -0
- data/lib/hrr_rb_ssh/authentication/method/none.rb +38 -0
- data/lib/hrr_rb_ssh/authentication/method/password/context.rb +29 -0
- data/lib/hrr_rb_ssh/authentication/method/password.rb +37 -0
- data/lib/hrr_rb_ssh/authentication/method.rb +21 -0
- data/lib/hrr_rb_ssh/authentication.rb +107 -0
- data/lib/hrr_rb_ssh/closed_authentication_error.rb +7 -0
- data/lib/hrr_rb_ssh/closed_connection_error.rb +7 -0
- data/lib/hrr_rb_ssh/closed_transport_error.rb +7 -0
- data/lib/hrr_rb_ssh/compat.rb +65 -0
- data/lib/hrr_rb_ssh/connection/channel/proc_chain/chain_context.rb +22 -0
- data/lib/hrr_rb_ssh/connection/channel/proc_chain.rb +25 -0
- data/lib/hrr_rb_ssh/connection/channel/session/env/context.rb +43 -0
- data/lib/hrr_rb_ssh/connection/channel/session/env.rb +31 -0
- data/lib/hrr_rb_ssh/connection/channel/session/exec/context.rb +41 -0
- data/lib/hrr_rb_ssh/connection/channel/session/exec.rb +31 -0
- data/lib/hrr_rb_ssh/connection/channel/session/pty_req/context.rb +50 -0
- data/lib/hrr_rb_ssh/connection/channel/session/pty_req.rb +31 -0
- data/lib/hrr_rb_ssh/connection/channel/session/shell/context.rb +37 -0
- data/lib/hrr_rb_ssh/connection/channel/session/shell.rb +31 -0
- data/lib/hrr_rb_ssh/connection/channel/session/subsystem/context.rb +40 -0
- data/lib/hrr_rb_ssh/connection/channel/session/subsystem.rb +31 -0
- data/lib/hrr_rb_ssh/connection/channel/session.rb +31 -0
- data/lib/hrr_rb_ssh/connection/channel.rb +278 -0
- data/lib/hrr_rb_ssh/connection/request_handler.rb +18 -0
- data/lib/hrr_rb_ssh/connection.rb +170 -0
- data/lib/hrr_rb_ssh/logger.rb +52 -0
- data/lib/hrr_rb_ssh/message/001_ssh_msg_disconnect.rb +44 -0
- data/lib/hrr_rb_ssh/message/002_ssh_msg_ignore.rb +24 -0
- data/lib/hrr_rb_ssh/message/003_ssh_msg_unimplemented.rb +24 -0
- data/lib/hrr_rb_ssh/message/004_ssh_msg_debug.rb +26 -0
- data/lib/hrr_rb_ssh/message/005_ssh_msg_service_request.rb +24 -0
- data/lib/hrr_rb_ssh/message/006_ssh_msg_service_accept.rb +24 -0
- data/lib/hrr_rb_ssh/message/020_ssh_msg_kexinit.rb +51 -0
- data/lib/hrr_rb_ssh/message/021_ssh_msg_newkeys.rb +23 -0
- data/lib/hrr_rb_ssh/message/030_ssh_msg_kexdh_init.rb +24 -0
- data/lib/hrr_rb_ssh/message/031_ssh_msg_kexdh_reply.rb +26 -0
- data/lib/hrr_rb_ssh/message/050_ssh_msg_userauth_request.rb +58 -0
- data/lib/hrr_rb_ssh/message/051_ssh_msg_userauth_failure.rb +25 -0
- data/lib/hrr_rb_ssh/message/052_ssh_msg_userauth_success.rb +23 -0
- data/lib/hrr_rb_ssh/message/060_ssh_msg_userauth_pk_ok.rb +25 -0
- data/lib/hrr_rb_ssh/message/080_ssh_msg_global_request.rb +47 -0
- data/lib/hrr_rb_ssh/message/081_ssh_msg_request_success.rb +36 -0
- data/lib/hrr_rb_ssh/message/082_ssh_msg_request_failure.rb +23 -0
- data/lib/hrr_rb_ssh/message/090_ssh_msg_channel_open.rb +67 -0
- data/lib/hrr_rb_ssh/message/091_ssh_msg_channel_open_confirmation.rb +67 -0
- data/lib/hrr_rb_ssh/message/092_ssh_msg_channel_open_failure.rb +34 -0
- data/lib/hrr_rb_ssh/message/093_ssh_msg_channel_window_adjust.rb +25 -0
- data/lib/hrr_rb_ssh/message/094_ssh_msg_channel_data.rb +25 -0
- data/lib/hrr_rb_ssh/message/095_ssh_msg_channel_extended_data.rb +30 -0
- data/lib/hrr_rb_ssh/message/096_ssh_msg_channel_eof.rb +24 -0
- data/lib/hrr_rb_ssh/message/097_ssh_msg_channel_close.rb +24 -0
- data/lib/hrr_rb_ssh/message/098_ssh_msg_channel_request.rb +139 -0
- data/lib/hrr_rb_ssh/message/099_ssh_msg_channel_success.rb +24 -0
- data/lib/hrr_rb_ssh/message/100_ssh_msg_channel_failure.rb +24 -0
- data/lib/hrr_rb_ssh/message/codable.rb +67 -0
- data/lib/hrr_rb_ssh/message.rb +36 -0
- data/lib/hrr_rb_ssh/transport/compression_algorithm/none.rb +33 -0
- data/lib/hrr_rb_ssh/transport/compression_algorithm/zlib.rb +38 -0
- data/lib/hrr_rb_ssh/transport/compression_algorithm.rb +22 -0
- data/lib/hrr_rb_ssh/transport/constant.rb +11 -0
- data/lib/hrr_rb_ssh/transport/data_type.rb +163 -0
- data/lib/hrr_rb_ssh/transport/encryption_algorithm/aes_128_cbc.rb +73 -0
- data/lib/hrr_rb_ssh/transport/encryption_algorithm/none.rb +49 -0
- data/lib/hrr_rb_ssh/transport/encryption_algorithm.rb +22 -0
- data/lib/hrr_rb_ssh/transport/kex_algorithm/diffie_hellman.rb +129 -0
- data/lib/hrr_rb_ssh/transport/kex_algorithm/diffie_hellman_group14_sha1.rb +42 -0
- data/lib/hrr_rb_ssh/transport/kex_algorithm/diffie_hellman_group1_sha1.rb +34 -0
- data/lib/hrr_rb_ssh/transport/kex_algorithm.rb +22 -0
- data/lib/hrr_rb_ssh/transport/mac_algorithm/hmac_sha1.rb +45 -0
- data/lib/hrr_rb_ssh/transport/mac_algorithm/none.rb +40 -0
- data/lib/hrr_rb_ssh/transport/mac_algorithm.rb +22 -0
- data/lib/hrr_rb_ssh/transport/mode.rb +11 -0
- data/lib/hrr_rb_ssh/transport/receiver.rb +75 -0
- data/lib/hrr_rb_ssh/transport/sender.rb +57 -0
- data/lib/hrr_rb_ssh/transport/sequence_number.rb +22 -0
- data/lib/hrr_rb_ssh/transport/server_host_key_algorithm/ssh_rsa.rb +108 -0
- data/lib/hrr_rb_ssh/transport/server_host_key_algorithm.rb +21 -0
- data/lib/hrr_rb_ssh/transport.rb +459 -0
- data/lib/hrr_rb_ssh/version.rb +6 -0
- data/lib/hrr_rb_ssh.rb +13 -0
- metadata +193 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
require 'hrr_rb_ssh/logger'
|
|
5
|
+
require 'hrr_rb_ssh/connection/request_handler'
|
|
6
|
+
require 'hrr_rb_ssh/connection/channel/session/subsystem/context'
|
|
7
|
+
|
|
8
|
+
module HrrRbSsh
|
|
9
|
+
class Connection
|
|
10
|
+
class Channel
|
|
11
|
+
module Session
|
|
12
|
+
request_type = 'subsystem'
|
|
13
|
+
|
|
14
|
+
class Subsystem
|
|
15
|
+
def self.run proc_chain, username, io, variables, message, options
|
|
16
|
+
logger = HrrRbSsh::Logger.new self.class.name
|
|
17
|
+
|
|
18
|
+
context = Context.new proc_chain, username, io, variables, message
|
|
19
|
+
handler = options.fetch('connection_channel_request_subsystem', RequestHandler.new {})
|
|
20
|
+
handler.run context
|
|
21
|
+
|
|
22
|
+
proc_chain.connect context.chain_proc
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@@request_type_list ||= Hash.new
|
|
27
|
+
@@request_type_list[request_type] = Subsystem
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
require 'hrr_rb_ssh/connection/channel/session/pty_req'
|
|
5
|
+
require 'hrr_rb_ssh/connection/channel/session/env'
|
|
6
|
+
require 'hrr_rb_ssh/connection/channel/session/shell'
|
|
7
|
+
require 'hrr_rb_ssh/connection/channel/session/exec'
|
|
8
|
+
require 'hrr_rb_ssh/connection/channel/session/subsystem'
|
|
9
|
+
|
|
10
|
+
module HrrRbSsh
|
|
11
|
+
class Connection
|
|
12
|
+
class Channel
|
|
13
|
+
channel_type = 'session'
|
|
14
|
+
|
|
15
|
+
module Session
|
|
16
|
+
@@request_type_list ||= Hash.new
|
|
17
|
+
|
|
18
|
+
def self.[] key
|
|
19
|
+
@@request_type_list[key]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.request_type_list
|
|
23
|
+
@@request_type_list.keys
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@@type_list ||= Hash.new
|
|
28
|
+
@@type_list[channel_type] = Session
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
require 'socket'
|
|
5
|
+
require 'hrr_rb_ssh/logger'
|
|
6
|
+
require 'hrr_rb_ssh/connection/channel/proc_chain'
|
|
7
|
+
require 'hrr_rb_ssh/connection/channel/session'
|
|
8
|
+
|
|
9
|
+
module HrrRbSsh
|
|
10
|
+
class Connection
|
|
11
|
+
class Channel
|
|
12
|
+
@@type_list ||= Hash.new
|
|
13
|
+
|
|
14
|
+
def self.[] key
|
|
15
|
+
@@type_list[key]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.type_list
|
|
19
|
+
@@type_list.keys
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
INITIAL_WINDOW_SIZE = 100000
|
|
23
|
+
MAXIMUM_PACKET_SIZE = 100000
|
|
24
|
+
|
|
25
|
+
attr_reader \
|
|
26
|
+
:receive_payload_queue
|
|
27
|
+
|
|
28
|
+
def initialize connection, channel_type, local_channel, remote_channel, initial_window_size, maximum_packet_size
|
|
29
|
+
@logger = HrrRbSsh::Logger.new self.class.name
|
|
30
|
+
|
|
31
|
+
@connection = connection
|
|
32
|
+
@channel_type = channel_type
|
|
33
|
+
@local_channel = local_channel
|
|
34
|
+
@remote_channel = remote_channel
|
|
35
|
+
@local_window_size = INITIAL_WINDOW_SIZE
|
|
36
|
+
@local_maximum_packet_size = MAXIMUM_PACKET_SIZE
|
|
37
|
+
@remote_window_size = initial_window_size
|
|
38
|
+
@remote_maximum_packet_size = maximum_packet_size
|
|
39
|
+
|
|
40
|
+
@receive_payload_queue = Queue.new
|
|
41
|
+
@receive_data_queue = Queue.new
|
|
42
|
+
|
|
43
|
+
@proc_chain = ProcChain.new
|
|
44
|
+
@channel_io, @request_handler_io = UNIXSocket.pair
|
|
45
|
+
|
|
46
|
+
@closed = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def start
|
|
50
|
+
@channel_loop_thread = channel_loop_thread
|
|
51
|
+
@sender_thread = sender_thread
|
|
52
|
+
@receiver_thread = receiver_thread
|
|
53
|
+
@proc_chain_thread = proc_chain_thread
|
|
54
|
+
@closed = false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def close from=:outside, exitstatus=0
|
|
58
|
+
return if @closed
|
|
59
|
+
@logger.info("close channel")
|
|
60
|
+
@closed = true
|
|
61
|
+
unless from == :proc_chain_thread
|
|
62
|
+
@proc_chain_thread.exit
|
|
63
|
+
end
|
|
64
|
+
@receive_payload_queue.close
|
|
65
|
+
@receive_data_queue.close
|
|
66
|
+
begin
|
|
67
|
+
@request_handler_io.close
|
|
68
|
+
rescue IOError # for compatibility for Ruby version < 2.3
|
|
69
|
+
Thread.pass
|
|
70
|
+
end
|
|
71
|
+
begin
|
|
72
|
+
@channel_io.close
|
|
73
|
+
rescue IOError # for compatibility for Ruby version < 2.3
|
|
74
|
+
Thread.pass
|
|
75
|
+
end
|
|
76
|
+
begin
|
|
77
|
+
if from == :proc_chain_thread
|
|
78
|
+
send_channel_eof
|
|
79
|
+
case exitstatus
|
|
80
|
+
when Integer
|
|
81
|
+
send_channel_request_exit_status exitstatus
|
|
82
|
+
else
|
|
83
|
+
@logger.warn("skip sending exit-status because exitstatus is not an instance of Integer")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
send_channel_close
|
|
87
|
+
rescue HrrRbSsh::ClosedConnectionError => e
|
|
88
|
+
Thread.pass
|
|
89
|
+
rescue => e
|
|
90
|
+
@logger.error([e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join)
|
|
91
|
+
end
|
|
92
|
+
@logger.info("channel closed")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def closed?
|
|
96
|
+
@closed
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def channel_loop_thread
|
|
100
|
+
Thread.start do
|
|
101
|
+
@logger.info("start channel loop thread")
|
|
102
|
+
variables = {}
|
|
103
|
+
loop do
|
|
104
|
+
begin
|
|
105
|
+
message = @receive_payload_queue.deq
|
|
106
|
+
if message.nil? && @receive_payload_queue.closed?
|
|
107
|
+
@logger.info("closing channel loop thread")
|
|
108
|
+
break
|
|
109
|
+
end
|
|
110
|
+
if message.has_key?(HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST::ID)
|
|
111
|
+
@logger.info("received channel request: #{message['request type']}")
|
|
112
|
+
request message, variables
|
|
113
|
+
if message['want reply']
|
|
114
|
+
send_channel_success
|
|
115
|
+
end
|
|
116
|
+
elsif message.has_key?(HrrRbSsh::Message::SSH_MSG_CHANNEL_DATA::ID)
|
|
117
|
+
@logger.info("received channel data")
|
|
118
|
+
local_channel = message['recipient channel']
|
|
119
|
+
@receive_data_queue.enq message['data']
|
|
120
|
+
elsif message.has_key?(HrrRbSsh::Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::ID)
|
|
121
|
+
@logger.debug("received channel window adjust")
|
|
122
|
+
@remote_window_size = [@remote_window_size + message['bytes to add'], 0xffff_ffff].min
|
|
123
|
+
else
|
|
124
|
+
@logger.warn("received unsupported message: #{message.inspect}")
|
|
125
|
+
end
|
|
126
|
+
rescue => e
|
|
127
|
+
@logger.error([e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join)
|
|
128
|
+
break
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
close from=:channel_loop_thread
|
|
132
|
+
@logger.info("channel loop thread closed")
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def sender_thread
|
|
137
|
+
Thread.start {
|
|
138
|
+
@logger.info("start sender thread")
|
|
139
|
+
loop do
|
|
140
|
+
if @channel_io.closed?
|
|
141
|
+
@logger.info("closing sender thread")
|
|
142
|
+
break
|
|
143
|
+
end
|
|
144
|
+
begin
|
|
145
|
+
data = @channel_io.readpartial(1024)
|
|
146
|
+
sendable_size = [data.size, @remote_window_size].min
|
|
147
|
+
sending_data = data[0, sendable_size]
|
|
148
|
+
send_channel_data sending_data if sendable_size > 0
|
|
149
|
+
@remote_window_size -= sendable_size
|
|
150
|
+
rescue EOFError => e
|
|
151
|
+
begin
|
|
152
|
+
@channel_io.close
|
|
153
|
+
rescue IOError # for compatibility for Ruby version < 2.3
|
|
154
|
+
Thread.pass
|
|
155
|
+
end
|
|
156
|
+
rescue IOError => e
|
|
157
|
+
@logger.warn("channel IO is closed")
|
|
158
|
+
close
|
|
159
|
+
rescue => e
|
|
160
|
+
@logger.error([e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join)
|
|
161
|
+
close
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
@logger.info("sender thread closed")
|
|
165
|
+
}
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def receiver_thread
|
|
169
|
+
Thread.start {
|
|
170
|
+
@logger.info("start receiver thread")
|
|
171
|
+
loop do
|
|
172
|
+
begin
|
|
173
|
+
data = @receive_data_queue.deq
|
|
174
|
+
if data.nil? && @receive_data_queue.closed?
|
|
175
|
+
@logger.info("closing receiver thread")
|
|
176
|
+
break
|
|
177
|
+
end
|
|
178
|
+
@channel_io.write data
|
|
179
|
+
@local_window_size -= data.size
|
|
180
|
+
if @local_window_size < INITIAL_WINDOW_SIZE/2
|
|
181
|
+
@logger.info("send channel window adjust")
|
|
182
|
+
send_channel_window_adjust
|
|
183
|
+
@local_window_size += INITIAL_WINDOW_SIZE
|
|
184
|
+
end
|
|
185
|
+
rescue IOError => e
|
|
186
|
+
@logger.warn("channel IO is closed")
|
|
187
|
+
close
|
|
188
|
+
rescue => e
|
|
189
|
+
@logger.error([e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join)
|
|
190
|
+
close
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
@logger.info("receiver thread closed")
|
|
194
|
+
}
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def proc_chain_thread
|
|
198
|
+
Thread.start {
|
|
199
|
+
@logger.info("start proc chain thread")
|
|
200
|
+
begin
|
|
201
|
+
exitstatus = @proc_chain.call_next
|
|
202
|
+
rescue => e
|
|
203
|
+
@logger.error([e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join)
|
|
204
|
+
exitstatus = 1
|
|
205
|
+
ensure
|
|
206
|
+
@logger.info("closing proc chain thread")
|
|
207
|
+
close from=:proc_chain_thread, exitstatus=exitstatus
|
|
208
|
+
@logger.info("proc chain thread closed")
|
|
209
|
+
end
|
|
210
|
+
}
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def request message, variables
|
|
214
|
+
request_type = message['request type']
|
|
215
|
+
@@type_list[@channel_type][request_type].run @proc_chain, @connection.username, @request_handler_io, variables, message, @connection.options
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def send_channel_success
|
|
219
|
+
message = {
|
|
220
|
+
'SSH_MSG_CHANNEL_SUCCESS' => HrrRbSsh::Message::SSH_MSG_CHANNEL_SUCCESS::VALUE,
|
|
221
|
+
'recipient channel' => @remote_channel,
|
|
222
|
+
}
|
|
223
|
+
payload = HrrRbSsh::Message::SSH_MSG_CHANNEL_SUCCESS.encode message
|
|
224
|
+
@connection.send payload
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def send_channel_window_adjust
|
|
228
|
+
message = {
|
|
229
|
+
'SSH_MSG_CHANNEL_WINDOW_ADJUST' => HrrRbSsh::Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::VALUE,
|
|
230
|
+
'recipient channel' => @remote_channel,
|
|
231
|
+
'bytes to add' => INITIAL_WINDOW_SIZE,
|
|
232
|
+
}
|
|
233
|
+
payload = HrrRbSsh::Message::SSH_MSG_CHANNEL_WINDOW_ADJUST.encode message
|
|
234
|
+
@connection.send payload
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def send_channel_data data
|
|
238
|
+
message = {
|
|
239
|
+
'SSH_MSG_CHANNEL_DATA' => HrrRbSsh::Message::SSH_MSG_CHANNEL_DATA::VALUE,
|
|
240
|
+
'recipient channel' => @remote_channel,
|
|
241
|
+
'data' => data,
|
|
242
|
+
}
|
|
243
|
+
payload = HrrRbSsh::Message::SSH_MSG_CHANNEL_DATA.encode message
|
|
244
|
+
@connection.send payload
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def send_channel_request_exit_status exitstatus
|
|
248
|
+
message = {
|
|
249
|
+
'SSH_MSG_CHANNEL_REQUEST' => HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
|
|
250
|
+
'recipient channel' => @remote_channel,
|
|
251
|
+
'request type' => 'exit-status',
|
|
252
|
+
'want reply' => false,
|
|
253
|
+
'exit status' => exitstatus,
|
|
254
|
+
}
|
|
255
|
+
payload = HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST.encode message
|
|
256
|
+
@connection.send payload
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def send_channel_eof
|
|
260
|
+
message = {
|
|
261
|
+
'SSH_MSG_CHANNEL_EOF' => HrrRbSsh::Message::SSH_MSG_CHANNEL_EOF::VALUE,
|
|
262
|
+
'recipient channel' => @remote_channel,
|
|
263
|
+
}
|
|
264
|
+
payload = HrrRbSsh::Message::SSH_MSG_CHANNEL_EOF.encode message
|
|
265
|
+
@connection.send payload
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def send_channel_close
|
|
269
|
+
message = {
|
|
270
|
+
'SSH_MSG_CHANNEL_CLOSE' => HrrRbSsh::Message::SSH_MSG_CHANNEL_CLOSE::VALUE,
|
|
271
|
+
'recipient channel' => @remote_channel,
|
|
272
|
+
}
|
|
273
|
+
payload = HrrRbSsh::Message::SSH_MSG_CHANNEL_CLOSE.encode message
|
|
274
|
+
@connection.send payload
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
require 'hrr_rb_ssh/logger'
|
|
5
|
+
|
|
6
|
+
module HrrRbSsh
|
|
7
|
+
class Connection
|
|
8
|
+
class RequestHandler
|
|
9
|
+
def initialize &block
|
|
10
|
+
@logger = HrrRbSsh::Logger.new self.class.name
|
|
11
|
+
@proc = block
|
|
12
|
+
end
|
|
13
|
+
def run context
|
|
14
|
+
@proc.call context
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
require 'hrr_rb_ssh/logger'
|
|
5
|
+
require 'hrr_rb_ssh/closed_connection_error'
|
|
6
|
+
require 'hrr_rb_ssh/connection/channel'
|
|
7
|
+
|
|
8
|
+
module HrrRbSsh
|
|
9
|
+
class Connection
|
|
10
|
+
attr_reader \
|
|
11
|
+
:username,
|
|
12
|
+
:options
|
|
13
|
+
|
|
14
|
+
def initialize authentication, options={}
|
|
15
|
+
@logger = HrrRbSsh::Logger.new self.class.name
|
|
16
|
+
|
|
17
|
+
@authentication = authentication
|
|
18
|
+
@options = options
|
|
19
|
+
|
|
20
|
+
@channels = Hash.new
|
|
21
|
+
@username = nil
|
|
22
|
+
@closed = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def send payload
|
|
26
|
+
raise ClosedConnectionError if @closed
|
|
27
|
+
begin
|
|
28
|
+
@authentication.send payload
|
|
29
|
+
rescue ClosedAuthenticationError
|
|
30
|
+
raise ClosedConnectionError
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def start
|
|
35
|
+
@authentication.start
|
|
36
|
+
@closed = false
|
|
37
|
+
connection_loop
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def close
|
|
41
|
+
@closed = true
|
|
42
|
+
@channels.values.each do |channel|
|
|
43
|
+
begin
|
|
44
|
+
channel.close
|
|
45
|
+
rescue => e
|
|
46
|
+
@logger.error([e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
@channels.clear
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def closed?
|
|
53
|
+
@closed
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def connection_loop
|
|
57
|
+
@logger.info("start connection")
|
|
58
|
+
loop do
|
|
59
|
+
begin
|
|
60
|
+
payload = @authentication.receive
|
|
61
|
+
rescue HrrRbSsh::ClosedAuthenticationError => e
|
|
62
|
+
@logger.info("closing connection loop")
|
|
63
|
+
break
|
|
64
|
+
end
|
|
65
|
+
@username ||= @authentication.username
|
|
66
|
+
case payload[0,1].unpack("C")[0]
|
|
67
|
+
when HrrRbSsh::Message::SSH_MSG_GLOBAL_REQUEST::VALUE
|
|
68
|
+
global_request payload
|
|
69
|
+
when HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN::VALUE
|
|
70
|
+
channel_open payload
|
|
71
|
+
when HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST::VALUE
|
|
72
|
+
channel_request payload
|
|
73
|
+
when HrrRbSsh::Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::VALUE
|
|
74
|
+
channel_window_adjust payload
|
|
75
|
+
when HrrRbSsh::Message::SSH_MSG_CHANNEL_DATA::VALUE
|
|
76
|
+
channel_data payload
|
|
77
|
+
when HrrRbSsh::Message::SSH_MSG_CHANNEL_CLOSE::VALUE
|
|
78
|
+
channel_close payload
|
|
79
|
+
else
|
|
80
|
+
@logger.warn("received unsupported message: id: #{payload[0,1].unpack("C")[0]}")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
@logger.info("closing connection")
|
|
84
|
+
close
|
|
85
|
+
@logger.info("connection closed")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def global_request payload
|
|
89
|
+
@logger.info('received ' + HrrRbSsh::Message::SSH_MSG_GLOBAL_REQUEST::ID)
|
|
90
|
+
message = HrrRbSsh::Message::SSH_MSG_GLOBAL_REQUEST.decode payload
|
|
91
|
+
if message['want reply']
|
|
92
|
+
# returns always failure because global request is not supported so far
|
|
93
|
+
send_request_failure
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def channel_open payload
|
|
98
|
+
@logger.info('received ' + HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN::ID)
|
|
99
|
+
message = HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN.decode payload
|
|
100
|
+
channel_type = message['channel type']
|
|
101
|
+
local_channel = message['sender channel']
|
|
102
|
+
remote_channel = message['sender channel']
|
|
103
|
+
initial_window_size = message['initial window size']
|
|
104
|
+
maximum_packet_size = message['maximum packet size']
|
|
105
|
+
channel = Channel.new self, channel_type, local_channel, remote_channel, initial_window_size, maximum_packet_size
|
|
106
|
+
@channels[local_channel] = channel
|
|
107
|
+
channel.start
|
|
108
|
+
send_channel_open_confirmation channel_type, local_channel, remote_channel, initial_window_size, maximum_packet_size
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def channel_request payload
|
|
112
|
+
@logger.info('received ' + HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST::ID)
|
|
113
|
+
message = HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST.decode payload
|
|
114
|
+
local_channel = message['recipient channel']
|
|
115
|
+
@channels[local_channel].receive_payload_queue.enq message
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def channel_window_adjust payload
|
|
119
|
+
@logger.info('received ' + HrrRbSsh::Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::ID)
|
|
120
|
+
message = HrrRbSsh::Message::SSH_MSG_CHANNEL_WINDOW_ADJUST.decode payload
|
|
121
|
+
local_channel = message['recipient channel']
|
|
122
|
+
@channels[local_channel].receive_payload_queue.enq message
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def channel_data payload
|
|
126
|
+
@logger.info('received ' + HrrRbSsh::Message::SSH_MSG_CHANNEL_DATA::ID)
|
|
127
|
+
message = HrrRbSsh::Message::SSH_MSG_CHANNEL_DATA.decode payload
|
|
128
|
+
local_channel = message['recipient channel']
|
|
129
|
+
@channels[local_channel].receive_payload_queue.enq message
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def channel_close payload
|
|
133
|
+
@logger.info('received ' + HrrRbSsh::Message::SSH_MSG_CHANNEL_CLOSE::ID)
|
|
134
|
+
message = HrrRbSsh::Message::SSH_MSG_CHANNEL_CLOSE.decode payload
|
|
135
|
+
local_channel = message['recipient channel']
|
|
136
|
+
channel = @channels[local_channel]
|
|
137
|
+
channel.close
|
|
138
|
+
@channels.delete local_channel
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def send_request_success
|
|
142
|
+
message = {
|
|
143
|
+
'SSH_MSG_REQUEST_SUCCESS' => HrrRbSsh::Message::SSH_MSG_REQUEST_SUCCESS::VALUE,
|
|
144
|
+
}
|
|
145
|
+
payload = HrrRbSsh::Message::SSH_MSG_REQUEST_SUCCESS.encode message
|
|
146
|
+
@authentication.send payload
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def send_request_failure
|
|
150
|
+
message = {
|
|
151
|
+
'SSH_MSG_REQUEST_FAILURE' => HrrRbSsh::Message::SSH_MSG_REQUEST_FAILURE::VALUE,
|
|
152
|
+
}
|
|
153
|
+
payload = HrrRbSsh::Message::SSH_MSG_REQUEST_FAILURE.encode message
|
|
154
|
+
@authentication.send payload
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def send_channel_open_confirmation channel_type, local_channel, remote_channel, initial_window_size, maximum_packet_size
|
|
158
|
+
message = {
|
|
159
|
+
'SSH_MSG_CHANNEL_OPEN_CONFIRMATION' => HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION::VALUE,
|
|
160
|
+
'channel type' => channel_type,
|
|
161
|
+
'recipient channel' => remote_channel,
|
|
162
|
+
'sender channel' => local_channel,
|
|
163
|
+
'initial window size' => initial_window_size,
|
|
164
|
+
'maximum packet size' => maximum_packet_size,
|
|
165
|
+
}
|
|
166
|
+
payload = HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION.encode message
|
|
167
|
+
@authentication.send payload
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
module HrrRbSsh
|
|
5
|
+
class Logger
|
|
6
|
+
def self.initialize logger
|
|
7
|
+
@@logger = logger
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.uninitialize
|
|
11
|
+
@@logger = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.initialized?
|
|
15
|
+
@@logger != nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize name
|
|
19
|
+
@name = name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fatal message
|
|
23
|
+
if self.class.initialized?
|
|
24
|
+
@@logger.fatal "#{@name}: #{message}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def error message
|
|
29
|
+
if self.class.initialized?
|
|
30
|
+
@@logger.error "#{@name}: #{message}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def warn message
|
|
35
|
+
if self.class.initialized?
|
|
36
|
+
@@logger.warn "#{@name}: #{message}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def info message
|
|
41
|
+
if self.class.initialized?
|
|
42
|
+
@@logger.info "#{@name}: #{message}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def debug message
|
|
47
|
+
if self.class.initialized?
|
|
48
|
+
@@logger.debug "#{@name}: #{message}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
require 'hrr_rb_ssh/logger'
|
|
5
|
+
require 'hrr_rb_ssh/message/codable'
|
|
6
|
+
|
|
7
|
+
module HrrRbSsh
|
|
8
|
+
module Message
|
|
9
|
+
module SSH_MSG_DISCONNECT
|
|
10
|
+
module ReasonCode
|
|
11
|
+
SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
|
|
12
|
+
SSH_DISCONNECT_PROTOCOL_ERROR = 2
|
|
13
|
+
SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3
|
|
14
|
+
SSH_DISCONNECT_RESERVED = 4
|
|
15
|
+
SSH_DISCONNECT_MAC_ERROR = 5
|
|
16
|
+
SSH_DISCONNECT_COMPRESSION_ERROR = 6
|
|
17
|
+
SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7
|
|
18
|
+
SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
|
|
19
|
+
SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
|
|
20
|
+
SSH_DISCONNECT_CONNECTION_LOST = 10
|
|
21
|
+
SSH_DISCONNECT_BY_APPLICATION = 11
|
|
22
|
+
SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12
|
|
23
|
+
SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13
|
|
24
|
+
SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
|
|
25
|
+
SSH_DISCONNECT_ILLEGAL_USER_NAME = 15
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
include Codable
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
ID = self.name.split('::').last
|
|
33
|
+
VALUE = 1
|
|
34
|
+
|
|
35
|
+
DEFINITION = [
|
|
36
|
+
# [Data Type, Field Name]
|
|
37
|
+
['byte', 'SSH_MSG_DISCONNECT'],
|
|
38
|
+
['uint32', 'reason code'],
|
|
39
|
+
['string', 'description'],
|
|
40
|
+
['string', 'language tag'],
|
|
41
|
+
]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
require 'hrr_rb_ssh/logger'
|
|
5
|
+
require 'hrr_rb_ssh/message/codable'
|
|
6
|
+
|
|
7
|
+
module HrrRbSsh
|
|
8
|
+
module Message
|
|
9
|
+
module SSH_MSG_IGNORE
|
|
10
|
+
class << self
|
|
11
|
+
include Codable
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
ID = self.name.split('::').last
|
|
15
|
+
VALUE = 2
|
|
16
|
+
|
|
17
|
+
DEFINITION = [
|
|
18
|
+
# [Data Type, Field Name]
|
|
19
|
+
['byte', 'SSH_MSG_IGNORE'],
|
|
20
|
+
['string', 'data'],
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# vim: et ts=2 sw=2
|
|
3
|
+
|
|
4
|
+
require 'hrr_rb_ssh/logger'
|
|
5
|
+
require 'hrr_rb_ssh/message/codable'
|
|
6
|
+
|
|
7
|
+
module HrrRbSsh
|
|
8
|
+
module Message
|
|
9
|
+
module SSH_MSG_UNIMPLEMENTED
|
|
10
|
+
class << self
|
|
11
|
+
include Codable
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
ID = self.name.split('::').last
|
|
15
|
+
VALUE = 3
|
|
16
|
+
|
|
17
|
+
DEFINITION = [
|
|
18
|
+
# [Data Type, Field Name]
|
|
19
|
+
['byte', 'SSH_MSG_UNIMPLEMENTED'],
|
|
20
|
+
['uint32', 'packet sequence number of rejected message'],
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|