net-ssh 5.0.0.beta1 → 5.0.0.beta2

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