net-ssh 4.1.0 → 6.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.
Files changed (111) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +5 -0
  5. data/.rubocop.yml +8 -2
  6. data/.rubocop_todo.yml +405 -552
  7. data/.travis.yml +23 -22
  8. data/CHANGES.txt +112 -1
  9. data/Gemfile +1 -7
  10. data/{Gemfile.norbnacl → Gemfile.noed25519} +1 -1
  11. data/Manifest +4 -5
  12. data/README.md +287 -0
  13. data/Rakefile +40 -29
  14. data/appveyor.yml +12 -6
  15. data/lib/net/ssh.rb +68 -32
  16. data/lib/net/ssh/authentication/agent.rb +234 -222
  17. data/lib/net/ssh/authentication/certificate.rb +175 -164
  18. data/lib/net/ssh/authentication/constants.rb +17 -14
  19. data/lib/net/ssh/authentication/ed25519.rb +162 -141
  20. data/lib/net/ssh/authentication/ed25519_loader.rb +32 -29
  21. data/lib/net/ssh/authentication/key_manager.rb +40 -9
  22. data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
  23. data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
  24. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +1 -1
  25. data/lib/net/ssh/authentication/methods/none.rb +10 -10
  26. data/lib/net/ssh/authentication/methods/password.rb +13 -13
  27. data/lib/net/ssh/authentication/methods/publickey.rb +56 -55
  28. data/lib/net/ssh/authentication/pageant.rb +468 -465
  29. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  30. data/lib/net/ssh/authentication/session.rb +130 -122
  31. data/lib/net/ssh/buffer.rb +345 -312
  32. data/lib/net/ssh/buffered_io.rb +163 -163
  33. data/lib/net/ssh/config.rb +316 -238
  34. data/lib/net/ssh/connection/channel.rb +670 -650
  35. data/lib/net/ssh/connection/constants.rb +30 -26
  36. data/lib/net/ssh/connection/event_loop.rb +108 -105
  37. data/lib/net/ssh/connection/keepalive.rb +54 -50
  38. data/lib/net/ssh/connection/session.rb +682 -671
  39. data/lib/net/ssh/connection/term.rb +180 -176
  40. data/lib/net/ssh/errors.rb +101 -99
  41. data/lib/net/ssh/key_factory.rb +195 -108
  42. data/lib/net/ssh/known_hosts.rb +161 -152
  43. data/lib/net/ssh/loggable.rb +57 -55
  44. data/lib/net/ssh/packet.rb +82 -78
  45. data/lib/net/ssh/prompt.rb +55 -53
  46. data/lib/net/ssh/proxy/command.rb +104 -89
  47. data/lib/net/ssh/proxy/errors.rb +12 -8
  48. data/lib/net/ssh/proxy/http.rb +93 -91
  49. data/lib/net/ssh/proxy/https.rb +42 -39
  50. data/lib/net/ssh/proxy/jump.rb +50 -47
  51. data/lib/net/ssh/proxy/socks4.rb +0 -2
  52. data/lib/net/ssh/proxy/socks5.rb +11 -12
  53. data/lib/net/ssh/service/forward.rb +370 -317
  54. data/lib/net/ssh/test.rb +83 -77
  55. data/lib/net/ssh/test/channel.rb +146 -142
  56. data/lib/net/ssh/test/extensions.rb +150 -146
  57. data/lib/net/ssh/test/kex.rb +35 -31
  58. data/lib/net/ssh/test/local_packet.rb +48 -44
  59. data/lib/net/ssh/test/packet.rb +87 -84
  60. data/lib/net/ssh/test/remote_packet.rb +35 -31
  61. data/lib/net/ssh/test/script.rb +173 -171
  62. data/lib/net/ssh/test/socket.rb +59 -55
  63. data/lib/net/ssh/transport/algorithms.rb +430 -364
  64. data/lib/net/ssh/transport/cipher_factory.rb +95 -91
  65. data/lib/net/ssh/transport/constants.rb +33 -25
  66. data/lib/net/ssh/transport/ctr.rb +33 -11
  67. data/lib/net/ssh/transport/hmac.rb +15 -13
  68. data/lib/net/ssh/transport/hmac/abstract.rb +82 -63
  69. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  70. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  71. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  72. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  73. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  74. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  75. data/lib/net/ssh/transport/identity_cipher.rb +55 -51
  76. data/lib/net/ssh/transport/kex.rb +14 -13
  77. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  78. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  79. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  80. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  81. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  82. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
  83. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -62
  84. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  85. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  86. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  87. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  88. data/lib/net/ssh/transport/key_expander.rb +29 -25
  89. data/lib/net/ssh/transport/openssl.rb +116 -116
  90. data/lib/net/ssh/transport/packet_stream.rb +223 -190
  91. data/lib/net/ssh/transport/server_version.rb +64 -66
  92. data/lib/net/ssh/transport/session.rb +306 -257
  93. data/lib/net/ssh/transport/state.rb +198 -196
  94. data/lib/net/ssh/verifiers/accept_new.rb +35 -0
  95. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +34 -0
  96. data/lib/net/ssh/verifiers/always.rb +56 -0
  97. data/lib/net/ssh/verifiers/never.rb +21 -0
  98. data/lib/net/ssh/version.rb +55 -53
  99. data/net-ssh-public_cert.pem +18 -19
  100. data/net-ssh.gemspec +12 -11
  101. data/support/ssh_tunnel_bug.rb +2 -2
  102. metadata +86 -75
  103. metadata.gz.sig +0 -0
  104. data/Gemfile.norbnacl.lock +0 -41
  105. data/README.rdoc +0 -169
  106. data/lib/net/ssh/ruby_compat.rb +0 -24
  107. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  108. data/lib/net/ssh/verifiers/null.rb +0 -12
  109. data/lib/net/ssh/verifiers/secure.rb +0 -52
  110. data/lib/net/ssh/verifiers/strict.rb +0 -24
  111. data/support/arcfour_check.rb +0 -20
@@ -1,33 +1,37 @@
1
- module Net; module SSH; module Connection
1
+ module Net
2
+ module SSH
3
+ module Connection
2
4
 
3
- # Definitions of constants that are specific to the connection layer of the
4
- # SSH protocol.
5
- module Constants
5
+ # Definitions of constants that are specific to the connection layer of the
6
+ # SSH protocol.
7
+ module Constants
6
8
 
7
- #--
8
- # Connection protocol generic messages
9
- #++
9
+ #--
10
+ # Connection protocol generic messages
11
+ #++
10
12
 
11
- GLOBAL_REQUEST = 80
12
- REQUEST_SUCCESS = 81
13
- REQUEST_FAILURE = 82
13
+ GLOBAL_REQUEST = 80
14
+ REQUEST_SUCCESS = 81
15
+ REQUEST_FAILURE = 82
14
16
 
15
- #--
16
- # Channel related messages
17
- #++
17
+ #--
18
+ # Channel related messages
19
+ #++
18
20
 
19
- CHANNEL_OPEN = 90
20
- CHANNEL_OPEN_CONFIRMATION = 91
21
- CHANNEL_OPEN_FAILURE = 92
22
- CHANNEL_WINDOW_ADJUST = 93
23
- CHANNEL_DATA = 94
24
- CHANNEL_EXTENDED_DATA = 95
25
- CHANNEL_EOF = 96
26
- CHANNEL_CLOSE = 97
27
- CHANNEL_REQUEST = 98
28
- CHANNEL_SUCCESS = 99
29
- CHANNEL_FAILURE = 100
21
+ CHANNEL_OPEN = 90
22
+ CHANNEL_OPEN_CONFIRMATION = 91
23
+ CHANNEL_OPEN_FAILURE = 92
24
+ CHANNEL_WINDOW_ADJUST = 93
25
+ CHANNEL_DATA = 94
26
+ CHANNEL_EXTENDED_DATA = 95
27
+ CHANNEL_EOF = 96
28
+ CHANNEL_CLOSE = 97
29
+ CHANNEL_REQUEST = 98
30
+ CHANNEL_SUCCESS = 99
31
+ CHANNEL_FAILURE = 100
30
32
 
31
- end
33
+ end
32
34
 
33
- end; end end
35
+ end
36
+ end
37
+ end
@@ -1,114 +1,117 @@
1
1
  require 'net/ssh/loggable'
2
- require 'net/ssh/ruby_compat'
3
2
 
4
- module Net; module SSH; module Connection
5
- # EventLoop can be shared across multiple sessions
6
- #
7
- # one issue is with blocks passed to loop, etc.
8
- # they should get current session as parameter, but in
9
- # case you're using multiple sessions in an event loop it doesnt makes sense
10
- # and we don't pass session.
11
- class EventLoop
12
- include Loggable
13
-
14
- def initialize(logger=nil)
15
- self.logger = logger
16
- @sessions = []
17
- end
18
-
19
- def register(session)
20
- @sessions << session
21
- end
22
-
23
- # process until timeout
24
- # if a block is given a session will be removed from loop
25
- # if block returns false for that session
26
- def process(wait = nil, &block)
27
- return false unless ev_preprocess(&block)
28
-
29
- ev_select_and_postprocess(wait)
30
- end
31
-
32
- # process the event loop but only for the sepcified session
33
- def process_only(session, wait = nil)
34
- orig_sessions = @sessions
35
- begin
36
- @sessions = [session]
37
- return false unless ev_preprocess
38
- ev_select_and_postprocess(wait)
39
- ensure
40
- @sessions = orig_sessions
41
- end
42
- end
43
-
44
- # Call preprocess on each session. If block given and that
45
- # block retuns false then we exit the processing
46
- def ev_preprocess(&block)
47
- return false if block_given? && !yield(self)
48
- @sessions.each(&:ev_preprocess)
49
- return false if block_given? && !yield(self)
50
- return true
51
- end
52
-
53
- def ev_select_and_postprocess(wait)
54
- owners = {}
55
- r = []
56
- w = []
57
- minwait = nil
58
- @sessions.each do |session|
59
- sr,sw,actwait = session.ev_do_calculate_rw_wait(wait)
60
- minwait = actwait if actwait && (minwait.nil? || actwait < minwait)
61
- r.push(*sr)
62
- w.push(*sw)
63
- sr.each { |ri| owners[ri] = session }
64
- sw.each { |wi| owners[wi] = session }
65
- end
66
-
67
- readers, writers, = Net::SSH::Compat.io_select(r, w, nil, minwait)
68
-
69
- fired_sessions = {}
70
-
71
- if readers
72
- readers.each do |reader|
73
- session = owners[reader]
74
- (fired_sessions[session] ||= {r: [],w: []})[:r] << reader
3
+ module Net
4
+ module SSH
5
+ module Connection
6
+ # EventLoop can be shared across multiple sessions
7
+ #
8
+ # one issue is with blocks passed to loop, etc.
9
+ # they should get current session as parameter, but in
10
+ # case you're using multiple sessions in an event loop it doesnt makes sense
11
+ # and we don't pass session.
12
+ class EventLoop
13
+ include Loggable
14
+
15
+ def initialize(logger=nil)
16
+ self.logger = logger
17
+ @sessions = []
75
18
  end
76
- end
77
- if writers
78
- writers.each do |writer|
79
- session = owners[writer]
80
- (fired_sessions[session] ||= {r: [],w: []})[:w] << writer
19
+
20
+ def register(session)
21
+ @sessions << session
22
+ end
23
+
24
+ # process until timeout
25
+ # if a block is given a session will be removed from loop
26
+ # if block returns false for that session
27
+ def process(wait = nil, &block)
28
+ return false unless ev_preprocess(&block)
29
+
30
+ ev_select_and_postprocess(wait)
31
+ end
32
+
33
+ # process the event loop but only for the sepcified session
34
+ def process_only(session, wait = nil)
35
+ orig_sessions = @sessions
36
+ begin
37
+ @sessions = [session]
38
+ return false unless ev_preprocess
39
+ ev_select_and_postprocess(wait)
40
+ ensure
41
+ @sessions = orig_sessions
42
+ end
43
+ end
44
+
45
+ # Call preprocess on each session. If block given and that
46
+ # block retuns false then we exit the processing
47
+ def ev_preprocess(&block)
48
+ return false if block_given? && !yield(self)
49
+ @sessions.each(&:ev_preprocess)
50
+ return false if block_given? && !yield(self)
51
+ return true
52
+ end
53
+
54
+ def ev_select_and_postprocess(wait)
55
+ owners = {}
56
+ r = []
57
+ w = []
58
+ minwait = nil
59
+ @sessions.each do |session|
60
+ sr,sw,actwait = session.ev_do_calculate_rw_wait(wait)
61
+ minwait = actwait if actwait && (minwait.nil? || actwait < minwait)
62
+ r.push(*sr)
63
+ w.push(*sw)
64
+ sr.each { |ri| owners[ri] = session }
65
+ sw.each { |wi| owners[wi] = session }
66
+ end
67
+
68
+ readers, writers, = IO.select(r, w, nil, minwait)
69
+
70
+ fired_sessions = {}
71
+
72
+ if readers
73
+ readers.each do |reader|
74
+ session = owners[reader]
75
+ (fired_sessions[session] ||= { r: [],w: [] })[:r] << reader
76
+ end
77
+ end
78
+ if writers
79
+ writers.each do |writer|
80
+ session = owners[writer]
81
+ (fired_sessions[session] ||= { r: [],w: [] })[:w] << writer
82
+ end
83
+ end
84
+
85
+ fired_sessions.each do |s,rw|
86
+ s.ev_do_handle_events(rw[:r],rw[:w])
87
+ end
88
+
89
+ @sessions.each { |s| s.ev_do_postprocess(fired_sessions.key?(s)) }
90
+ true
81
91
  end
82
92
  end
83
93
 
84
- fired_sessions.each do |s,rw|
85
- s.ev_do_handle_events(rw[:r],rw[:w])
94
+ # optimized version for a single session
95
+ class SingleSessionEventLoop < EventLoop
96
+ # Compatibility for original single session event loops:
97
+ # we call block with session as argument
98
+ def ev_preprocess(&block)
99
+ return false if block_given? && !yield(@sessions.first)
100
+ @sessions.each(&:ev_preprocess)
101
+ return false if block_given? && !yield(@sessions.first)
102
+ return true
103
+ end
104
+
105
+ def ev_select_and_postprocess(wait)
106
+ raise "Only one session expected" unless @sessions.count == 1
107
+ session = @sessions.first
108
+ sr,sw,actwait = session.ev_do_calculate_rw_wait(wait)
109
+ readers, writers, = IO.select(sr, sw, nil, actwait)
110
+
111
+ session.ev_do_handle_events(readers,writers)
112
+ session.ev_do_postprocess(!((readers.nil? || readers.empty?) && (writers.nil? || writers.empty?)))
113
+ end
86
114
  end
87
-
88
- @sessions.each { |s| s.ev_do_postprocess(fired_sessions.key?(s)) }
89
- true
90
- end
91
- end
92
-
93
- # optimized version for a single session
94
- class SingleSessionEventLoop < EventLoop
95
- # Compatibility for original single session event loops:
96
- # we call block with session as argument
97
- def ev_preprocess(&block)
98
- return false if block_given? && !yield(@sessions.first)
99
- @sessions.each(&:ev_preprocess)
100
- return false if block_given? && !yield(@sessions.first)
101
- return true
102
- end
103
-
104
- def ev_select_and_postprocess(wait)
105
- raise "Only one session expected" unless @sessions.count == 1
106
- session = @sessions.first
107
- sr,sw,actwait = session.ev_do_calculate_rw_wait(wait)
108
- readers, writers, = Net::SSH::Compat.io_select(sr, sw, nil, actwait)
109
-
110
- session.ev_do_handle_events(readers,writers)
111
- session.ev_do_postprocess(!((readers.nil? || readers.empty?) && (writers.nil? || writers.empty?)))
112
115
  end
113
116
  end
114
- end; end; end
117
+ end
@@ -1,55 +1,59 @@
1
1
  require 'net/ssh/loggable'
2
- module Net; module SSH; module Connection
2
+ module Net
3
+ module SSH
4
+ module Connection
5
+
6
+ class Keepalive
7
+ include Loggable
8
+
9
+ def initialize(session)
10
+ @last_keepalive_sent_at = nil
11
+ @unresponded_keepalive_count = 0
12
+ @session = session
13
+ self.logger = session.logger
14
+ end
15
+
16
+ def options
17
+ @session.options
18
+ end
19
+
20
+ def enabled?
21
+ options[:keepalive]
22
+ end
23
+
24
+ def interval
25
+ options[:keepalive_interval] || Session::DEFAULT_IO_SELECT_TIMEOUT
26
+ end
27
+
28
+ def should_send?
29
+ return false unless enabled?
30
+ return true unless @last_keepalive_sent_at
31
+ Time.now - @last_keepalive_sent_at >= interval
32
+ end
33
+
34
+ def keepalive_maxcount
35
+ (options[:keepalive_maxcount] || 3).to_i
36
+ end
37
+
38
+ def send_as_needed(was_events)
39
+ return if was_events
40
+ return unless should_send?
41
+ info { "sending keepalive #{@unresponded_keepalive_count}" }
42
+
43
+ @unresponded_keepalive_count += 1
44
+ @session.send_global_request("keepalive@openssh.com") { |success, response|
45
+ debug { "keepalive response successful. Missed #{@unresponded_keepalive_count - 1} keepalives" }
46
+ @unresponded_keepalive_count = 0
47
+ }
48
+ @last_keepalive_sent_at = Time.now
49
+ if keepalive_maxcount > 0 && @unresponded_keepalive_count > keepalive_maxcount
50
+ error { "Timeout, server #{@session.host} not responding. Missed #{@unresponded_keepalive_count - 1} timeouts." }
51
+ @unresponded_keepalive_count = 0
52
+ raise Net::SSH::Timeout, "Timeout, server #{@session.host} not responding."
53
+ end
54
+ end
55
+ end
3
56
 
4
- class Keepalive
5
- include Loggable
6
-
7
- def initialize(session)
8
- @last_keepalive_sent_at = nil
9
- @unresponded_keepalive_count = 0
10
- @session = session
11
- self.logger = session.logger
12
- end
13
-
14
- def options
15
- @session.options
16
- end
17
-
18
- def enabled?
19
- options[:keepalive]
20
- end
21
-
22
- def interval
23
- options[:keepalive_interval] || Session::DEFAULT_IO_SELECT_TIMEOUT
24
- end
25
-
26
- def should_send?
27
- return false unless enabled?
28
- return true unless @last_keepalive_sent_at
29
- Time.now - @last_keepalive_sent_at >= interval
30
- end
31
-
32
- def keepalive_maxcount
33
- (options[:keepalive_maxcount] || 3).to_i
34
- end
35
-
36
- def send_as_needed(was_events)
37
- return if was_events
38
- return unless should_send?
39
- info { "sending keepalive #{@unresponded_keepalive_count}" }
40
-
41
- @unresponded_keepalive_count += 1
42
- @session.send_global_request("keepalive@openssh.com") { |success, response|
43
- debug { "keepalive response successful. Missed #{@unresponded_keepalive_count-1} keepalives" }
44
- @unresponded_keepalive_count = 0
45
- }
46
- @last_keepalive_sent_at = Time.now
47
- if keepalive_maxcount > 0 && @unresponded_keepalive_count > keepalive_maxcount
48
- error { "Timeout, server #{@session.host} not responding. Missed #{@unresponded_keepalive_count-1} timeouts." }
49
- @unresponded_keepalive_count = 0
50
- raise Net::SSH::Timeout, "Timeout, server #{@session.host} not responding."
51
57
  end
52
58
  end
53
59
  end
54
-
55
- end; end; end
@@ -1,700 +1,711 @@
1
1
  require 'net/ssh/loggable'
2
- require 'net/ssh/ruby_compat'
3
2
  require 'net/ssh/connection/channel'
4
3
  require 'net/ssh/connection/constants'
5
4
  require 'net/ssh/service/forward'
6
5
  require 'net/ssh/connection/keepalive'
7
6
  require 'net/ssh/connection/event_loop'
8
7
 
9
- module Net; module SSH; module Connection
10
-
11
- # A session class representing the connection service running on top of
12
- # the SSH transport layer. It manages the creation of channels (see
13
- # #open_channel), and the dispatching of messages to the various channels.
14
- # It also encapsulates the SSH event loop (via #loop and #process),
15
- # and serves as a central point-of-reference for all SSH-related services (e.g.
16
- # port forwarding, SFTP, SCP, etc.).
17
- #
18
- # You will rarely (if ever) need to instantiate this class directly; rather,
19
- # you'll almost always use Net::SSH.start to initialize a new network
20
- # connection, authenticate a user, and return a new connection session,
21
- # all in one call.
22
- #
23
- # Net::SSH.start("localhost", "user") do |ssh|
24
- # # 'ssh' is an instance of Net::SSH::Connection::Session
25
- # ssh.exec! "/etc/init.d/some_process start"
26
- # end
27
- class Session
28
- include Constants, Loggable
29
-
30
- # Default IO.select timeout threshold
31
- DEFAULT_IO_SELECT_TIMEOUT = 300
32
-
33
- # The underlying transport layer abstraction (see Net::SSH::Transport::Session).
34
- attr_reader :transport
35
-
36
- # The map of options that were used to initialize this instance.
37
- attr_reader :options
38
-
39
- # The collection of custom properties for this instance. (See #[] and #[]=).
40
- attr_reader :properties
41
-
42
- # The map of channels, each key being the local-id for the channel.
43
- attr_reader :channels #:nodoc:
44
-
45
- # The map of listeners that the event loop knows about. See #listen_to.
46
- attr_reader :listeners #:nodoc:
47
-
48
- # The map of specialized handlers for opening specific channel types. See
49
- # #on_open_channel.
50
- attr_reader :channel_open_handlers #:nodoc:
51
-
52
- # The list of callbacks for pending requests. See #send_global_request.
53
- attr_reader :pending_requests #:nodoc:
54
-
55
- class NilChannel
56
- def initialize(session)
57
- @session = session
58
- end
59
-
60
- def method_missing(sym, *args)
61
- @session.lwarn { "ignoring request #{sym.inspect} for non-existent (closed?) channel; probably ssh server bug" }
62
- end
63
- end
64
-
65
- # Create a new connection service instance atop the given transport
66
- # layer. Initializes the listeners to be only the underlying socket object.
67
- def initialize(transport, options={})
68
- self.logger = transport.logger
69
-
70
- @transport = transport
71
- @options = options
72
-
73
- @channel_id_counter = -1
74
- @channels = Hash.new(NilChannel.new(self))
75
- @listeners = { transport.socket => nil }
76
- @pending_requests = []
77
- @channel_open_handlers = {}
78
- @on_global_request = {}
79
- @properties = (options[:properties] || {}).dup
80
-
81
- @max_pkt_size = (options.key?(:max_pkt_size) ? options[:max_pkt_size] : 0x8000)
82
- @max_win_size = (options.key?(:max_win_size) ? options[:max_win_size] : 0x20000)
83
-
84
- @keepalive = Keepalive.new(self)
85
-
86
- @event_loop = options[:event_loop] || SingleSessionEventLoop.new
87
- @event_loop.register(self)
88
- end
89
-
90
- # Retrieves a custom property from this instance. This can be used to
91
- # store additional state in applications that must manage multiple
92
- # SSH connections.
93
- def [](key)
94
- @properties[key]
95
- end
96
-
97
- # Sets a custom property for this instance.
98
- def []=(key, value)
99
- @properties[key] = value
100
- end
101
-
102
- # Returns the name of the host that was given to the transport layer to
103
- # connect to.
104
- def host
105
- transport.host
106
- end
107
-
108
- # Returns true if the underlying transport has been closed. Note that
109
- # this can be a little misleading, since if the remote server has
110
- # closed the connection, the local end will still think it is open
111
- # until the next operation on the socket. Nevertheless, this method can
112
- # be useful if you just want to know if _you_ have closed the connection.
113
- def closed?
114
- transport.closed?
115
- end
116
-
117
- # Closes the session gracefully, blocking until all channels have
118
- # successfully closed, and then closes the underlying transport layer
119
- # connection.
120
- def close
121
- info { "closing remaining channels (#{channels.length} open)" }
122
- channels.each { |id, channel| channel.close }
123
- begin
124
- loop(0.1) { channels.any? }
125
- rescue Net::SSH::Disconnect
126
- raise unless channels.empty?
127
- end
128
- transport.close
129
- end
130
-
131
- # Performs a "hard" shutdown of the connection. In general, this should
132
- # never be done, but it might be necessary (in a rescue clause, for instance,
133
- # when the connection needs to close but you don't know the status of the
134
- # underlying protocol's state).
135
- def shutdown!
136
- transport.shutdown!
137
- end
138
-
139
- # preserve a reference to Kernel#loop
140
- alias :loop_forever :loop
141
-
142
- # Returns +true+ if there are any channels currently active on this
143
- # session. By default, this will not include "invisible" channels
144
- # (such as those created by forwarding ports and such), but if you pass
145
- # a +true+ value for +include_invisible+, then those will be counted.
146
- #
147
- # This can be useful for determining whether the event loop should continue
148
- # to be run.
149
- #
150
- # ssh.loop { ssh.busy? }
151
- def busy?(include_invisible=false)
152
- if include_invisible
153
- channels.any?
154
- else
155
- channels.any? { |id, ch| !ch[:invisible] }
156
- end
157
- end
158
-
159
- # The main event loop. Calls #process until #process returns false. If a
160
- # block is given, it is passed to #process, otherwise a default proc is
161
- # used that just returns true if there are any channels active (see #busy?).
162
- # The # +wait+ parameter is also passed through to #process (where it is
163
- # interpreted as the maximum number of seconds to wait for IO.select to return).
164
- #
165
- # # loop for as long as there are any channels active
166
- # ssh.loop
167
- #
168
- # # loop for as long as there are any channels active, but make sure
169
- # # the event loop runs at least once per 0.1 second
170
- # ssh.loop(0.1)
171
- #
172
- # # loop until ctrl-C is pressed
173
- # int_pressed = false
174
- # trap("INT") { int_pressed = true }
175
- # ssh.loop(0.1) { not int_pressed }
176
- def loop(wait=nil, &block)
177
- running = block || Proc.new { busy? }
178
- loop_forever { break unless process(wait, &running) }
179
- end
180
-
181
- # The core of the event loop. It processes a single iteration of the event
182
- # loop. If a block is given, it should return false when the processing
183
- # should abort, which causes #process to return false. Otherwise,
184
- # #process returns true. The session itself is yielded to the block as its
185
- # only argument.
186
- #
187
- # If +wait+ is nil (the default), this method will block until any of the
188
- # monitored IO objects are ready to be read from or written to. If you want
189
- # it to not block, you can pass 0, or you can pass any other numeric value
190
- # to indicate that it should block for no more than that many seconds.
191
- # Passing 0 is a good way to poll the connection, but if you do it too
192
- # frequently it can make your CPU quite busy!
193
- #
194
- # This will also cause all active channels to be processed once each (see
195
- # Net::SSH::Connection::Channel#on_process).
196
- #
197
- # TODO revise example
198
- #
199
- # # process multiple Net::SSH connections in parallel
200
- # connections = [
201
- # Net::SSH.start("host1", ...),
202
- # Net::SSH.start("host2", ...)
203
- # ]
204
- #
205
- # connections.each do |ssh|
206
- # ssh.exec "grep something /in/some/files"
207
- # end
208
- #
209
- # condition = Proc.new { |s| s.busy? }
210
- #
211
- # loop do
212
- # connections.delete_if { |ssh| !ssh.process(0.1, &condition) }
213
- # break if connections.empty?
214
- # end
215
- def process(wait=nil, &block)
216
- @event_loop.process(wait, &block)
217
- rescue
218
- force_channel_cleanup_on_close if closed?
219
- raise
220
- end
221
-
222
- # This is called internally as part of #process. It dispatches any
223
- # available incoming packets, and then runs Net::SSH::Connection::Channel#process
224
- # for any active channels. If a block is given, it is invoked at the
225
- # start of the method and again at the end, and if the block ever returns
226
- # false, this method returns false. Otherwise, it returns true.
227
- def preprocess(&block)
228
- return false if block_given? && !yield(self)
229
- ev_preprocess(&block)
230
- return false if block_given? && !yield(self)
231
- return true
232
- end
233
-
234
- # Called by event loop to process available data before going to
235
- # event multiplexing
236
- def ev_preprocess(&block)
237
- dispatch_incoming_packets(raise_disconnect_errors: false)
238
- each_channel { |id, channel| channel.process unless channel.local_closed? }
239
- end
240
-
241
- # Returns the file descriptors the event loop should wait for read/write events,
242
- # we also return the max wait
243
- def ev_do_calculate_rw_wait(wait)
244
- r = listeners.keys
245
- w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
246
- [r,w,io_select_wait(wait)]
247
- end
248
-
249
- # This is called internally as part of #process.
250
- def postprocess(readers, writers)
251
- ev_do_handle_events(readers, writers)
252
- end
253
-
254
- # It loops over the given arrays of reader IO's and writer IO's,
255
- # processing them as needed, and
256
- # then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
257
- # transport layer to rekey. Then returns true.
258
- def ev_do_handle_events(readers, writers)
259
- Array(readers).each do |reader|
260
- if listeners[reader]
261
- listeners[reader].call(reader)
262
- else
263
- if reader.fill.zero?
264
- reader.close
265
- stop_listening_to(reader)
8
+ module Net
9
+ module SSH
10
+ module Connection
11
+
12
+ # A session class representing the connection service running on top of
13
+ # the SSH transport layer. It manages the creation of channels (see
14
+ # #open_channel), and the dispatching of messages to the various channels.
15
+ # It also encapsulates the SSH event loop (via #loop and #process),
16
+ # and serves as a central point-of-reference for all SSH-related services (e.g.
17
+ # port forwarding, SFTP, SCP, etc.).
18
+ #
19
+ # You will rarely (if ever) need to instantiate this class directly; rather,
20
+ # you'll almost always use Net::SSH.start to initialize a new network
21
+ # connection, authenticate a user, and return a new connection session,
22
+ # all in one call.
23
+ #
24
+ # Net::SSH.start("localhost", "user") do |ssh|
25
+ # # 'ssh' is an instance of Net::SSH::Connection::Session
26
+ # ssh.exec! "/etc/init.d/some_process start"
27
+ # end
28
+ class Session
29
+ include Loggable
30
+ include Constants
31
+
32
+ # Default IO.select timeout threshold
33
+ DEFAULT_IO_SELECT_TIMEOUT = 300
34
+
35
+ # The underlying transport layer abstraction (see Net::SSH::Transport::Session).
36
+ attr_reader :transport
37
+
38
+ # The map of options that were used to initialize this instance.
39
+ attr_reader :options
40
+
41
+ # The collection of custom properties for this instance. (See #[] and #[]=).
42
+ attr_reader :properties
43
+
44
+ # The map of channels, each key being the local-id for the channel.
45
+ attr_reader :channels #:nodoc:
46
+
47
+ # The map of listeners that the event loop knows about. See #listen_to.
48
+ attr_reader :listeners #:nodoc:
49
+
50
+ # The map of specialized handlers for opening specific channel types. See
51
+ # #on_open_channel.
52
+ attr_reader :channel_open_handlers #:nodoc:
53
+
54
+ # The list of callbacks for pending requests. See #send_global_request.
55
+ attr_reader :pending_requests #:nodoc:
56
+
57
+ class NilChannel
58
+ def initialize(session)
59
+ @session = session
60
+ end
61
+
62
+ def method_missing(sym, *args)
63
+ @session.lwarn { "ignoring request #{sym.inspect} for non-existent (closed?) channel; probably ssh server bug" }
266
64
  end
267
65
  end
268
- end
269
-
270
- Array(writers).each do |writer|
271
- writer.send_pending
272
- end
273
- end
274
-
275
- # calls Net::SSH::Transport::Session#rekey_as_needed to allow the
276
- # transport layer to rekey
277
- def ev_do_postprocess(was_events)
278
- @keepalive.send_as_needed(was_events)
279
- transport.rekey_as_needed
280
- true
281
- end
282
-
283
- # Send a global request of the given type. The +extra+ parameters must
284
- # be even in number, and conform to the same format as described for
285
- # Net::SSH::Buffer.from. If a callback is not specified, the request will
286
- # not require a response from the server, otherwise the server is required
287
- # to respond and indicate whether the request was successful or not. This
288
- # success or failure is indicated by the callback being invoked, with the
289
- # first parameter being true or false (success, or failure), and the second
290
- # being the packet itself.
291
- #
292
- # Generally, Net::SSH will manage global requests that need to be sent
293
- # (e.g. port forward requests and such are handled in the Net::SSH::Service::Forward
294
- # class, for instance). However, there may be times when you need to
295
- # send a global request that isn't explicitly handled by Net::SSH, and so
296
- # this method is available to you.
297
- #
298
- # ssh.send_global_request("keep-alive@openssh.com")
299
- def send_global_request(type, *extra, &callback)
300
- info { "sending global request #{type}" }
301
- msg = Buffer.from(:byte, GLOBAL_REQUEST, :string, type.to_s, :bool, !callback.nil?, *extra)
302
- send_message(msg)
303
- pending_requests << callback if callback
304
- self
305
- end
306
-
307
- # Requests that a new channel be opened. By default, the channel will be
308
- # of type "session", but if you know what you're doing you can select any
309
- # of the channel types supported by the SSH protocol. The +extra+ parameters
310
- # must be even in number and conform to the same format as described for
311
- # Net::SSH::Buffer.from. If a callback is given, it will be invoked when
312
- # the server confirms that the channel opened successfully. The sole parameter
313
- # for the callback is the channel object itself.
314
- #
315
- # In general, you'll use #open_channel without any arguments; the only
316
- # time you'd want to set the channel type or pass additional initialization
317
- # data is if you were implementing an SSH extension.
318
- #
319
- # channel = ssh.open_channel do |ch|
320
- # ch.exec "grep something /some/files" do |ch, success|
321
- # ...
322
- # end
323
- # end
324
- #
325
- # channel.wait
326
- def open_channel(type="session", *extra, &on_confirm)
327
- local_id = get_next_channel_id
328
-
329
- channel = Channel.new(self, type, local_id, @max_pkt_size, @max_win_size, &on_confirm)
330
- msg = Buffer.from(:byte, CHANNEL_OPEN, :string, type, :long, local_id,
331
- :long, channel.local_maximum_window_size,
332
- :long, channel.local_maximum_packet_size, *extra)
333
- send_message(msg)
334
-
335
- channels[local_id] = channel
336
- end
337
-
338
- class StringWithExitstatus < String
339
- def initialize(str, exitstatus)
340
- super(str)
341
- @exitstatus = exitstatus
342
- end
343
-
344
- attr_reader :exitstatus
345
- end
346
-
347
- # A convenience method for executing a command and interacting with it. If
348
- # no block is given, all output is printed via $stdout and $stderr. Otherwise,
349
- # the block is called for each data and extended data packet, with three
350
- # arguments: the channel object, a symbol indicating the data type
351
- # (:stdout or :stderr), and the data (as a string).
352
- #
353
- # Note that this method returns immediately, and requires an event loop
354
- # (see Session#loop) in order for the command to actually execute.
355
- #
356
- # This is effectively identical to calling #open_channel, and then
357
- # Net::SSH::Connection::Channel#exec, and then setting up the channel
358
- # callbacks. However, for most uses, this will be sufficient.
359
- #
360
- # ssh.exec "grep something /some/files" do |ch, stream, data|
361
- # if stream == :stderr
362
- # puts "ERROR: #{data}"
363
- # else
364
- # puts data
365
- # end
366
- # end
367
- def exec(command, status: nil, &block)
368
- open_channel do |channel|
369
- channel.exec(command) do |ch, success|
370
- raise "could not execute command: #{command.inspect}" unless success
371
-
372
- if status
373
- channel.on_request("exit-status") do |ch2,data|
374
- status[:exit_code] = data.read_long
375
- end
376
-
377
- channel.on_request("exit-signal") do |ch2, data|
378
- status[:exit_signal] = data.read_long
379
- end
66
+
67
+ # Create a new connection service instance atop the given transport
68
+ # layer. Initializes the listeners to be only the underlying socket object.
69
+ def initialize(transport, options={})
70
+ self.logger = transport.logger
71
+
72
+ @transport = transport
73
+ @options = options
74
+
75
+ @channel_id_counter = -1
76
+ @channels = Hash.new(NilChannel.new(self))
77
+ @listeners = { transport.socket => nil }
78
+ @pending_requests = []
79
+ @channel_open_handlers = {}
80
+ @on_global_request = {}
81
+ @properties = (options[:properties] || {}).dup
82
+
83
+ @max_pkt_size = (options.key?(:max_pkt_size) ? options[:max_pkt_size] : 0x8000)
84
+ @max_win_size = (options.key?(:max_win_size) ? options[:max_win_size] : 0x20000)
85
+
86
+ @keepalive = Keepalive.new(self)
87
+
88
+ @event_loop = options[:event_loop] || SingleSessionEventLoop.new
89
+ @event_loop.register(self)
90
+ end
91
+
92
+ # Retrieves a custom property from this instance. This can be used to
93
+ # store additional state in applications that must manage multiple
94
+ # SSH connections.
95
+ def [](key)
96
+ @properties[key]
97
+ end
98
+
99
+ # Sets a custom property for this instance.
100
+ def []=(key, value)
101
+ @properties[key] = value
102
+ end
103
+
104
+ # Returns the name of the host that was given to the transport layer to
105
+ # connect to.
106
+ def host
107
+ transport.host
108
+ end
109
+
110
+ # Returns true if the underlying transport has been closed. Note that
111
+ # this can be a little misleading, since if the remote server has
112
+ # closed the connection, the local end will still think it is open
113
+ # until the next operation on the socket. Nevertheless, this method can
114
+ # be useful if you just want to know if _you_ have closed the connection.
115
+ def closed?
116
+ transport.closed?
117
+ end
118
+
119
+ # Closes the session gracefully, blocking until all channels have
120
+ # successfully closed, and then closes the underlying transport layer
121
+ # connection.
122
+ def close
123
+ info { "closing remaining channels (#{channels.length} open)" }
124
+ channels.each { |id, channel| channel.close }
125
+ begin
126
+ loop(0.1) { channels.any? }
127
+ rescue Net::SSH::Disconnect
128
+ raise unless channels.empty?
380
129
  end
381
-
382
- channel.on_data do |ch2, data|
383
- if block
384
- block.call(ch2, :stdout, data)
130
+ transport.close
131
+ end
132
+
133
+ # Performs a "hard" shutdown of the connection. In general, this should
134
+ # never be done, but it might be necessary (in a rescue clause, for instance,
135
+ # when the connection needs to close but you don't know the status of the
136
+ # underlying protocol's state).
137
+ def shutdown!
138
+ transport.shutdown!
139
+ end
140
+
141
+ # preserve a reference to Kernel#loop
142
+ alias :loop_forever :loop
143
+
144
+ # Returns +true+ if there are any channels currently active on this
145
+ # session. By default, this will not include "invisible" channels
146
+ # (such as those created by forwarding ports and such), but if you pass
147
+ # a +true+ value for +include_invisible+, then those will be counted.
148
+ #
149
+ # This can be useful for determining whether the event loop should continue
150
+ # to be run.
151
+ #
152
+ # ssh.loop { ssh.busy? }
153
+ def busy?(include_invisible=false)
154
+ if include_invisible
155
+ channels.any?
156
+ else
157
+ channels.any? { |id, ch| !ch[:invisible] }
158
+ end
159
+ end
160
+
161
+ # The main event loop. Calls #process until #process returns false. If a
162
+ # block is given, it is passed to #process, otherwise a default proc is
163
+ # used that just returns true if there are any channels active (see #busy?).
164
+ # The # +wait+ parameter is also passed through to #process (where it is
165
+ # interpreted as the maximum number of seconds to wait for IO.select to return).
166
+ #
167
+ # # loop for as long as there are any channels active
168
+ # ssh.loop
169
+ #
170
+ # # loop for as long as there are any channels active, but make sure
171
+ # # the event loop runs at least once per 0.1 second
172
+ # ssh.loop(0.1)
173
+ #
174
+ # # loop until ctrl-C is pressed
175
+ # int_pressed = false
176
+ # trap("INT") { int_pressed = true }
177
+ # ssh.loop(0.1) { not int_pressed }
178
+ def loop(wait=nil, &block)
179
+ running = block || Proc.new { busy? }
180
+ loop_forever { break unless process(wait, &running) }
181
+ begin
182
+ process(0)
183
+ rescue IOError => e
184
+ if e.message =~ /closed/
185
+ debug { "stream was closed after loop => shallowing exception so it will be re-raised in next loop" }
385
186
  else
386
- $stdout.print(data)
187
+ raise
387
188
  end
388
189
  end
389
-
390
- channel.on_extended_data do |ch2, type, data|
391
- if block
392
- block.call(ch2, :stderr, data)
190
+ end
191
+
192
+ # The core of the event loop. It processes a single iteration of the event
193
+ # loop. If a block is given, it should return false when the processing
194
+ # should abort, which causes #process to return false. Otherwise,
195
+ # #process returns true. The session itself is yielded to the block as its
196
+ # only argument.
197
+ #
198
+ # If +wait+ is nil (the default), this method will block until any of the
199
+ # monitored IO objects are ready to be read from or written to. If you want
200
+ # it to not block, you can pass 0, or you can pass any other numeric value
201
+ # to indicate that it should block for no more than that many seconds.
202
+ # Passing 0 is a good way to poll the connection, but if you do it too
203
+ # frequently it can make your CPU quite busy!
204
+ #
205
+ # This will also cause all active channels to be processed once each (see
206
+ # Net::SSH::Connection::Channel#on_process).
207
+ #
208
+ # TODO revise example
209
+ #
210
+ # # process multiple Net::SSH connections in parallel
211
+ # connections = [
212
+ # Net::SSH.start("host1", ...),
213
+ # Net::SSH.start("host2", ...)
214
+ # ]
215
+ #
216
+ # connections.each do |ssh|
217
+ # ssh.exec "grep something /in/some/files"
218
+ # end
219
+ #
220
+ # condition = Proc.new { |s| s.busy? }
221
+ #
222
+ # loop do
223
+ # connections.delete_if { |ssh| !ssh.process(0.1, &condition) }
224
+ # break if connections.empty?
225
+ # end
226
+ def process(wait=nil, &block)
227
+ @event_loop.process(wait, &block)
228
+ rescue StandardError
229
+ force_channel_cleanup_on_close if closed?
230
+ raise
231
+ end
232
+
233
+ # This is called internally as part of #process. It dispatches any
234
+ # available incoming packets, and then runs Net::SSH::Connection::Channel#process
235
+ # for any active channels. If a block is given, it is invoked at the
236
+ # start of the method and again at the end, and if the block ever returns
237
+ # false, this method returns false. Otherwise, it returns true.
238
+ def preprocess(&block)
239
+ return false if block_given? && !yield(self)
240
+ ev_preprocess(&block)
241
+ return false if block_given? && !yield(self)
242
+ return true
243
+ end
244
+
245
+ # Called by event loop to process available data before going to
246
+ # event multiplexing
247
+ def ev_preprocess(&block)
248
+ dispatch_incoming_packets(raise_disconnect_errors: false)
249
+ each_channel { |id, channel| channel.process unless channel.local_closed? }
250
+ end
251
+
252
+ # Returns the file descriptors the event loop should wait for read/write events,
253
+ # we also return the max wait
254
+ def ev_do_calculate_rw_wait(wait)
255
+ r = listeners.keys
256
+ w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
257
+ [r,w,io_select_wait(wait)]
258
+ end
259
+
260
+ # This is called internally as part of #process.
261
+ def postprocess(readers, writers)
262
+ ev_do_handle_events(readers, writers)
263
+ end
264
+
265
+ # It loops over the given arrays of reader IO's and writer IO's,
266
+ # processing them as needed, and
267
+ # then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
268
+ # transport layer to rekey. Then returns true.
269
+ def ev_do_handle_events(readers, writers)
270
+ Array(readers).each do |reader|
271
+ if listeners[reader]
272
+ listeners[reader].call(reader)
393
273
  else
394
- $stderr.print(data)
274
+ if reader.fill.zero?
275
+ reader.close
276
+ stop_listening_to(reader)
277
+ end
395
278
  end
396
279
  end
397
- end
398
- end
399
- end
400
-
401
- # Same as #exec, except this will block until the command finishes. Also,
402
- # if no block is given, this will return all output (stdout and stderr)
403
- # as a single string.
404
- #
405
- # matches = ssh.exec!("grep something /some/files")
406
- #
407
- # the returned string has an exitstatus method to query it's exit satus
408
- def exec!(command, status: nil, &block)
409
- block_or_concat = block || Proc.new do |ch, type, data|
410
- ch[:result] ||= ""
411
- ch[:result] << data
412
- end
413
-
414
- status ||= {}
415
- channel = exec(command, status: status, &block_or_concat)
416
- channel.wait
417
-
418
- channel[:result] ||= "" unless block
419
- channel[:result] &&= channel[:result].force_encoding("UTF-8") unless block
420
-
421
- StringWithExitstatus.new(channel[:result], status[:exit_code]) if channel[:result]
422
- end
423
-
424
- # Enqueues a message to be sent to the server as soon as the socket is
425
- # available for writing. Most programs will never need to call this, but
426
- # if you are implementing an extension to the SSH protocol, or if you
427
- # need to send a packet that Net::SSH does not directly support, you can
428
- # use this to send it.
429
- #
430
- # ssh.send_message(Buffer.from(:byte, REQUEST_SUCCESS).to_s)
431
- def send_message(message)
432
- transport.enqueue_message(message)
433
- end
434
-
435
- # Adds an IO object for the event loop to listen to. If a callback
436
- # is given, it will be invoked when the io is ready to be read, otherwise,
437
- # the io will merely have its #fill method invoked.
438
- #
439
- # Any +io+ value passed to this method _must_ have mixed into it the
440
- # Net::SSH::BufferedIo functionality, typically by calling #extend on the
441
- # object.
442
- #
443
- # The following example executes a process on the remote server, opens
444
- # a socket to somewhere, and then pipes data from that socket to the
445
- # remote process' stdin stream:
446
- #
447
- # channel = ssh.open_channel do |ch|
448
- # ch.exec "/some/process/that/wants/input" do |ch, success|
449
- # abort "can't execute!" unless success
450
- #
451
- # io = TCPSocket.new(somewhere, port)
452
- # io.extend(Net::SSH::BufferedIo)
453
- # ssh.listen_to(io)
454
- #
455
- # ch.on_process do
456
- # if io.available > 0
457
- # ch.send_data(io.read_available)
458
- # end
459
- # end
460
- #
461
- # ch.on_close do
462
- # ssh.stop_listening_to(io)
463
- # io.close
464
- # end
465
- # end
466
- # end
467
- #
468
- # channel.wait
469
- def listen_to(io, &callback)
470
- listeners[io] = callback
471
- end
472
-
473
- # Removes the given io object from the listeners collection, so that the
474
- # event loop will no longer monitor it.
475
- def stop_listening_to(io)
476
- listeners.delete(io)
477
- end
478
-
479
- # Returns a reference to the Net::SSH::Service::Forward service, which can
480
- # be used for forwarding ports over SSH.
481
- def forward
482
- @forward ||= Service::Forward.new(self)
483
- end
484
-
485
- # Registers a handler to be invoked when the server wants to open a
486
- # channel on the client. The callback receives the connection object,
487
- # the new channel object, and the packet itself as arguments, and should
488
- # raise ChannelOpenFailed if it is unable to open the channel for some
489
- # reason. Otherwise, the channel will be opened and a confirmation message
490
- # sent to the server.
491
- #
492
- # This is used by the Net::SSH::Service::Forward service to open a channel
493
- # when a remote forwarded port receives a connection. However, you are
494
- # welcome to register handlers for other channel types, as needed.
495
- def on_open_channel(type, &block)
496
- channel_open_handlers[type] = block
497
- end
498
-
499
- # Registers a handler to be invoked when the server sends a global request
500
- # of the given type. The callback receives the request data as the first
501
- # parameter, and true/false as the second (indicating whether a response
502
- # is required). If the callback sends the response, it should return
503
- # :sent. Otherwise, if it returns true, REQUEST_SUCCESS will be sent, and
504
- # if it returns false, REQUEST_FAILURE will be sent.
505
- def on_global_request(type, &block)
506
- old, @on_global_request[type] = @on_global_request[type], block
507
- old
508
- end
509
-
510
- def cleanup_channel(channel)
511
- if channel.local_closed? and channel.remote_closed?
512
- info { "#{host} delete channel #{channel.local_id} which closed locally and remotely" }
513
- channels.delete(channel.local_id)
514
- end
515
- end
516
-
517
- # If the #preprocess and #postprocess callbacks for this session need to run
518
- # periodically, this method returns the maximum number of seconds which may
519
- # pass between callbacks.
520
- def max_select_wait_time
521
- @keepalive.interval if @keepalive.enabled?
522
- end
523
-
524
-
525
- private
526
-
527
- # iterate channels with the posibility of callbacks opening new channels during the iteration
528
- def each_channel(&block)
529
- channels.dup.each(&block)
530
- end
531
-
532
- # Read all pending packets from the connection and dispatch them as
533
- # appropriate. Returns as soon as there are no more pending packets.
534
- def dispatch_incoming_packets(raise_disconnect_errors: true)
535
- while packet = transport.poll_message
536
- unless MAP.key?(packet.type)
537
- raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})"
280
+
281
+ Array(writers).each do |writer|
282
+ writer.send_pending
538
283
  end
539
-
540
- send(MAP[packet.type], packet)
541
284
  end
542
- rescue
543
- force_channel_cleanup_on_close if closed?
544
- raise if raise_disconnect_errors || !$!.is_a?(Net::SSH::Disconnect)
545
- end
546
-
547
- # Returns the next available channel id to be assigned, and increments
548
- # the counter.
549
- def get_next_channel_id
550
- @channel_id_counter += 1
551
- end
552
-
553
- def force_channel_cleanup_on_close
554
- channels.each do |id, channel|
555
- channel_closed(channel)
285
+
286
+ # calls Net::SSH::Transport::Session#rekey_as_needed to allow the
287
+ # transport layer to rekey
288
+ def ev_do_postprocess(was_events)
289
+ @keepalive.send_as_needed(was_events)
290
+ transport.rekey_as_needed
291
+ true
556
292
  end
557
- end
558
-
559
- def channel_closed(channel)
560
- channel.remote_closed!
561
- channel.close
562
-
563
- cleanup_channel(channel)
564
- channel.do_close
565
- end
566
-
567
- # Invoked when a global request is received. The registered global
568
- # request callback will be invoked, if one exists, and the necessary
569
- # reply returned.
570
- def global_request(packet)
571
- info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" }
572
- callback = @on_global_request[packet[:request_type]]
573
- result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false
574
-
575
- if result != :sent && result != true && result != false
576
- raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}"
293
+
294
+ # Send a global request of the given type. The +extra+ parameters must
295
+ # be even in number, and conform to the same format as described for
296
+ # Net::SSH::Buffer.from. If a callback is not specified, the request will
297
+ # not require a response from the server, otherwise the server is required
298
+ # to respond and indicate whether the request was successful or not. This
299
+ # success or failure is indicated by the callback being invoked, with the
300
+ # first parameter being true or false (success, or failure), and the second
301
+ # being the packet itself.
302
+ #
303
+ # Generally, Net::SSH will manage global requests that need to be sent
304
+ # (e.g. port forward requests and such are handled in the Net::SSH::Service::Forward
305
+ # class, for instance). However, there may be times when you need to
306
+ # send a global request that isn't explicitly handled by Net::SSH, and so
307
+ # this method is available to you.
308
+ #
309
+ # ssh.send_global_request("keep-alive@openssh.com")
310
+ def send_global_request(type, *extra, &callback)
311
+ info { "sending global request #{type}" }
312
+ msg = Buffer.from(:byte, GLOBAL_REQUEST, :string, type.to_s, :bool, !callback.nil?, *extra)
313
+ send_message(msg)
314
+ pending_requests << callback if callback
315
+ self
577
316
  end
578
-
579
- if packet[:want_reply] && result != :sent
580
- msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE)
317
+
318
+ # Requests that a new channel be opened. By default, the channel will be
319
+ # of type "session", but if you know what you're doing you can select any
320
+ # of the channel types supported by the SSH protocol. The +extra+ parameters
321
+ # must be even in number and conform to the same format as described for
322
+ # Net::SSH::Buffer.from. If a callback is given, it will be invoked when
323
+ # the server confirms that the channel opened successfully. The sole parameter
324
+ # for the callback is the channel object itself.
325
+ #
326
+ # In general, you'll use #open_channel without any arguments; the only
327
+ # time you'd want to set the channel type or pass additional initialization
328
+ # data is if you were implementing an SSH extension.
329
+ #
330
+ # channel = ssh.open_channel do |ch|
331
+ # ch.exec "grep something /some/files" do |ch, success|
332
+ # ...
333
+ # end
334
+ # end
335
+ #
336
+ # channel.wait
337
+ def open_channel(type="session", *extra, &on_confirm)
338
+ local_id = get_next_channel_id
339
+
340
+ channel = Channel.new(self, type, local_id, @max_pkt_size, @max_win_size, &on_confirm)
341
+ msg = Buffer.from(:byte, CHANNEL_OPEN, :string, type, :long, local_id,
342
+ :long, channel.local_maximum_window_size,
343
+ :long, channel.local_maximum_packet_size, *extra)
581
344
  send_message(msg)
345
+
346
+ channels[local_id] = channel
582
347
  end
583
- end
584
-
585
- # Invokes the next pending request callback with +true+.
586
- def request_success(packet)
587
- info { "global request success" }
588
- callback = pending_requests.shift
589
- callback.call(true, packet) if callback
590
- end
591
-
592
- # Invokes the next pending request callback with +false+.
593
- def request_failure(packet)
594
- info { "global request failure" }
595
- callback = pending_requests.shift
596
- callback.call(false, packet) if callback
597
- end
598
-
599
- # Called when the server wants to open a channel. If no registered
600
- # channel handler exists for the given channel type, CHANNEL_OPEN_FAILURE
601
- # is returned, otherwise the callback is invoked and everything proceeds
602
- # accordingly.
603
- def channel_open(packet)
604
- info { "channel open #{packet[:channel_type]}" }
605
-
606
- local_id = get_next_channel_id
607
-
608
- channel = Channel.new(self, packet[:channel_type], local_id, @max_pkt_size, @max_win_size)
609
- channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
610
-
611
- callback = channel_open_handlers[packet[:channel_type]]
612
-
613
- if callback
614
- begin
615
- callback[self, channel, packet]
616
- rescue ChannelOpenFailed => err
617
- failure = [err.code, err.reason]
348
+
349
+ class StringWithExitstatus < String
350
+ def initialize(str, exitstatus)
351
+ super(str)
352
+ @exitstatus = exitstatus
353
+ end
354
+
355
+ attr_reader :exitstatus
356
+ end
357
+
358
+ # A convenience method for executing a command and interacting with it. If
359
+ # no block is given, all output is printed via $stdout and $stderr. Otherwise,
360
+ # the block is called for each data and extended data packet, with three
361
+ # arguments: the channel object, a symbol indicating the data type
362
+ # (:stdout or :stderr), and the data (as a string).
363
+ #
364
+ # Note that this method returns immediately, and requires an event loop
365
+ # (see Session#loop) in order for the command to actually execute.
366
+ #
367
+ # This is effectively identical to calling #open_channel, and then
368
+ # Net::SSH::Connection::Channel#exec, and then setting up the channel
369
+ # callbacks. However, for most uses, this will be sufficient.
370
+ #
371
+ # ssh.exec "grep something /some/files" do |ch, stream, data|
372
+ # if stream == :stderr
373
+ # puts "ERROR: #{data}"
374
+ # else
375
+ # puts data
376
+ # end
377
+ # end
378
+ def exec(command, status: nil, &block)
379
+ open_channel do |channel|
380
+ channel.exec(command) do |ch, success|
381
+ raise "could not execute command: #{command.inspect}" unless success
382
+
383
+ if status
384
+ channel.on_request("exit-status") do |ch2,data|
385
+ status[:exit_code] = data.read_long
386
+ end
387
+
388
+ channel.on_request("exit-signal") do |ch2, data|
389
+ status[:exit_signal] = data.read_long
390
+ end
391
+ end
392
+
393
+ channel.on_data do |ch2, data|
394
+ if block
395
+ block.call(ch2, :stdout, data)
396
+ else
397
+ $stdout.print(data)
398
+ end
399
+ end
400
+
401
+ channel.on_extended_data do |ch2, type, data|
402
+ if block
403
+ block.call(ch2, :stderr, data)
404
+ else
405
+ $stderr.print(data)
406
+ end
407
+ end
408
+ end
409
+ end
410
+ end
411
+
412
+ # Same as #exec, except this will block until the command finishes. Also,
413
+ # if no block is given, this will return all output (stdout and stderr)
414
+ # as a single string.
415
+ #
416
+ # matches = ssh.exec!("grep something /some/files")
417
+ #
418
+ # the returned string has an exitstatus method to query it's exit satus
419
+ def exec!(command, status: nil, &block)
420
+ block_or_concat = block || Proc.new do |ch, type, data|
421
+ ch[:result] ||= ""
422
+ ch[:result] << data
423
+ end
424
+
425
+ status ||= {}
426
+ channel = exec(command, status: status, &block_or_concat)
427
+ channel.wait
428
+
429
+ channel[:result] ||= "" unless block
430
+ channel[:result] &&= channel[:result].force_encoding("UTF-8") unless block
431
+
432
+ StringWithExitstatus.new(channel[:result], status[:exit_code]) if channel[:result]
433
+ end
434
+
435
+ # Enqueues a message to be sent to the server as soon as the socket is
436
+ # available for writing. Most programs will never need to call this, but
437
+ # if you are implementing an extension to the SSH protocol, or if you
438
+ # need to send a packet that Net::SSH does not directly support, you can
439
+ # use this to send it.
440
+ #
441
+ # ssh.send_message(Buffer.from(:byte, REQUEST_SUCCESS).to_s)
442
+ def send_message(message)
443
+ transport.enqueue_message(message)
444
+ end
445
+
446
+ # Adds an IO object for the event loop to listen to. If a callback
447
+ # is given, it will be invoked when the io is ready to be read, otherwise,
448
+ # the io will merely have its #fill method invoked.
449
+ #
450
+ # Any +io+ value passed to this method _must_ have mixed into it the
451
+ # Net::SSH::BufferedIo functionality, typically by calling #extend on the
452
+ # object.
453
+ #
454
+ # The following example executes a process on the remote server, opens
455
+ # a socket to somewhere, and then pipes data from that socket to the
456
+ # remote process' stdin stream:
457
+ #
458
+ # channel = ssh.open_channel do |ch|
459
+ # ch.exec "/some/process/that/wants/input" do |ch, success|
460
+ # abort "can't execute!" unless success
461
+ #
462
+ # io = TCPSocket.new(somewhere, port)
463
+ # io.extend(Net::SSH::BufferedIo)
464
+ # ssh.listen_to(io)
465
+ #
466
+ # ch.on_process do
467
+ # if io.available > 0
468
+ # ch.send_data(io.read_available)
469
+ # end
470
+ # end
471
+ #
472
+ # ch.on_close do
473
+ # ssh.stop_listening_to(io)
474
+ # io.close
475
+ # end
476
+ # end
477
+ # end
478
+ #
479
+ # channel.wait
480
+ def listen_to(io, &callback)
481
+ listeners[io] = callback
482
+ end
483
+
484
+ # Removes the given io object from the listeners collection, so that the
485
+ # event loop will no longer monitor it.
486
+ def stop_listening_to(io)
487
+ listeners.delete(io)
488
+ end
489
+
490
+ # Returns a reference to the Net::SSH::Service::Forward service, which can
491
+ # be used for forwarding ports over SSH.
492
+ def forward
493
+ @forward ||= Service::Forward.new(self)
494
+ end
495
+
496
+ # Registers a handler to be invoked when the server wants to open a
497
+ # channel on the client. The callback receives the connection object,
498
+ # the new channel object, and the packet itself as arguments, and should
499
+ # raise ChannelOpenFailed if it is unable to open the channel for some
500
+ # reason. Otherwise, the channel will be opened and a confirmation message
501
+ # sent to the server.
502
+ #
503
+ # This is used by the Net::SSH::Service::Forward service to open a channel
504
+ # when a remote forwarded port receives a connection. However, you are
505
+ # welcome to register handlers for other channel types, as needed.
506
+ def on_open_channel(type, &block)
507
+ channel_open_handlers[type] = block
508
+ end
509
+
510
+ # Registers a handler to be invoked when the server sends a global request
511
+ # of the given type. The callback receives the request data as the first
512
+ # parameter, and true/false as the second (indicating whether a response
513
+ # is required). If the callback sends the response, it should return
514
+ # :sent. Otherwise, if it returns true, REQUEST_SUCCESS will be sent, and
515
+ # if it returns false, REQUEST_FAILURE will be sent.
516
+ def on_global_request(type, &block)
517
+ old, @on_global_request[type] = @on_global_request[type], block
518
+ old
519
+ end
520
+
521
+ def cleanup_channel(channel)
522
+ if channel.local_closed? and channel.remote_closed?
523
+ info { "#{host} delete channel #{channel.local_id} which closed locally and remotely" }
524
+ channels.delete(channel.local_id)
525
+ end
526
+ end
527
+
528
+ # If the #preprocess and #postprocess callbacks for this session need to run
529
+ # periodically, this method returns the maximum number of seconds which may
530
+ # pass between callbacks.
531
+ def max_select_wait_time
532
+ @keepalive.interval if @keepalive.enabled?
533
+ end
534
+
535
+ private
536
+
537
+ # iterate channels with the posibility of callbacks opening new channels during the iteration
538
+ def each_channel(&block)
539
+ channels.dup.each(&block)
540
+ end
541
+
542
+ # Read all pending packets from the connection and dispatch them as
543
+ # appropriate. Returns as soon as there are no more pending packets.
544
+ def dispatch_incoming_packets(raise_disconnect_errors: true)
545
+ while packet = transport.poll_message
546
+ raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})" unless MAP.key?(packet.type)
547
+
548
+ send(MAP[packet.type], packet)
549
+ end
550
+ rescue StandardError
551
+ force_channel_cleanup_on_close if closed?
552
+ raise if raise_disconnect_errors || !$!.is_a?(Net::SSH::Disconnect)
553
+ end
554
+
555
+ # Returns the next available channel id to be assigned, and increments
556
+ # the counter.
557
+ def get_next_channel_id
558
+ @channel_id_counter += 1
559
+ end
560
+
561
+ def force_channel_cleanup_on_close
562
+ channels.each do |id, channel|
563
+ channel_closed(channel)
564
+ end
565
+ end
566
+
567
+ def channel_closed(channel)
568
+ channel.remote_closed!
569
+ channel.close
570
+
571
+ cleanup_channel(channel)
572
+ channel.do_close
573
+ end
574
+
575
+ # Invoked when a global request is received. The registered global
576
+ # request callback will be invoked, if one exists, and the necessary
577
+ # reply returned.
578
+ def global_request(packet)
579
+ info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" }
580
+ callback = @on_global_request[packet[:request_type]]
581
+ result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false
582
+
583
+ if result != :sent && result != true && result != false
584
+ raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}"
585
+ end
586
+
587
+ if packet[:want_reply] && result != :sent
588
+ msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE)
589
+ send_message(msg)
590
+ end
591
+ end
592
+
593
+ # Invokes the next pending request callback with +true+.
594
+ def request_success(packet)
595
+ info { "global request success" }
596
+ callback = pending_requests.shift
597
+ callback.call(true, packet) if callback
598
+ end
599
+
600
+ # Invokes the next pending request callback with +false+.
601
+ def request_failure(packet)
602
+ info { "global request failure" }
603
+ callback = pending_requests.shift
604
+ callback.call(false, packet) if callback
605
+ end
606
+
607
+ # Called when the server wants to open a channel. If no registered
608
+ # channel handler exists for the given channel type, CHANNEL_OPEN_FAILURE
609
+ # is returned, otherwise the callback is invoked and everything proceeds
610
+ # accordingly.
611
+ def channel_open(packet)
612
+ info { "channel open #{packet[:channel_type]}" }
613
+
614
+ local_id = get_next_channel_id
615
+
616
+ channel = Channel.new(self, packet[:channel_type], local_id, @max_pkt_size, @max_win_size)
617
+ channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
618
+
619
+ callback = channel_open_handlers[packet[:channel_type]]
620
+
621
+ if callback
622
+ begin
623
+ callback[self, channel, packet]
624
+ rescue ChannelOpenFailed => err
625
+ failure = [err.code, err.reason]
626
+ else
627
+ channels[local_id] = channel
628
+ msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long,
629
+ channel.local_id, :long, channel.local_maximum_window_size, :long,
630
+ channel.local_maximum_packet_size)
631
+ end
618
632
  else
619
- channels[local_id] = channel
620
- msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long, channel.local_id, :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size)
633
+ failure = [3, "unknown channel type #{channel.type}"]
634
+ end
635
+
636
+ if failure
637
+ error { failure.inspect }
638
+ msg = Buffer.from(:byte, CHANNEL_OPEN_FAILURE, :long, channel.remote_id, :long, failure[0], :string, failure[1], :string, "")
621
639
  end
622
- else
623
- failure = [3, "unknown channel type #{channel.type}"]
640
+
641
+ send_message(msg)
624
642
  end
625
-
626
- if failure
627
- error { failure.inspect }
628
- msg = Buffer.from(:byte, CHANNEL_OPEN_FAILURE, :long, channel.remote_id, :long, failure[0], :string, failure[1], :string, "")
643
+
644
+ def channel_open_confirmation(packet)
645
+ info { "channel_open_confirmation: #{packet[:local_id]} #{packet[:remote_id]} #{packet[:window_size]} #{packet[:packet_size]}" }
646
+ channel = channels[packet[:local_id]]
647
+ channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
648
+ end
649
+
650
+ def channel_open_failure(packet)
651
+ error { "channel_open_failed: #{packet[:local_id]} #{packet[:reason_code]} #{packet[:description]}" }
652
+ channel = channels.delete(packet[:local_id])
653
+ channel.do_open_failed(packet[:reason_code], packet[:description])
654
+ end
655
+
656
+ def channel_window_adjust(packet)
657
+ info { "channel_window_adjust: #{packet[:local_id]} +#{packet[:extra_bytes]}" }
658
+ channels[packet[:local_id]].do_window_adjust(packet[:extra_bytes])
659
+ end
660
+
661
+ def channel_request(packet)
662
+ info { "channel_request: #{packet[:local_id]} #{packet[:request]} #{packet[:want_reply]}" }
663
+ channels[packet[:local_id]].do_request(packet[:request], packet[:want_reply], packet[:request_data])
664
+ end
665
+
666
+ def channel_data(packet)
667
+ info { "channel_data: #{packet[:local_id]} #{packet[:data].length}b" }
668
+ channels[packet[:local_id]].do_data(packet[:data])
669
+ end
670
+
671
+ def channel_extended_data(packet)
672
+ info { "channel_extended_data: #{packet[:local_id]} #{packet[:data_type]} #{packet[:data].length}b" }
673
+ channels[packet[:local_id]].do_extended_data(packet[:data_type], packet[:data])
674
+ end
675
+
676
+ def channel_eof(packet)
677
+ info { "channel_eof: #{packet[:local_id]}" }
678
+ channels[packet[:local_id]].do_eof
679
+ end
680
+
681
+ def channel_close(packet)
682
+ info { "channel_close: #{packet[:local_id]}" }
683
+
684
+ channel = channels[packet[:local_id]]
685
+ channel_closed(channel)
686
+ end
687
+
688
+ def channel_success(packet)
689
+ info { "channel_success: #{packet[:local_id]}" }
690
+ channels[packet[:local_id]].do_success
691
+ end
692
+
693
+ def channel_failure(packet)
694
+ info { "channel_failure: #{packet[:local_id]}" }
695
+ channels[packet[:local_id]].do_failure
696
+ end
697
+
698
+ def io_select_wait(wait)
699
+ [wait, max_select_wait_time].compact.min
700
+ end
701
+
702
+ MAP = Constants.constants.each_with_object({}) do |name, memo|
703
+ value = const_get(name)
704
+ next unless Integer === value
705
+ memo[value] = name.downcase.to_sym
629
706
  end
630
-
631
- send_message(msg)
632
- end
633
-
634
- def channel_open_confirmation(packet)
635
- info { "channel_open_confirmation: #{packet[:local_id]} #{packet[:remote_id]} #{packet[:window_size]} #{packet[:packet_size]}" }
636
- channel = channels[packet[:local_id]]
637
- channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
638
- end
639
-
640
- def channel_open_failure(packet)
641
- error { "channel_open_failed: #{packet[:local_id]} #{packet[:reason_code]} #{packet[:description]}" }
642
- channel = channels.delete(packet[:local_id])
643
- channel.do_open_failed(packet[:reason_code], packet[:description])
644
- end
645
-
646
- def channel_window_adjust(packet)
647
- info { "channel_window_adjust: #{packet[:local_id]} +#{packet[:extra_bytes]}" }
648
- channels[packet[:local_id]].do_window_adjust(packet[:extra_bytes])
649
- end
650
-
651
- def channel_request(packet)
652
- info { "channel_request: #{packet[:local_id]} #{packet[:request]} #{packet[:want_reply]}" }
653
- channels[packet[:local_id]].do_request(packet[:request], packet[:want_reply], packet[:request_data])
654
- end
655
-
656
- def channel_data(packet)
657
- info { "channel_data: #{packet[:local_id]} #{packet[:data].length}b" }
658
- channels[packet[:local_id]].do_data(packet[:data])
659
- end
660
-
661
- def channel_extended_data(packet)
662
- info { "channel_extended_data: #{packet[:local_id]} #{packet[:data_type]} #{packet[:data].length}b" }
663
- channels[packet[:local_id]].do_extended_data(packet[:data_type], packet[:data])
664
- end
665
-
666
- def channel_eof(packet)
667
- info { "channel_eof: #{packet[:local_id]}" }
668
- channels[packet[:local_id]].do_eof
669
- end
670
-
671
- def channel_close(packet)
672
- info { "channel_close: #{packet[:local_id]}" }
673
-
674
- channel = channels[packet[:local_id]]
675
- channel_closed(channel)
676
- end
677
-
678
- def channel_success(packet)
679
- info { "channel_success: #{packet[:local_id]}" }
680
- channels[packet[:local_id]].do_success
681
- end
682
-
683
- def channel_failure(packet)
684
- info { "channel_failure: #{packet[:local_id]}" }
685
- channels[packet[:local_id]].do_failure
686
- end
687
-
688
- def io_select_wait(wait)
689
- [wait, max_select_wait_time].compact.min
690
707
  end
691
708
 
692
- MAP = Constants.constants.inject({}) do |memo, name|
693
- value = const_get(name)
694
- next unless Integer === value
695
- memo[value] = name.downcase.to_sym
696
- memo
697
- end
709
+ end
698
710
  end
699
-
700
- end; end; end
711
+ end