hrr_rb_ssh 0.3.0.pre1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -3
  3. data/.travis.yml +1 -0
  4. data/README.md +208 -46
  5. data/demo/client.rb +71 -0
  6. data/demo/echo_server.rb +8 -3
  7. data/demo/more_flexible_auth.rb +105 -0
  8. data/demo/multi_step_auth.rb +99 -0
  9. data/demo/server.rb +10 -4
  10. data/demo/subsystem_echo_server.rb +8 -3
  11. data/hrr_rb_ssh.gemspec +6 -6
  12. data/lib/hrr_rb_ssh.rb +1 -1
  13. data/lib/hrr_rb_ssh/algorithm/publickey.rb +0 -1
  14. data/lib/hrr_rb_ssh/algorithm/publickey/ecdsa_sha2.rb +12 -9
  15. data/lib/hrr_rb_ssh/algorithm/publickey/ecdsa_sha2/ecdsa_signature_blob.rb +2 -4
  16. data/lib/hrr_rb_ssh/algorithm/publickey/ecdsa_sha2/public_key_blob.rb +2 -4
  17. data/lib/hrr_rb_ssh/algorithm/publickey/ecdsa_sha2/signature.rb +2 -4
  18. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_dss.rb +10 -7
  19. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_dss/public_key_blob.rb +2 -4
  20. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_dss/signature.rb +2 -4
  21. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_rsa.rb +9 -6
  22. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_rsa/public_key_blob.rb +2 -4
  23. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_rsa/signature.rb +2 -4
  24. data/lib/hrr_rb_ssh/authentication.rb +103 -22
  25. data/lib/hrr_rb_ssh/authentication/constant.rb +14 -0
  26. data/lib/hrr_rb_ssh/authentication/method/keyboard_interactive.rb +44 -7
  27. data/lib/hrr_rb_ssh/authentication/method/keyboard_interactive/context.rb +16 -9
  28. data/lib/hrr_rb_ssh/authentication/method/keyboard_interactive/info_request.rb +7 -6
  29. data/lib/hrr_rb_ssh/authentication/method/keyboard_interactive/info_response.rb +5 -2
  30. data/lib/hrr_rb_ssh/authentication/method/none.rb +23 -7
  31. data/lib/hrr_rb_ssh/authentication/method/none/context.rb +15 -7
  32. data/lib/hrr_rb_ssh/authentication/method/password.rb +28 -7
  33. data/lib/hrr_rb_ssh/authentication/method/password/context.rb +16 -7
  34. data/lib/hrr_rb_ssh/authentication/method/publickey.rb +63 -10
  35. data/lib/hrr_rb_ssh/authentication/method/publickey/algorithm.rb +0 -1
  36. data/lib/hrr_rb_ssh/authentication/method/publickey/algorithm/functionable.rb +32 -8
  37. data/lib/hrr_rb_ssh/authentication/method/publickey/algorithm/signature_blob.rb +2 -4
  38. data/lib/hrr_rb_ssh/authentication/method/publickey/context.rb +11 -2
  39. data/lib/hrr_rb_ssh/client.rb +234 -0
  40. data/lib/hrr_rb_ssh/codable.rb +15 -13
  41. data/lib/hrr_rb_ssh/compat/ruby.rb +0 -1
  42. data/lib/hrr_rb_ssh/connection.rb +145 -75
  43. data/lib/hrr_rb_ssh/connection/channel.rb +342 -109
  44. data/lib/hrr_rb_ssh/connection/channel/channel_type/direct_tcpip.rb +24 -19
  45. data/lib/hrr_rb_ssh/connection/channel/channel_type/forwarded_tcpip.rb +24 -19
  46. data/lib/hrr_rb_ssh/connection/channel/channel_type/session.rb +19 -12
  47. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/proc_chain.rb +0 -2
  48. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/proc_chain/chain_context.rb +0 -3
  49. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/env.rb +2 -5
  50. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/env/context.rb +5 -4
  51. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/exec.rb +2 -5
  52. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/exec/context.rb +5 -4
  53. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/pty_req.rb +2 -5
  54. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/pty_req/context.rb +5 -4
  55. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/shell.rb +2 -5
  56. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/shell/context.rb +5 -4
  57. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/subsystem.rb +2 -5
  58. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/subsystem/context.rb +5 -4
  59. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/window_change.rb +2 -5
  60. data/lib/hrr_rb_ssh/connection/channel/channel_type/session/request_type/window_change/context.rb +5 -4
  61. data/lib/hrr_rb_ssh/connection/global_request_handler.rb +14 -12
  62. data/lib/hrr_rb_ssh/connection/request_handler.rb +1 -3
  63. data/lib/hrr_rb_ssh/connection/request_handler/reference_env_request_handler.rb +0 -2
  64. data/lib/hrr_rb_ssh/connection/request_handler/reference_exec_request_handler.rb +4 -6
  65. data/lib/hrr_rb_ssh/connection/request_handler/reference_pty_req_request_handler.rb +10 -12
  66. data/lib/hrr_rb_ssh/connection/request_handler/reference_shell_request_handler.rb +4 -6
  67. data/lib/hrr_rb_ssh/connection/request_handler/reference_window_change_request_handler.rb +0 -2
  68. data/lib/hrr_rb_ssh/error/closed_authentication.rb +1 -1
  69. data/lib/hrr_rb_ssh/error/closed_connection.rb +1 -1
  70. data/lib/hrr_rb_ssh/error/closed_transport.rb +1 -1
  71. data/lib/hrr_rb_ssh/loggable.rb +42 -0
  72. data/lib/hrr_rb_ssh/message/001_ssh_msg_disconnect.rb +2 -4
  73. data/lib/hrr_rb_ssh/message/002_ssh_msg_ignore.rb +2 -4
  74. data/lib/hrr_rb_ssh/message/003_ssh_msg_unimplemented.rb +2 -4
  75. data/lib/hrr_rb_ssh/message/004_ssh_msg_debug.rb +2 -4
  76. data/lib/hrr_rb_ssh/message/005_ssh_msg_service_request.rb +2 -4
  77. data/lib/hrr_rb_ssh/message/006_ssh_msg_service_accept.rb +2 -4
  78. data/lib/hrr_rb_ssh/message/020_ssh_msg_kexinit.rb +2 -4
  79. data/lib/hrr_rb_ssh/message/021_ssh_msg_newkeys.rb +2 -4
  80. data/lib/hrr_rb_ssh/message/030_ssh_msg_kex_dh_gex_request_old.rb +2 -4
  81. data/lib/hrr_rb_ssh/message/030_ssh_msg_kexdh_init.rb +2 -4
  82. data/lib/hrr_rb_ssh/message/030_ssh_msg_kexecdh_init.rb +2 -4
  83. data/lib/hrr_rb_ssh/message/031_ssh_msg_kex_dh_gex_group.rb +2 -4
  84. data/lib/hrr_rb_ssh/message/031_ssh_msg_kexdh_reply.rb +2 -4
  85. data/lib/hrr_rb_ssh/message/031_ssh_msg_kexecdh_reply.rb +2 -4
  86. data/lib/hrr_rb_ssh/message/032_ssh_msg_kex_dh_gex_init.rb +2 -4
  87. data/lib/hrr_rb_ssh/message/033_ssh_msg_kex_dh_gex_reply.rb +2 -4
  88. data/lib/hrr_rb_ssh/message/034_ssh_msg_kex_dh_gex_request.rb +2 -4
  89. data/lib/hrr_rb_ssh/message/050_ssh_msg_userauth_request.rb +2 -4
  90. data/lib/hrr_rb_ssh/message/051_ssh_msg_userauth_failure.rb +2 -4
  91. data/lib/hrr_rb_ssh/message/052_ssh_msg_userauth_success.rb +2 -4
  92. data/lib/hrr_rb_ssh/message/060_ssh_msg_userauth_info_request.rb +2 -4
  93. data/lib/hrr_rb_ssh/message/060_ssh_msg_userauth_pk_ok.rb +2 -4
  94. data/lib/hrr_rb_ssh/message/061_ssh_msg_userauth_info_response.rb +2 -4
  95. data/lib/hrr_rb_ssh/message/080_ssh_msg_global_request.rb +2 -4
  96. data/lib/hrr_rb_ssh/message/081_ssh_msg_request_success.rb +2 -4
  97. data/lib/hrr_rb_ssh/message/082_ssh_msg_request_failure.rb +2 -4
  98. data/lib/hrr_rb_ssh/message/090_ssh_msg_channel_open.rb +2 -4
  99. data/lib/hrr_rb_ssh/message/091_ssh_msg_channel_open_confirmation.rb +2 -4
  100. data/lib/hrr_rb_ssh/message/092_ssh_msg_channel_open_failure.rb +2 -4
  101. data/lib/hrr_rb_ssh/message/093_ssh_msg_channel_window_adjust.rb +2 -4
  102. data/lib/hrr_rb_ssh/message/094_ssh_msg_channel_data.rb +2 -4
  103. data/lib/hrr_rb_ssh/message/095_ssh_msg_channel_extended_data.rb +2 -4
  104. data/lib/hrr_rb_ssh/message/096_ssh_msg_channel_eof.rb +2 -4
  105. data/lib/hrr_rb_ssh/message/097_ssh_msg_channel_close.rb +2 -4
  106. data/lib/hrr_rb_ssh/message/098_ssh_msg_channel_request.rb +3 -5
  107. data/lib/hrr_rb_ssh/message/099_ssh_msg_channel_success.rb +2 -4
  108. data/lib/hrr_rb_ssh/message/100_ssh_msg_channel_failure.rb +2 -4
  109. data/lib/hrr_rb_ssh/server.rb +16 -10
  110. data/lib/hrr_rb_ssh/transport.rb +113 -77
  111. data/lib/hrr_rb_ssh/transport/compression_algorithm/functionable.rb +5 -3
  112. data/lib/hrr_rb_ssh/transport/compression_algorithm/unfunctionable.rb +5 -3
  113. data/lib/hrr_rb_ssh/transport/encryption_algorithm/functionable.rb +5 -3
  114. data/lib/hrr_rb_ssh/transport/encryption_algorithm/unfunctionable.rb +5 -3
  115. data/lib/hrr_rb_ssh/transport/kex_algorithm/diffie_hellman.rb +43 -37
  116. data/lib/hrr_rb_ssh/transport/kex_algorithm/diffie_hellman/h0.rb +2 -4
  117. data/lib/hrr_rb_ssh/transport/kex_algorithm/diffie_hellman_group_exchange.rb +87 -52
  118. data/lib/hrr_rb_ssh/transport/kex_algorithm/diffie_hellman_group_exchange/h0.rb +2 -4
  119. data/lib/hrr_rb_ssh/transport/kex_algorithm/elliptic_curve_diffie_hellman.rb +43 -37
  120. data/lib/hrr_rb_ssh/transport/kex_algorithm/elliptic_curve_diffie_hellman/h0.rb +2 -4
  121. data/lib/hrr_rb_ssh/transport/mac_algorithm/functionable.rb +5 -3
  122. data/lib/hrr_rb_ssh/transport/mac_algorithm/unfunctionable.rb +5 -3
  123. data/lib/hrr_rb_ssh/transport/receiver.rb +8 -7
  124. data/lib/hrr_rb_ssh/transport/sender.rb +5 -3
  125. data/lib/hrr_rb_ssh/transport/sequence_number.rb +0 -4
  126. data/lib/hrr_rb_ssh/transport/server_host_key_algorithm.rb +0 -1
  127. data/lib/hrr_rb_ssh/transport/server_host_key_algorithm/functionable.rb +5 -3
  128. data/lib/hrr_rb_ssh/version.rb +1 -1
  129. metadata +18 -51
  130. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_ed25519.rb +0 -61
  131. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_ed25519/openssh_private_key.rb +0 -29
  132. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_ed25519/openssh_private_key_content.rb +0 -26
  133. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_ed25519/pkey.rb +0 -158
  134. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_ed25519/public_key_blob.rb +0 -23
  135. data/lib/hrr_rb_ssh/algorithm/publickey/ssh_ed25519/signature.rb +0 -23
  136. data/lib/hrr_rb_ssh/authentication/method/publickey/algorithm/ssh_ed25519.rb +0 -21
  137. data/lib/hrr_rb_ssh/compat/ruby/array.rb +0 -14
  138. data/lib/hrr_rb_ssh/logger.rb +0 -56
  139. data/lib/hrr_rb_ssh/transport/server_host_key_algorithm/ssh_ed25519.rb +0 -20
@@ -9,10 +9,8 @@ module HrrRbSsh
9
9
  class Method
10
10
  class Publickey
11
11
  class Algorithm
12
- module SignatureBlob
13
- class << self
14
- include Codable
15
- end
12
+ class SignatureBlob
13
+ include Codable
16
14
  DEFINITION = [
17
15
  [DataType::String, :'session identifier'],
18
16
  [DataType::Byte, :'message number'],
@@ -1,16 +1,21 @@
1
1
  # coding: utf-8
2
2
  # vim: et ts=2 sw=2
3
3
 
4
- require 'hrr_rb_ssh/logger'
4
+ require 'hrr_rb_ssh/loggable'
5
5
 
6
6
  module HrrRbSsh
7
7
  class Authentication
8
8
  class Method
9
9
  class Publickey
10
10
  class Context
11
+ include Loggable
12
+
11
13
  attr_reader \
12
14
  :username,
13
15
  :session_id,
16
+ :variables,
17
+ :vars,
18
+ :authentication_methods,
14
19
  :message_number,
15
20
  :service_name,
16
21
  :method_name,
@@ -19,11 +24,15 @@ module HrrRbSsh
19
24
  :public_key_blob,
20
25
  :signature
21
26
 
22
- def initialize username, algorithm, session_id, message
27
+ def initialize username, algorithm, session_id, message, variables, authentication_methods, logger: nil
28
+ self.logger = logger
23
29
  @username = username
24
30
  @algorithm = algorithm
25
31
  @session_id = session_id
26
32
  @message = message
33
+ @variables = variables
34
+ @vars = variables
35
+ @authentication_methods = authentication_methods
27
36
 
28
37
  @message_number = message[:'message number']
29
38
  @service_name = message[:'service name']
@@ -0,0 +1,234 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'socket'
5
+ require 'stringio'
6
+ require 'hrr_rb_ssh/loggable'
7
+ require 'hrr_rb_ssh/mode'
8
+ require 'hrr_rb_ssh/transport'
9
+ require 'hrr_rb_ssh/authentication'
10
+ require 'hrr_rb_ssh/connection'
11
+
12
+ module HrrRbSsh
13
+ class Client
14
+ include Loggable
15
+
16
+ def self.start target, options={}
17
+ client = self.new target, options
18
+ client.start
19
+ if block_given?
20
+ begin
21
+ yield client
22
+ ensure
23
+ client.close unless client.closed?
24
+ end
25
+ else
26
+ client
27
+ end
28
+ end
29
+
30
+ def initialize target, tmp_options={}
31
+ @closed = true
32
+ options = initialize_options tmp_options
33
+ io = case target
34
+ when IO
35
+ target
36
+ when Array
37
+ io = TCPSocket.new *target
38
+ when String
39
+ port = 22
40
+ io = TCPSocket.new target, port
41
+ end
42
+ transport = HrrRbSsh::Transport.new io, HrrRbSsh::Mode::CLIENT, options, logger: logger
43
+ authentication = HrrRbSsh::Authentication.new transport, HrrRbSsh::Mode::CLIENT, options, logger: logger
44
+ @connection = HrrRbSsh::Connection.new authentication, HrrRbSsh::Mode::CLIENT, options, logger: logger
45
+ end
46
+
47
+ def initialize_options tmp_options
48
+ tmp_options = Hash[tmp_options.map{|k, v| [k.to_s, v]}]
49
+ self.logger = tmp_options['logger']
50
+ options = {}
51
+ options['username'] = tmp_options['username']
52
+ options['authentication_preferred_authentication_methods'] = tmp_options['authentication_preferred_authentication_methods']
53
+ options['client_authentication_password'] = tmp_options['password']
54
+ options['client_authentication_publickey'] = tmp_options['publickey']
55
+ options['client_authentication_keyboard_interactive'] = tmp_options['keyboard_interactive']
56
+ options['transport_preferred_encryption_algorithms'] = tmp_options['transport_preferred_encryption_algorithms']
57
+ options['transport_preferred_server_host_key_algorithms'] = tmp_options['transport_preferred_server_host_key_algorithms']
58
+ options['transport_preferred_kex_algorithms'] = tmp_options['transport_preferred_kex_algorithms']
59
+ options['transport_preferred_mac_algorithms'] = tmp_options['transport_preferred_mac_algorithms']
60
+ options['transport_preferred_compression_algorithms'] = tmp_options['transport_preferred_compression_algorithms']
61
+ options
62
+ end
63
+
64
+ def start
65
+ @connection.start foreground: false
66
+ @closed = false
67
+ end
68
+
69
+ def loop
70
+ @connection.loop
71
+ end
72
+
73
+ def closed?
74
+ @closed
75
+ end
76
+
77
+ def close
78
+ log_info { "closing client" }
79
+ @closed = true
80
+ @connection.close
81
+ log_info { "client closed" }
82
+ end
83
+
84
+ def exec! command, pty: false, env: {}
85
+ log_info { "start exec!: #{command}" }
86
+ out_buf = StringIO.new
87
+ err_buf = StringIO.new
88
+ begin
89
+ log_info { "Opning channel" }
90
+ channel = @connection.request_channel_open "session"
91
+ log_info { "Channel opened" }
92
+ if pty
93
+ channel.send_channel_request_pty_req 'xterm', 80, 24, 580, 336, ''
94
+ end
95
+ env.each{ |variable_name, variable_value|
96
+ channel.send_channel_request_env variable_name, variable_value
97
+ }
98
+ channel.send_channel_request_exec command
99
+ out_t = Thread.new {
100
+ while true
101
+ begin
102
+ out_buf.write channel.io[1].readpartial(10240)
103
+ rescue
104
+ break
105
+ end
106
+ end
107
+ }
108
+ err_t = Thread.new {
109
+ while true
110
+ begin
111
+ err_buf.write channel.io[2].readpartial(10240)
112
+ rescue
113
+ break
114
+ end
115
+ end
116
+ }
117
+ [out_t, err_t].each{ |t|
118
+ begin
119
+ t.join
120
+ rescue => e
121
+ log_warn { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
122
+ end
123
+ }
124
+ rescue => e
125
+ log_error { "Failed opening channel" }
126
+ log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
127
+ raise "Error in exec!"
128
+ ensure
129
+ if channel
130
+ log_info { "closing channel IOs" }
131
+ channel.io.each{ |io| io.close rescue nil }
132
+ log_info { "channel IOs closed" }
133
+ log_info { "closing channel" }
134
+ log_info { "wait until threads closed in channel" }
135
+ channel.wait_until_closed
136
+ channel.close
137
+ log_info { "channel closed" }
138
+ end
139
+ end
140
+ [out_buf.string, err_buf.string]
141
+ end
142
+
143
+ def exec command, pty: false, env: {}
144
+ log_info { "start exec: #{command}" }
145
+ begin
146
+ log_info { "Opning channel" }
147
+ channel = @connection.request_channel_open "session"
148
+ log_info { "Channel opened" }
149
+ if pty
150
+ channel.send_channel_request_pty_req 'xterm', 80, 24, 580, 336, ''
151
+ end
152
+ env.each{ |variable_name, variable_value|
153
+ channel.send_channel_request_env variable_name, variable_value
154
+ }
155
+ channel.send_channel_request_exec command
156
+ yield channel.io
157
+ rescue => e
158
+ log_error { "Failed opening channel" }
159
+ log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
160
+ raise "Error in shell"
161
+ ensure
162
+ if channel
163
+ log_info { "closing channel IOs" }
164
+ channel.io.each{ |io| io.close rescue nil }
165
+ log_info { "channel IOs closed" }
166
+ log_info { "closing channel" }
167
+ log_info { "wait until threads closed in channel" }
168
+ channel.wait_until_closed
169
+ channel.close
170
+ log_info { "channel closed" }
171
+ end
172
+ end
173
+ channel_exit_status = channel.exit_status rescue nil
174
+ end
175
+
176
+ def shell env: {}
177
+ log_info { "start shell" }
178
+ begin
179
+ log_info { "Opning channel" }
180
+ channel = @connection.request_channel_open "session"
181
+ log_info { "Channel opened" }
182
+ channel.send_channel_request_pty_req 'xterm', 80, 24, 580, 336, ''
183
+ env.each{ |variable_name, variable_value|
184
+ channel.send_channel_request_env variable_name, variable_value
185
+ }
186
+ channel.send_channel_request_shell
187
+ yield channel.io
188
+ rescue => e
189
+ log_error { "Failed opening channel" }
190
+ log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
191
+ raise "Error in shell"
192
+ ensure
193
+ if channel
194
+ log_info { "closing channel IOs" }
195
+ channel.io.each{ |io| io.close rescue nil }
196
+ log_info { "channel IOs closed" }
197
+ log_info { "closing channel" }
198
+ log_info { "wait until threads closed in channel" }
199
+ channel.wait_until_closed
200
+ channel.close
201
+ log_info { "channel closed" }
202
+ end
203
+ end
204
+ channel_exit_status = channel.exit_status rescue nil
205
+ end
206
+
207
+ def subsystem name
208
+ log_info { "start subsystem" }
209
+ begin
210
+ log_info { "Opning channel" }
211
+ channel = @connection.request_channel_open "session"
212
+ log_info { "Channel opened" }
213
+ channel.send_channel_request_subsystem name
214
+ yield channel.io
215
+ rescue => e
216
+ log_error { "Failed opening channel" }
217
+ log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
218
+ raise "Error in subsystem"
219
+ ensure
220
+ if channel
221
+ log_info { "closing channel IOs" }
222
+ channel.io.each{ |io| io.close rescue nil }
223
+ log_info { "channel IOs closed" }
224
+ log_info { "closing channel" }
225
+ log_info { "wait until threads closed in channel" }
226
+ channel.wait_until_closed
227
+ channel.close
228
+ log_info { "channel closed" }
229
+ end
230
+ end
231
+ channel_exit_status = channel.exit_status rescue nil
232
+ end
233
+ end
234
+ end
@@ -1,36 +1,38 @@
1
1
  # coding: utf-8
2
2
  # vim: et ts=2 sw=2
3
3
 
4
- require 'hrr_rb_ssh/logger'
4
+ require 'hrr_rb_ssh/loggable'
5
5
 
6
6
  module HrrRbSsh
7
7
  module Codable
8
- def logger
9
- @logger ||= Logger.new self.name
8
+ include Loggable
9
+
10
+ def initialize logger: nil
11
+ self.logger = logger
10
12
  end
11
13
 
12
14
  def common_definition
13
- self::DEFINITION
15
+ self.class::DEFINITION
14
16
  end
15
17
 
16
18
  def conditional_definition message
17
- return [] unless self.const_defined? :CONDITIONAL_DEFINITION
19
+ return [] unless self.class.const_defined? :CONDITIONAL_DEFINITION
18
20
  message.inject([]){ |a, (k,v)|
19
21
  field_name = k
20
22
  field_value = if v.instance_of? ::Proc then v.call else v end
21
- a + (self::CONDITIONAL_DEFINITION.fetch(field_name, {})[field_value] || [])
23
+ a + (self.class::CONDITIONAL_DEFINITION.fetch(field_name, {})[field_value] || [])
22
24
  }
23
25
  end
24
26
 
25
27
  def encode message, complementary_message={}
26
- logger.debug { 'encoding message: ' + message.inspect }
28
+ log_debug { 'encoding message: ' + message.inspect }
27
29
  definition = common_definition + conditional_definition(message.merge complementary_message)
28
30
  definition.map{ |data_type, field_name|
29
31
  begin
30
32
  field_value = if message[field_name].instance_of? ::Proc then message[field_name].call else message[field_name] end
31
- data_type.encode( field_value )
33
+ data_type.encode field_value
32
34
  rescue => e
33
- logger.debug { "'field_name', 'field_value': #{field_name.inspect}, #{field_value.inspect}" }
35
+ log_debug { "'field_name', 'field_value': #{field_name.inspect}, #{field_value.inspect}" }
34
36
  raise e
35
37
  end
36
38
  }.join
@@ -49,7 +51,7 @@ module HrrRbSsh
49
51
  decoded_message = definition.map{ |data_type, field_name|
50
52
  [
51
53
  field_name,
52
- data_type.decode( payload_io )
54
+ data_type.decode(payload_io)
53
55
  ]
54
56
  }
55
57
  decoded_message + decode_recursively(payload_io, decoded_message)
@@ -58,11 +60,11 @@ module HrrRbSsh
58
60
 
59
61
  def decode payload, complementary_message={}
60
62
  payload_io = StringIO.new payload
61
- decoded_message = decode_recursively(payload_io).to_h
63
+ decoded_message = decode_recursively(payload_io).inject(Hash.new){ |h, (k, v)| h.update({k => v}) }
62
64
  if complementary_message.any?
63
- decoded_message.merge! decode_recursively(payload_io, complementary_message.to_a).to_h
65
+ decoded_message.merge! decode_recursively(payload_io, complementary_message.to_a).inject(Hash.new){ |h, (k, v)| h.update({k => v}) }
64
66
  end
65
- logger.debug { 'decoded message: ' + decoded_message.inspect }
67
+ log_debug { 'decoded message: ' + decoded_message.inspect }
66
68
  decoded_message
67
69
  end
68
70
  end
@@ -1,6 +1,5 @@
1
1
  # coding: utf-8
2
2
  # vim: et ts=2 sw=2
3
3
 
4
- require 'hrr_rb_ssh/compat/ruby/array'
5
4
  require 'hrr_rb_ssh/compat/ruby/openssl'
6
5
  require 'hrr_rb_ssh/compat/ruby/queue'
@@ -1,26 +1,30 @@
1
1
  # coding: utf-8
2
2
  # vim: et ts=2 sw=2
3
3
 
4
- require 'hrr_rb_ssh/logger'
4
+ require 'hrr_rb_ssh/loggable'
5
5
  require 'hrr_rb_ssh/error/closed_connection'
6
6
  require 'hrr_rb_ssh/connection/global_request_handler'
7
7
  require 'hrr_rb_ssh/connection/channel'
8
8
 
9
9
  module HrrRbSsh
10
10
  class Connection
11
+ include Loggable
12
+
11
13
  attr_reader \
12
14
  :username,
13
- :options
14
-
15
- def initialize authentication, options={}
16
- @logger = Logger.new self.class.name
15
+ :variables,
16
+ :options,
17
+ :mode
17
18
 
19
+ def initialize authentication, mode, options={}, logger: nil
20
+ self.logger = logger
18
21
  @authentication = authentication
22
+ @mode = mode
19
23
  @options = options
20
-
21
- @global_request_handler = GlobalRequestHandler.new self
24
+ @global_request_handler = GlobalRequestHandler.new self, logger: logger
22
25
  @channels = Hash.new
23
26
  @username = nil
27
+ @variables = nil
24
28
  @closed = nil
25
29
  end
26
30
 
@@ -36,75 +40,103 @@ module HrrRbSsh
36
40
  def assign_channel
37
41
  i = 0
38
42
  res = nil
39
- loop do
43
+ while true
40
44
  break unless @channels.keys.include?(i)
41
45
  i += 1
42
46
  end
43
47
  i
44
48
  end
45
49
 
46
- def start
47
- @authentication.start
50
+ def start foreground: true
51
+ log_info { "start connection" }
52
+ begin
53
+ @authentication.start
54
+ rescue Error::ClosedAuthentication
55
+ close
56
+ raise Error::ClosedConnection
57
+ end
48
58
  @closed = false
49
- connection_loop
59
+ @connection_loop_thread = connection_loop_thread
60
+ if foreground
61
+ @connection_loop_thread.join
62
+ end
63
+ end
64
+
65
+ def loop
66
+ @connection_loop_thread.join
50
67
  end
51
68
 
52
69
  def close
70
+ return if @closed
71
+ log_info { "close connection" }
53
72
  @closed = true
73
+ @authentication.close
54
74
  @channels.values.each do |channel|
55
75
  begin
56
76
  channel.close
57
77
  rescue => e
58
- @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
78
+ log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
59
79
  end
60
80
  end
61
81
  @channels.clear
62
82
  @global_request_handler.close
83
+ @connection_loop_thread.join if @connection_loop_thread && @connection_loop_thread != Thread.current
84
+ log_info { "connection closed" }
63
85
  end
64
86
 
65
87
  def closed?
66
88
  @closed
67
89
  end
68
90
 
69
- def connection_loop
70
- @logger.info { "start connection" }
71
- loop do
91
+ def connection_loop_thread
92
+ log_info { "start connection loop" }
93
+ Thread.new do
72
94
  begin
73
- payload = @authentication.receive
74
- rescue Error::ClosedAuthentication => e
75
- @logger.info { "closing connection loop" }
76
- break
77
- end
78
- @username ||= @authentication.username
79
- case payload[0,1].unpack("C")[0]
80
- when Message::SSH_MSG_GLOBAL_REQUEST::VALUE
81
- global_request payload
82
- when Message::SSH_MSG_CHANNEL_OPEN::VALUE
83
- channel_open payload
84
- when Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION::VALUE
85
- channel_open_confirmation payload
86
- when Message::SSH_MSG_CHANNEL_REQUEST::VALUE
87
- channel_request payload
88
- when Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::VALUE
89
- channel_window_adjust payload
90
- when Message::SSH_MSG_CHANNEL_DATA::VALUE
91
- channel_data payload
92
- when Message::SSH_MSG_CHANNEL_EOF::VALUE
93
- channel_eof payload
94
- when Message::SSH_MSG_CHANNEL_CLOSE::VALUE
95
- channel_close payload
96
- else
97
- @logger.warn { "received unsupported message: id: #{payload[0,1].unpack("C")[0]}" }
95
+ while true
96
+ begin
97
+ payload = @authentication.receive
98
+ rescue Error::ClosedAuthentication => e
99
+ log_info { "authentication closed" }
100
+ break
101
+ end
102
+ @username ||= @authentication.username
103
+ @variables ||= @authentication.variables
104
+ case payload[0,1].unpack("C")[0]
105
+ when Message::SSH_MSG_GLOBAL_REQUEST::VALUE
106
+ global_request payload
107
+ when Message::SSH_MSG_CHANNEL_OPEN::VALUE
108
+ channel_open payload
109
+ when Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION::VALUE
110
+ channel_open_confirmation payload
111
+ when Message::SSH_MSG_CHANNEL_REQUEST::VALUE
112
+ channel_request payload
113
+ when Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::VALUE
114
+ channel_window_adjust payload
115
+ when Message::SSH_MSG_CHANNEL_DATA::VALUE
116
+ channel_data payload
117
+ when Message::SSH_MSG_CHANNEL_EXTENDED_DATA::VALUE
118
+ channel_extended_data payload
119
+ when Message::SSH_MSG_CHANNEL_EOF::VALUE
120
+ channel_eof payload
121
+ when Message::SSH_MSG_CHANNEL_CLOSE::VALUE
122
+ channel_close payload
123
+ else
124
+ log_warn { "received unsupported message: id: #{payload[0,1].unpack("C")[0]}" }
125
+ end
126
+ end
127
+ rescue => e
128
+ log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
129
+ ensure
130
+ log_info { "closing connection loop" }
131
+ close
132
+ log_info { "connection loop closed" }
98
133
  end
99
134
  end
100
- @logger.info { "closing connection" }
101
- close
102
- @logger.info { "connection closed" }
103
135
  end
104
136
 
105
137
  def global_request payload
106
- @logger.info { 'received ' + Message::SSH_MSG_GLOBAL_REQUEST::ID }
107
- message = Message::SSH_MSG_GLOBAL_REQUEST.decode payload
138
+ log_info { 'received ' + Message::SSH_MSG_GLOBAL_REQUEST::ID }
139
+ message = Message::SSH_MSG_GLOBAL_REQUEST.new(logger: logger).decode payload
108
140
  begin
109
141
  @global_request_handler.request message
110
142
  rescue
@@ -119,10 +151,10 @@ module HrrRbSsh
119
151
  end
120
152
 
121
153
  def channel_open_start address, port, socket
122
- @logger.info { 'channel open start' }
123
- channel = Channel.new self, {:'channel type' => "forwarded-tcpip"}, socket
154
+ log_info { 'channel open start' }
155
+ channel = Channel.new self, {:'channel type' => "forwarded-tcpip"}, socket, logger: logger
124
156
  @channels[channel.local_channel] = channel
125
- @logger.info { 'channel opened' }
157
+ log_info { 'channel opened' }
126
158
  message = {
127
159
  :'message number' => Message::SSH_MSG_CHANNEL_OPEN::VALUE,
128
160
  :'channel type' => "forwarded-tcpip",
@@ -138,72 +170,110 @@ module HrrRbSsh
138
170
  end
139
171
 
140
172
  def channel_open payload
141
- @logger.info { 'received ' + Message::SSH_MSG_CHANNEL_OPEN::ID }
142
- message = Message::SSH_MSG_CHANNEL_OPEN.decode payload
173
+ log_info { 'received ' + Message::SSH_MSG_CHANNEL_OPEN::ID }
174
+ message = Message::SSH_MSG_CHANNEL_OPEN.new(logger: logger).decode payload
143
175
  begin
144
- channel = Channel.new self, message
176
+ channel = Channel.new self, message, logger: logger
145
177
  @channels[channel.local_channel] = channel
146
178
  channel.start
147
179
  send_channel_open_confirmation channel
148
180
  rescue => e
181
+ log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
149
182
  recipient_channel = message[:'sender channel']
150
183
  send_channel_open_failure recipient_channel, Message::SSH_MSG_CHANNEL_OPEN_FAILURE::ReasonCode::SSH_OPEN_CONNECT_FAILED, e.message
151
184
  end
152
185
  end
153
186
 
187
+ def request_channel_open channel_type, channel_specific_message={}, wait_response=true
188
+ log_info { 'request channel open' }
189
+ case channel_type
190
+ when "session"
191
+ channel = Channel.new self, {:'channel type' => channel_type}, logger: logger
192
+ @channels[channel.local_channel] = channel
193
+ end
194
+ message = {
195
+ :'message number' => Message::SSH_MSG_CHANNEL_OPEN::VALUE,
196
+ :'channel type' => channel_type,
197
+ :'sender channel' => channel.local_channel,
198
+ :'initial window size' => channel.local_window_size,
199
+ :'maximum packet size' => channel.local_maximum_packet_size,
200
+ }
201
+ send_channel_open message.merge(channel_specific_message)
202
+ log_info { 'sent channel open' }
203
+ if wait_response
204
+ log_info { 'wait response' }
205
+ channel.wait_until_started
206
+ end
207
+ unless channel.closed?
208
+ log_info { 'channel opened' }
209
+ channel
210
+ else
211
+ raise "Faild opening channel"
212
+ end
213
+ end
214
+
154
215
  def channel_open_confirmation payload
155
- @logger.info { 'received ' + Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION::ID }
156
- message = Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION.decode payload
216
+ log_info { 'received ' + Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION::ID }
217
+ message = Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION.new(logger: logger).decode payload
157
218
  channel = @channels[message[:'recipient channel']]
158
219
  channel.set_remote_parameters message
159
220
  channel.start
160
221
  end
161
222
 
162
223
  def channel_request payload
163
- @logger.info { 'received ' + Message::SSH_MSG_CHANNEL_REQUEST::ID }
164
- message = Message::SSH_MSG_CHANNEL_REQUEST.decode payload
224
+ log_info { 'received ' + Message::SSH_MSG_CHANNEL_REQUEST::ID }
225
+ message = Message::SSH_MSG_CHANNEL_REQUEST.new(logger: logger).decode payload
165
226
  local_channel = message[:'recipient channel']
166
- @channels[local_channel].receive_message_queue.enq message
227
+ @channels[local_channel].receive_message_queue.enq message if @channels.has_key? local_channel
167
228
  end
168
229
 
169
230
  def channel_window_adjust payload
170
- @logger.info { 'received ' + Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::ID }
171
- message = Message::SSH_MSG_CHANNEL_WINDOW_ADJUST.decode payload
231
+ log_info { 'received ' + Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::ID }
232
+ message = Message::SSH_MSG_CHANNEL_WINDOW_ADJUST.new(logger: logger).decode payload
172
233
  local_channel = message[:'recipient channel']
173
- @channels[local_channel].receive_message_queue.enq message
234
+ @channels[local_channel].receive_message_queue.enq message if @channels.has_key? local_channel
174
235
  end
175
236
 
176
237
  def channel_data payload
177
- @logger.info { 'received ' + Message::SSH_MSG_CHANNEL_DATA::ID }
178
- message = Message::SSH_MSG_CHANNEL_DATA.decode payload
238
+ log_info { 'received ' + Message::SSH_MSG_CHANNEL_DATA::ID }
239
+ message = Message::SSH_MSG_CHANNEL_DATA.new(logger: logger).decode payload
179
240
  local_channel = message[:'recipient channel']
180
- @channels[local_channel].receive_message_queue.enq message
241
+ @channels[local_channel].receive_message_queue.enq message if @channels.has_key? local_channel
242
+ end
243
+
244
+ def channel_extended_data payload
245
+ log_info { 'received ' + Message::SSH_MSG_CHANNEL_EXTENDED_DATA::ID }
246
+ message = Message::SSH_MSG_CHANNEL_EXTENDED_DATA.new(logger: logger).decode payload
247
+ local_channel = message[:'recipient channel']
248
+ @channels[local_channel].receive_message_queue.enq message if @channels.has_key? local_channel
181
249
  end
182
250
 
183
251
  def channel_eof payload
184
- @logger.info { 'received ' + Message::SSH_MSG_CHANNEL_EOF::ID }
185
- message = Message::SSH_MSG_CHANNEL_EOF.decode payload
252
+ log_info { 'received ' + Message::SSH_MSG_CHANNEL_EOF::ID }
253
+ message = Message::SSH_MSG_CHANNEL_EOF.new(logger: logger).decode payload
186
254
  local_channel = message[:'recipient channel']
187
- channel = @channels[local_channel]
188
- channel.receive_message_queue.close
255
+ @channels[local_channel].receive_message_queue.enq message if @channels.has_key? local_channel
189
256
  end
190
257
 
191
258
  def channel_close payload
192
- @logger.info { 'received ' + Message::SSH_MSG_CHANNEL_CLOSE::ID }
193
- message = Message::SSH_MSG_CHANNEL_CLOSE.decode payload
259
+ log_info { 'received ' + Message::SSH_MSG_CHANNEL_CLOSE::ID }
260
+ message = Message::SSH_MSG_CHANNEL_CLOSE.new(logger: logger).decode payload
194
261
  local_channel = message[:'recipient channel']
195
262
  channel = @channels[local_channel]
196
263
  channel.close
197
- @logger.info { "deleting channel" }
264
+ log_info { "wait until threads closed in channel" }
265
+ channel.wait_until_closed
266
+ log_info { "channel closed" }
267
+ log_info { "deleting channel" }
198
268
  @channels.delete local_channel
199
- @logger.info { "channel deleted" }
269
+ log_info { "channel deleted" }
200
270
  end
201
271
 
202
272
  def send_request_success
203
273
  message = {
204
274
  :'message number' => Message::SSH_MSG_REQUEST_SUCCESS::VALUE,
205
275
  }
206
- payload = Message::SSH_MSG_REQUEST_SUCCESS.encode message
276
+ payload = Message::SSH_MSG_REQUEST_SUCCESS.new(logger: logger).encode message
207
277
  @authentication.send payload
208
278
  end
209
279
 
@@ -211,12 +281,12 @@ module HrrRbSsh
211
281
  message = {
212
282
  :'message number' => Message::SSH_MSG_REQUEST_FAILURE::VALUE,
213
283
  }
214
- payload = Message::SSH_MSG_REQUEST_FAILURE.encode message
284
+ payload = Message::SSH_MSG_REQUEST_FAILURE.new(logger: logger).encode message
215
285
  @authentication.send payload
216
286
  end
217
287
 
218
288
  def send_channel_open message
219
- payload = Message::SSH_MSG_CHANNEL_OPEN.encode message
289
+ payload = Message::SSH_MSG_CHANNEL_OPEN.new(logger: logger).encode message
220
290
  @authentication.send payload
221
291
  end
222
292
 
@@ -229,7 +299,7 @@ module HrrRbSsh
229
299
  :'initial window size' => channel.local_window_size,
230
300
  :'maximum packet size' => channel.local_maximum_packet_size,
231
301
  }
232
- payload = Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION.encode message
302
+ payload = Message::SSH_MSG_CHANNEL_OPEN_CONFIRMATION.new(logger: logger).encode message
233
303
  @authentication.send payload
234
304
  end
235
305
 
@@ -241,7 +311,7 @@ module HrrRbSsh
241
311
  :'description' => description,
242
312
  :'language tag' => "",
243
313
  }
244
- payload = Message::SSH_MSG_CHANNEL_OPEN_FAILURE.encode message
314
+ payload = Message::SSH_MSG_CHANNEL_OPEN_FAILURE.new(logger: logger).encode message
245
315
  @authentication.send payload
246
316
  end
247
317
  end