hrr_rb_ssh 0.3.0.pre3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,6 +27,9 @@ module HrrRbSsh
27
27
  if @sender_thread_finished && @receiver_thread_finished
28
28
  @logger.info { "closing forwarded-tcpip" }
29
29
  @socket.close
30
+ @logger.info { "closing channel IOs" }
31
+ @channel.io.each{ |io| io.close rescue nil }
32
+ @logger.info { "channel IOs closed" }
30
33
  @channel.close from=:channel_type_instance
31
34
  @logger.info { "forwarded-tcpip closed" }
32
35
  end
@@ -43,15 +46,15 @@ module HrrRbSsh
43
46
  @channel.io[1].write s.readpartial(10240)
44
47
  rescue EOFError
45
48
  @logger.info { "socket is EOF" }
46
- @channel.io[1].close
49
+ @channel.io[1].close rescue nil
47
50
  break
48
51
  rescue IOError
49
52
  @logger.info { "socket is closed" }
50
- @channel.io[1].close
53
+ @channel.io[1].close rescue nil
51
54
  break
52
55
  rescue => e
53
56
  @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
54
- @channel.io[1].close
57
+ @channel.io[1].close rescue nil
55
58
  break
56
59
  end
57
60
  end
@@ -18,7 +18,10 @@ module HrrRbSsh
18
18
  end
19
19
 
20
20
  def start
21
- @proc_chain_thread = proc_chain_thread
21
+ case @connection.mode
22
+ when Mode::SERVER
23
+ @proc_chain_thread = proc_chain_thread
24
+ end
22
25
  end
23
26
 
24
27
  def close
@@ -42,6 +45,9 @@ module HrrRbSsh
42
45
  exitstatus = 1
43
46
  ensure
44
47
  @logger.info { "closing proc chain thread" }
48
+ @logger.info { "closing channel IOs" }
49
+ @channel.io.each{ |io| io.close rescue nil }
50
+ @logger.info { "channel IOs closed" }
45
51
  @logger.info { "wait for sending output" }
46
52
  @channel.wait_until_senders_closed
47
53
  @logger.info { "sending output finished" }
@@ -2,6 +2,8 @@
2
2
  # vim: et ts=2 sw=2
3
3
 
4
4
  require 'socket'
5
+ require 'thread'
6
+ require 'monitor'
5
7
  require 'hrr_rb_ssh/logger'
6
8
  require 'hrr_rb_ssh/connection/channel/channel_type'
7
9
 
@@ -19,7 +21,8 @@ module HrrRbSsh
19
21
  :local_maximum_packet_size,
20
22
  :remote_window_size,
21
23
  :remote_maximum_packet_size,
22
- :receive_message_queue
24
+ :receive_message_queue,
25
+ :exit_status
23
26
 
24
27
  def initialize connection, message, socket=nil
25
28
  @logger = Logger.new self.class.name
@@ -38,60 +41,81 @@ module HrrRbSsh
38
41
 
39
42
  @receive_message_queue = Queue.new
40
43
  @receive_data_queue = Queue.new
44
+ @receive_extended_data_queue = Queue.new
41
45
 
42
46
  @r_io_in, @w_io_in = IO.pipe
43
47
  @r_io_out, @w_io_out = IO.pipe
44
48
  @r_io_err, @w_io_err = IO.pipe
45
49
 
50
+ @channel_closing_monitor = Monitor.new
51
+
46
52
  @closed = nil
53
+ @exit_status = nil
47
54
  end
48
55
 
49
56
  def set_remote_parameters message
50
57
  @remote_channel = message[:'sender channel']
51
- @remote_window_size = message[:'initial window size']
58
+ @remote_window_size = message[:'initial window size']
52
59
  @remote_maximum_packet_size = message[:'maximum packet size']
53
60
  end
54
61
 
55
62
  def io
56
- [@r_io_in, @w_io_out, @w_io_err]
63
+ case @connection.mode
64
+ when Mode::SERVER
65
+ [@r_io_in, @w_io_out, @w_io_err]
66
+ when Mode::CLIENT
67
+ [@w_io_in, @r_io_out, @r_io_err]
68
+ end
57
69
  end
58
70
 
59
71
  def start
60
72
  @channel_loop_thread = channel_loop_thread
61
- @out_sender_thread = out_sender_thread
62
- @err_sender_thread = err_sender_thread
63
- @receiver_thread = receiver_thread
64
- @channel_type_instance.start
73
+ case @connection.mode
74
+ when Mode::SERVER
75
+ @out_sender_thread = out_sender_thread
76
+ @err_sender_thread = err_sender_thread
77
+ @receiver_thread = receiver_thread
78
+ @channel_type_instance.start
79
+ when Mode::CLIENT
80
+ @out_receiver_thread = out_receiver_thread
81
+ @err_receiver_thread = err_receiver_thread
82
+ @sender_thread = sender_thread
83
+ @channel_type_instance.start
84
+ end
65
85
  @closed = false
86
+ @logger.debug { "in start: #{@waiting_thread}" }
87
+ @waiting_thread.wakeup if @waiting_thread
88
+ end
89
+
90
+ def wait_until_started
91
+ @waiting_thread = Thread.current
92
+ @logger.debug { "in wait_until_started: #{@waiting_thread}" }
93
+ Thread.stop
66
94
  end
67
95
 
68
96
  def wait_until_senders_closed
69
- [@w_io_out, @w_io_err].each{ |io|
97
+ [
98
+ @out_sender_thread,
99
+ @err_sender_thread,
100
+ ].each{ |t|
70
101
  begin
71
- io.close
72
- rescue IOError # for compatibility for Ruby version < 2.3
73
- Thread.pass
102
+ t.join if t.instance_of? Thread
103
+ rescue => e
104
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
74
105
  end
75
106
  }
76
- [@out_sender_thread, @err_sender_thread].select{ |t| t.instance_of? Thread }.each(&:join)
77
107
  end
78
108
 
79
109
  def close from=:outside, exitstatus=0
80
- return if @closed
81
- @logger.info { "close channel" }
82
- @closed = true
110
+ @channel_closing_monitor.synchronize {
111
+ return if @closed
112
+ @logger.info { "close channel" }
113
+ @closed = true
114
+ }
83
115
  unless from == :channel_type_instance
84
116
  @channel_type_instance.close
85
117
  end
86
118
  @receive_message_queue.close
87
- @receive_data_queue.close
88
- [@r_io_in, @w_io_in, @r_io_out, @w_io_out, @r_io_err, @w_io_err].each{ |io|
89
- begin
90
- io.close
91
- rescue IOError # for compatibility for Ruby version < 2.3
92
- Thread.pass
93
- end
94
- }
95
119
  begin
96
120
  if from == :channel_type_instance
97
121
  send_channel_eof
@@ -111,6 +135,24 @@ module HrrRbSsh
111
135
  @logger.info { "channel closed" }
112
136
  end
113
137
 
138
+ def wait_until_closed
139
+ [
140
+ @out_sender_thread,
141
+ @err_sender_thread,
142
+ @receiver_thread,
143
+ @out_receiver_thread,
144
+ @err_receiver_thread,
145
+ @sender_thread,
146
+ @channel_loop_thread
147
+ ].each{ |t|
148
+ begin
149
+ t.join if t.instance_of? Thread
150
+ rescue => e
151
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
152
+ end
153
+ }
154
+ end
155
+
114
156
  def closed?
115
157
  @closed
116
158
  end
@@ -118,44 +160,60 @@ module HrrRbSsh
118
160
  def channel_loop_thread
119
161
  Thread.start do
120
162
  @logger.info { "start channel loop thread" }
121
- loop do
122
- begin
123
- message = @receive_message_queue.deq
124
- if message.nil? && @receive_message_queue.closed?
125
- @receive_data_queue.close
126
- @logger.info { "closing channel loop thread" }
127
- break
128
- end
129
- case message[:'message number']
130
- when Message::SSH_MSG_CHANNEL_REQUEST::VALUE
131
- @logger.info { "received channel request: #{message[:'request type']}" }
132
- begin
133
- @channel_type_instance.request message
134
- rescue => e
135
- @logger.warn { "request failed: #{e.message}" }
136
- if message[:'want reply']
137
- send_channel_failure
163
+ begin
164
+ loop do
165
+ begin
166
+ message = @receive_message_queue.deq
167
+ if message.nil? && @receive_message_queue.closed?
168
+ break
169
+ end
170
+ case message[:'message number']
171
+ when Message::SSH_MSG_CHANNEL_EOF::VALUE
172
+ @receive_data_queue.close
173
+ @receive_extended_data_queue.close
174
+ when Message::SSH_MSG_CHANNEL_REQUEST::VALUE
175
+ @logger.info { "received channel request: #{message[:'request type']}" }
176
+ case @connection.mode
177
+ when Mode::SERVER
178
+ begin
179
+ @channel_type_instance.request message
180
+ rescue => e
181
+ @logger.warn { "request failed: #{e.message}" }
182
+ send_channel_failure if message[:'want reply']
183
+ else
184
+ send_channel_success if message[:'want reply']
185
+ end
186
+ when Mode::CLIENT
187
+ case message[:'request type']
188
+ when "exit-status"
189
+ @logger.info { "exit status: #{message[:'exit status']}" }
190
+ @exit_status = message[:'exit status'].to_i
191
+ end
138
192
  end
193
+ when Message::SSH_MSG_CHANNEL_DATA::VALUE
194
+ @logger.info { "received channel data" }
195
+ local_channel = message[:'recipient channel']
196
+ @receive_data_queue.enq message[:'data']
197
+ when Message::SSH_MSG_CHANNEL_EXTENDED_DATA::VALUE
198
+ @logger.info { "received channel extended data" }
199
+ local_channel = message[:'recipient channel']
200
+ @receive_extended_data_queue.enq message[:'data']
201
+ when Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::VALUE
202
+ @logger.debug { "received channel window adjust" }
203
+ @remote_window_size = [@remote_window_size + message[:'bytes to add'], 0xffff_ffff].min
139
204
  else
140
- if message[:'want reply']
141
- send_channel_success
142
- end
205
+ @logger.warn { "received unsupported message: #{message.inspect}" }
143
206
  end
144
- when Message::SSH_MSG_CHANNEL_DATA::VALUE
145
- @logger.info { "received channel data" }
146
- local_channel = message[:'recipient channel']
147
- @receive_data_queue.enq message[:'data']
148
- when Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::VALUE
149
- @logger.debug { "received channel window adjust" }
150
- @remote_window_size = [@remote_window_size + message[:'bytes to add'], 0xffff_ffff].min
151
- else
152
- @logger.warn { "received unsupported message: #{message.inspect}" }
207
+ rescue => e
208
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
209
+ close from=:channel_loop_thread
210
+ break
153
211
  end
154
- rescue => e
155
- @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
156
- close from=:channel_loop_thread
157
- break
158
212
  end
213
+ ensure
214
+ @logger.info { "closing channel loop thread" }
215
+ @receive_data_queue.close
216
+ @receive_extended_data_queue.close
159
217
  end
160
218
  @logger.info { "channel loop thread closed" }
161
219
  end
@@ -175,17 +233,11 @@ module HrrRbSsh
175
233
  sending_data = data[0, sendable_size]
176
234
  send_channel_data sending_data if sendable_size > 0
177
235
  @remote_window_size -= sendable_size
178
- rescue EOFError => e
179
- begin
180
- @r_io_out.close
181
- rescue IOError # for compatibility for Ruby version < 2.3
182
- Thread.pass
183
- end
184
- rescue IOError => e
185
- @logger.warn { "channel IO is closed" }
186
- close
236
+ rescue EOFError, IOError => e
237
+ @r_io_out.close rescue nil
187
238
  rescue => e
188
239
  @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
240
+ @r_io_out.close rescue nil
189
241
  close
190
242
  end
191
243
  end
@@ -207,17 +259,11 @@ module HrrRbSsh
207
259
  sending_data = data[0, sendable_size]
208
260
  send_channel_extended_data sending_data if sendable_size > 0
209
261
  @remote_window_size -= sendable_size
210
- rescue EOFError => e
211
- begin
212
- @r_io_err.close
213
- rescue IOError # for compatibility for Ruby version < 2.3
214
- Thread.pass
215
- end
216
- rescue IOError => e
217
- @logger.warn { "channel IO is closed" }
218
- close
262
+ rescue EOFError, IOError => e
263
+ @r_io_err.close rescue nil
219
264
  rescue => e
220
265
  @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
266
+ @r_io_err.close rescue nil
221
267
  close
222
268
  end
223
269
  end
@@ -233,9 +279,9 @@ module HrrRbSsh
233
279
  data = @receive_data_queue.deq
234
280
  if data.nil? && @receive_data_queue.closed?
235
281
  @logger.info { "closing receiver thread" }
236
- @logger.info { "closing channel IO write" }
237
- @w_io_in.close_write
238
- @logger.info { "channel IO write closed" }
282
+ @logger.info { "closing w_io_in" }
283
+ @w_io_in.close
284
+ @logger.info { "w_io_in closed" }
239
285
  break
240
286
  end
241
287
  @w_io_in.write data
@@ -245,8 +291,7 @@ module HrrRbSsh
245
291
  send_channel_window_adjust
246
292
  @local_window_size += INITIAL_WINDOW_SIZE
247
293
  end
248
- rescue IOError => e
249
- @logger.warn { "channel IO is closed" }
294
+ rescue Errno::EPIPE, IOError => e
250
295
  close
251
296
  break
252
297
  rescue => e
@@ -259,6 +304,98 @@ module HrrRbSsh
259
304
  }
260
305
  end
261
306
 
307
+ def out_receiver_thread
308
+ Thread.start {
309
+ @logger.info { "start out receiver thread" }
310
+ loop do
311
+ begin
312
+ data = @receive_data_queue.deq
313
+ if data.nil? && @receive_data_queue.closed?
314
+ @logger.info { "closing out receiver thread" }
315
+ @logger.info { "closing w_io_out" }
316
+ @w_io_out.close
317
+ @logger.info { "w_io_out closed" }
318
+ break
319
+ end
320
+ @w_io_out.write data
321
+ @local_window_size -= data.size
322
+ if @local_window_size < INITIAL_WINDOW_SIZE/2
323
+ @logger.info { "send channel window adjust" }
324
+ send_channel_window_adjust
325
+ @local_window_size += INITIAL_WINDOW_SIZE
326
+ end
327
+ rescue Errno::EPIPE, IOError => e
328
+ close
329
+ break
330
+ rescue => e
331
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
332
+ close
333
+ break
334
+ end
335
+ end
336
+ @logger.info { "out receiver thread closed" }
337
+ }
338
+ end
339
+
340
+ def err_receiver_thread
341
+ Thread.start {
342
+ @logger.info { "start err receiver thread" }
343
+ loop do
344
+ begin
345
+ data = @receive_extended_data_queue.deq
346
+ if data.nil? && @receive_extended_data_queue.closed?
347
+ @logger.info { "closing err receiver thread" }
348
+ @logger.info { "closing w_io_err" }
349
+ @w_io_err.close
350
+ @logger.info { "w_io_err closed" }
351
+ break
352
+ end
353
+ @w_io_err.write data
354
+ @local_window_size -= data.size
355
+ if @local_window_size < INITIAL_WINDOW_SIZE/2
356
+ @logger.info { "send channel window adjust" }
357
+ send_channel_window_adjust
358
+ @local_window_size += INITIAL_WINDOW_SIZE
359
+ end
360
+ rescue Error::EPIPE, IOError => e
361
+ close
362
+ break
363
+ rescue => e
364
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
365
+ close
366
+ break
367
+ end
368
+ end
369
+ @logger.info { "err receiver thread closed" }
370
+ }
371
+ end
372
+
373
+ def sender_thread
374
+ Thread.start {
375
+ @logger.info { "start sender thread" }
376
+ loop do
377
+ if @r_io_in.closed?
378
+ @logger.info { "closing sender thread" }
379
+ break
380
+ end
381
+ begin
382
+ data = @r_io_in.readpartial(10240)
383
+ sendable_size = [data.size, @remote_window_size].min
384
+ sending_data = data[0, sendable_size]
385
+ send_channel_data sending_data if sendable_size > 0
386
+ @remote_window_size -= sendable_size
387
+ rescue EOFError, IOError => e
388
+ @r_io_in.close rescue nil
389
+ rescue => e
390
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
391
+ @r_io_in.close rescue nil
392
+ close
393
+ end
394
+ end
395
+ @logger.info { "sender thread closed" }
396
+ }
397
+ end
398
+
262
399
  def send_channel_success
263
400
  message = {
264
401
  :'message number' => Message::SSH_MSG_CHANNEL_SUCCESS::VALUE,
@@ -308,6 +445,98 @@ module HrrRbSsh
308
445
  @connection.send payload
309
446
  end
310
447
 
448
+ def send_channel_request_pty_req term_env_var_val, term_width_chars, term_height_rows, term_width_pixel, term_height_pixel, encoded_term_modes
449
+ message = {
450
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
451
+ :'recipient channel' => @remote_channel,
452
+ :'request type' => "pty-req",
453
+ :'want reply' => false,
454
+ :'TERM environment variable value' => term_env_var_val,
455
+ :'terminal width, characters' => term_width_chars,
456
+ :'terminal height, rows' => term_height_rows,
457
+ :'terminal width, pixels' => term_width_pixel,
458
+ :'terminal height, pixels' => term_height_pixel,
459
+ :'encoded terminal modes' => encoded_term_modes,
460
+ }
461
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
462
+ @connection.send payload
463
+ end
464
+
465
+ def send_channel_request_env variable_name, variable_value
466
+ message = {
467
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
468
+ :'recipient channel' => @remote_channel,
469
+ :'request type' => "env",
470
+ :'want reply' => false,
471
+ :'variable name' => variable_name,
472
+ :'variable value' => variable_value,
473
+ }
474
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
475
+ @connection.send payload
476
+ end
477
+
478
+ def send_channel_request_shell
479
+ message = {
480
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
481
+ :'recipient channel' => @remote_channel,
482
+ :'request type' => "shell",
483
+ :'want reply' => false,
484
+ }
485
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
486
+ @connection.send payload
487
+ end
488
+
489
+ def send_channel_request_exec command
490
+ message = {
491
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
492
+ :'recipient channel' => @remote_channel,
493
+ :'request type' => "exec",
494
+ :'want reply' => false,
495
+ :'command' => command,
496
+ }
497
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
498
+ @connection.send payload
499
+ end
500
+
501
+ def send_channel_request_subsystem subsystem_name
502
+ message = {
503
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
504
+ :'recipient channel' => @remote_channel,
505
+ :'request type' => "subsystem",
506
+ :'want reply' => false,
507
+ :'subsystem name' => subsystem_name,
508
+ }
509
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
510
+ @connection.send payload
511
+ end
512
+
513
+ def send_channel_request_window_change term_width_cols, term_height_rows, term_width_pixel, term_height_pixel
514
+ message = {
515
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
516
+ :'recipient channel' => @remote_channel,
517
+ :'request type' => "window-change",
518
+ :'want reply' => false,
519
+ :'terminal width, columns' => term_width_cols,
520
+ :'terminal height, rows' => term_height_rows,
521
+ :'terminal width, pixels' => term_width_pixel,
522
+ :'terminal height, pixels' => term_height_pixel,
523
+ }
524
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
525
+ @connection.send payload
526
+ end
527
+
528
+ def send_channel_request_signal signal_name
529
+ message = {
530
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
531
+ :'recipient channel' => @remote_channel,
532
+ :'request type' => "signal",
533
+ :'want reply' => false,
534
+ :'signal name' => signal_name,
535
+ }
536
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
537
+ @connection.send payload
538
+ end
539
+
311
540
  def send_channel_request_exit_status exitstatus
312
541
  message = {
313
542
  :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,