sonixlabs-net-ssh 2.3.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 (123) hide show
  1. data/CHANGELOG.rdoc +262 -0
  2. data/Manifest +121 -0
  3. data/README.rdoc +184 -0
  4. data/Rakefile +86 -0
  5. data/Rudyfile +96 -0
  6. data/THANKS.rdoc +19 -0
  7. data/lib/net/ssh.rb +223 -0
  8. data/lib/net/ssh/authentication/agent.rb +179 -0
  9. data/lib/net/ssh/authentication/constants.rb +18 -0
  10. data/lib/net/ssh/authentication/key_manager.rb +253 -0
  11. data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  12. data/lib/net/ssh/authentication/methods/hostbased.rb +75 -0
  13. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +70 -0
  14. data/lib/net/ssh/authentication/methods/password.rb +43 -0
  15. data/lib/net/ssh/authentication/methods/publickey.rb +96 -0
  16. data/lib/net/ssh/authentication/pageant.rb +264 -0
  17. data/lib/net/ssh/authentication/session.rb +146 -0
  18. data/lib/net/ssh/buffer.rb +340 -0
  19. data/lib/net/ssh/buffered_io.rb +198 -0
  20. data/lib/net/ssh/config.rb +207 -0
  21. data/lib/net/ssh/connection/channel.rb +630 -0
  22. data/lib/net/ssh/connection/constants.rb +33 -0
  23. data/lib/net/ssh/connection/session.rb +597 -0
  24. data/lib/net/ssh/connection/term.rb +178 -0
  25. data/lib/net/ssh/errors.rb +88 -0
  26. data/lib/net/ssh/key_factory.rb +102 -0
  27. data/lib/net/ssh/known_hosts.rb +129 -0
  28. data/lib/net/ssh/loggable.rb +61 -0
  29. data/lib/net/ssh/packet.rb +102 -0
  30. data/lib/net/ssh/prompt.rb +93 -0
  31. data/lib/net/ssh/proxy/command.rb +75 -0
  32. data/lib/net/ssh/proxy/errors.rb +14 -0
  33. data/lib/net/ssh/proxy/http.rb +94 -0
  34. data/lib/net/ssh/proxy/socks4.rb +70 -0
  35. data/lib/net/ssh/proxy/socks5.rb +142 -0
  36. data/lib/net/ssh/ruby_compat.rb +43 -0
  37. data/lib/net/ssh/service/forward.rb +298 -0
  38. data/lib/net/ssh/test.rb +89 -0
  39. data/lib/net/ssh/test/channel.rb +129 -0
  40. data/lib/net/ssh/test/extensions.rb +152 -0
  41. data/lib/net/ssh/test/kex.rb +44 -0
  42. data/lib/net/ssh/test/local_packet.rb +51 -0
  43. data/lib/net/ssh/test/packet.rb +81 -0
  44. data/lib/net/ssh/test/remote_packet.rb +38 -0
  45. data/lib/net/ssh/test/script.rb +157 -0
  46. data/lib/net/ssh/test/socket.rb +64 -0
  47. data/lib/net/ssh/transport/algorithms.rb +386 -0
  48. data/lib/net/ssh/transport/cipher_factory.rb +79 -0
  49. data/lib/net/ssh/transport/constants.rb +30 -0
  50. data/lib/net/ssh/transport/hmac.rb +42 -0
  51. data/lib/net/ssh/transport/hmac/abstract.rb +79 -0
  52. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  53. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  54. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  55. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  56. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  57. data/lib/net/ssh/transport/hmac/sha2_256.rb +15 -0
  58. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +13 -0
  59. data/lib/net/ssh/transport/hmac/sha2_512.rb +14 -0
  60. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +13 -0
  61. data/lib/net/ssh/transport/identity_cipher.rb +55 -0
  62. data/lib/net/ssh/transport/kex.rb +17 -0
  63. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  64. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +80 -0
  65. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +15 -0
  66. data/lib/net/ssh/transport/key_expander.rb +26 -0
  67. data/lib/net/ssh/transport/openssl.rb +127 -0
  68. data/lib/net/ssh/transport/packet_stream.rb +235 -0
  69. data/lib/net/ssh/transport/server_version.rb +71 -0
  70. data/lib/net/ssh/transport/session.rb +278 -0
  71. data/lib/net/ssh/transport/state.rb +206 -0
  72. data/lib/net/ssh/verifiers/lenient.rb +30 -0
  73. data/lib/net/ssh/verifiers/null.rb +12 -0
  74. data/lib/net/ssh/verifiers/strict.rb +53 -0
  75. data/lib/net/ssh/version.rb +62 -0
  76. data/lib/sonixlabs-net-ssh.rb +1 -0
  77. data/net-ssh.gemspec +145 -0
  78. data/setup.rb +1585 -0
  79. data/support/arcfour_check.rb +20 -0
  80. data/support/ssh_tunnel_bug.rb +65 -0
  81. data/test/authentication/methods/common.rb +28 -0
  82. data/test/authentication/methods/test_abstract.rb +51 -0
  83. data/test/authentication/methods/test_hostbased.rb +114 -0
  84. data/test/authentication/methods/test_keyboard_interactive.rb +100 -0
  85. data/test/authentication/methods/test_password.rb +52 -0
  86. data/test/authentication/methods/test_publickey.rb +148 -0
  87. data/test/authentication/test_agent.rb +205 -0
  88. data/test/authentication/test_key_manager.rb +171 -0
  89. data/test/authentication/test_session.rb +106 -0
  90. data/test/common.rb +107 -0
  91. data/test/configs/eqsign +3 -0
  92. data/test/configs/exact_match +8 -0
  93. data/test/configs/host_plus +10 -0
  94. data/test/configs/multihost +4 -0
  95. data/test/configs/wild_cards +14 -0
  96. data/test/connection/test_channel.rb +467 -0
  97. data/test/connection/test_session.rb +488 -0
  98. data/test/test_all.rb +9 -0
  99. data/test/test_buffer.rb +336 -0
  100. data/test/test_buffered_io.rb +63 -0
  101. data/test/test_config.rb +120 -0
  102. data/test/test_key_factory.rb +79 -0
  103. data/test/transport/hmac/test_md5.rb +39 -0
  104. data/test/transport/hmac/test_md5_96.rb +25 -0
  105. data/test/transport/hmac/test_none.rb +34 -0
  106. data/test/transport/hmac/test_sha1.rb +34 -0
  107. data/test/transport/hmac/test_sha1_96.rb +25 -0
  108. data/test/transport/hmac/test_sha2_256.rb +35 -0
  109. data/test/transport/hmac/test_sha2_256_96.rb +25 -0
  110. data/test/transport/hmac/test_sha2_512.rb +35 -0
  111. data/test/transport/hmac/test_sha2_512_96.rb +25 -0
  112. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  113. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  114. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +33 -0
  115. data/test/transport/test_algorithms.rb +308 -0
  116. data/test/transport/test_cipher_factory.rb +213 -0
  117. data/test/transport/test_hmac.rb +34 -0
  118. data/test/transport/test_identity_cipher.rb +40 -0
  119. data/test/transport/test_packet_stream.rb +736 -0
  120. data/test/transport/test_server_version.rb +78 -0
  121. data/test/transport/test_session.rb +315 -0
  122. data/test/transport/test_state.rb +179 -0
  123. metadata +178 -0
@@ -0,0 +1,207 @@
1
+ module Net; module SSH
2
+
3
+ # The Net::SSH::Config class is used to parse OpenSSH configuration files,
4
+ # and translates that syntax into the configuration syntax that Net::SSH
5
+ # understands. This lets Net::SSH scripts read their configuration (to
6
+ # some extent) from OpenSSH configuration files (~/.ssh/config, /etc/ssh_config,
7
+ # and so forth).
8
+ #
9
+ # Only a subset of OpenSSH configuration options are understood:
10
+ #
11
+ # * Ciphers => maps to the :encryption option
12
+ # * Compression => :compression
13
+ # * CompressionLevel => :compression_level
14
+ # * ConnectTimeout => maps to the :timeout option
15
+ # * ForwardAgent => :forward_agent
16
+ # * GlobalKnownHostsFile => :global_known_hosts_file
17
+ # * HostBasedAuthentication => maps to the :auth_methods option
18
+ # * HostKeyAlgorithms => maps to :host_key option
19
+ # * HostKeyAlias => :host_key_alias
20
+ # * HostName => :host_name
21
+ # * IdentityFile => maps to the :keys option
22
+ # * IdentitiesOnly => :keys_only
23
+ # * Macs => maps to the :hmac option
24
+ # * PasswordAuthentication => maps to the :auth_methods option
25
+ # * Port => :port
26
+ # * PreferredAuthentications => maps to the :auth_methods option
27
+ # * ProxyCommand => maps to the :proxy option
28
+ # * RekeyLimit => :rekey_limit
29
+ # * User => :user
30
+ # * UserKnownHostsFile => :user_known_hosts_file
31
+ #
32
+ # Note that you will never need to use this class directly--you can control
33
+ # whether the OpenSSH configuration files are read by passing the :config
34
+ # option to Net::SSH.start. (They are, by default.)
35
+ class Config
36
+ class << self
37
+ @@default_files = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config)
38
+
39
+ # Returns an array of locations of OpenSSH configuration files
40
+ # to parse by default.
41
+ def default_files
42
+ @@default_files
43
+ end
44
+
45
+ # Loads the configuration data for the given +host+ from all of the
46
+ # given +files+ (defaulting to the list of files returned by
47
+ # #default_files), translates the resulting hash into the options
48
+ # recognized by Net::SSH, and returns them.
49
+ def for(host, files=default_files)
50
+ translate(files.inject({}) { |settings, file| load(file, host, settings) })
51
+ end
52
+
53
+ # Load the OpenSSH configuration settings in the given +file+ for the
54
+ # given +host+. If +settings+ is given, the options are merged into
55
+ # that hash, with existing values taking precedence over newly parsed
56
+ # ones. Returns a hash containing the OpenSSH options. (See
57
+ # #translate for how to convert the OpenSSH options into Net::SSH
58
+ # options.)
59
+ def load(path, host, settings={})
60
+ file = File.expand_path(path)
61
+ return settings unless File.readable?(file)
62
+
63
+ globals = {}
64
+ matched_host = nil
65
+ multi_host = []
66
+ seen_host = false
67
+ IO.foreach(file) do |line|
68
+ next if line =~ /^\s*(?:#.*)?$/
69
+
70
+ if line =~ /^\s*(\S+)\s*=(.*)$/
71
+ key, value = $1, $2
72
+ else
73
+ key, value = line.strip.split(/\s+/, 2)
74
+ end
75
+
76
+ # silently ignore malformed entries
77
+ next if value.nil?
78
+
79
+ key.downcase!
80
+ value = $1 if value =~ /^"(.*)"$/
81
+
82
+ value = case value.strip
83
+ when /^\d+$/ then value.to_i
84
+ when /^no$/i then false
85
+ when /^yes$/i then true
86
+ else value
87
+ end
88
+
89
+ if key == 'host'
90
+ # Support "Host host1 host2 hostN".
91
+ # See http://github.com/net-ssh/net-ssh/issues#issue/6
92
+ multi_host = value.to_s.split(/\s+/)
93
+ matched_host = multi_host.select { |h| host =~ pattern2regex(h) }.first
94
+ seen_host = true
95
+ elsif !seen_host
96
+ if key == 'identityfile'
97
+ (globals[key] ||= []) << value
98
+ else
99
+ globals[key] = value unless settings.key?(key)
100
+ end
101
+ elsif !matched_host.nil?
102
+ if key == 'identityfile'
103
+ (settings[key] ||= []) << value
104
+ else
105
+ settings[key] = value unless settings.key?(key)
106
+ end
107
+ end
108
+ end
109
+
110
+ settings = globals.merge(settings) if globals
111
+
112
+ return settings
113
+ end
114
+
115
+ # Given a hash of OpenSSH configuration options, converts them into
116
+ # a hash of Net::SSH options. Unrecognized options are ignored. The
117
+ # +settings+ hash must have Strings for keys, all downcased, and
118
+ # the returned hash will have Symbols for keys.
119
+ def translate(settings)
120
+ settings.inject({}) do |hash, (key, value)|
121
+ case key
122
+ when 'bindaddress' then
123
+ hash[:bind_address] = value
124
+ when 'ciphers' then
125
+ hash[:encryption] = value.split(/,/)
126
+ when 'compression' then
127
+ hash[:compression] = value
128
+ when 'compressionlevel' then
129
+ hash[:compression_level] = value
130
+ when 'connecttimeout' then
131
+ hash[:timeout] = value
132
+ when 'forwardagent' then
133
+ hash[:forward_agent] = value
134
+ when 'identitiesonly' then
135
+ hash[:keys_only] = value
136
+ when 'globalknownhostsfile'
137
+ hash[:global_known_hosts_file] = value
138
+ when 'hostbasedauthentication' then
139
+ if value
140
+ hash[:auth_methods] ||= []
141
+ hash[:auth_methods] << "hostbased"
142
+ end
143
+ when 'hostkeyalgorithms' then
144
+ hash[:host_key] = value.split(/,/)
145
+ when 'hostkeyalias' then
146
+ hash[:host_key_alias] = value
147
+ when 'hostname' then
148
+ hash[:host_name] = value
149
+ when 'identityfile' then
150
+ hash[:keys] = value
151
+ when 'macs' then
152
+ hash[:hmac] = value.split(/,/)
153
+ when 'passwordauthentication'
154
+ if value
155
+ hash[:auth_methods] ||= []
156
+ hash[:auth_methods] << "password"
157
+ end
158
+ when 'port'
159
+ hash[:port] = value
160
+ when 'preferredauthentications'
161
+ hash[:auth_methods] = value.split(/,/)
162
+ when 'proxycommand'
163
+ if value and !(value =~ /^none$/)
164
+ require 'net/ssh/proxy/command'
165
+ hash[:proxy] = Net::SSH::Proxy::Command.new(value)
166
+ end
167
+ when 'pubkeyauthentication'
168
+ if value
169
+ hash[:auth_methods] ||= []
170
+ hash[:auth_methods] << "publickey"
171
+ end
172
+ when 'rekeylimit'
173
+ hash[:rekey_limit] = interpret_size(value)
174
+ when 'user'
175
+ hash[:user] = value
176
+ when 'userknownhostsfile'
177
+ hash[:user_known_hosts_file] = value
178
+ end
179
+ hash
180
+ end
181
+ end
182
+
183
+ private
184
+
185
+ # Converts an ssh_config pattern into a regex for matching against
186
+ # host names.
187
+ def pattern2regex(pattern)
188
+ pattern = "^" + pattern.to_s.gsub(/\./, "\\.").
189
+ gsub(/\?/, '.').
190
+ gsub(/([+\/])/, '\\\\\\0').
191
+ gsub(/\*/, '.*') + "$"
192
+ Regexp.new(pattern, true)
193
+ end
194
+
195
+ # Converts the given size into an integer number of bytes.
196
+ def interpret_size(size)
197
+ case size
198
+ when /k$/i then size.to_i * 1024
199
+ when /m$/i then size.to_i * 1024 * 1024
200
+ when /g$/i then size.to_i * 1024 * 1024 * 1024
201
+ else size.to_i
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ end; end
@@ -0,0 +1,630 @@
1
+ require 'net/ssh/loggable'
2
+ require 'net/ssh/connection/constants'
3
+ require 'net/ssh/connection/term'
4
+
5
+ module Net; module SSH; module Connection
6
+
7
+ # The channel abstraction. Multiple "channels" can be multiplexed onto a
8
+ # single SSH channel, each operating independently and seemingly in parallel.
9
+ # This class represents a single such channel. Most operations performed
10
+ # with the Net::SSH library will involve using one or more channels.
11
+ #
12
+ # Channels are intended to be used asynchronously. You request that one be
13
+ # opened (via Connection::Session#open_channel), and when it is opened, your
14
+ # callback is invoked. Then, you set various other callbacks on the newly
15
+ # opened channel, which are called in response to the corresponding events.
16
+ # Programming with Net::SSH works best if you think of your programs as
17
+ # state machines. Complex programs are best implemented as objects that
18
+ # wrap a channel. See Net::SCP and Net::SFTP for examples of how complex
19
+ # state machines can be built on top of the SSH protocol.
20
+ #
21
+ # ssh.open_channel do |channel|
22
+ # channel.exec("/invoke/some/command") do |ch, success|
23
+ # abort "could not execute command" unless success
24
+ #
25
+ # channel.on_data do |ch, data|
26
+ # puts "got stdout: #{data}"
27
+ # channel.send_data "something for stdin\n"
28
+ # end
29
+ #
30
+ # channel.on_extended_data do |ch, type, data|
31
+ # puts "got stderr: #{data}"
32
+ # end
33
+ #
34
+ # channel.on_close do |ch|
35
+ # puts "channel is closing!"
36
+ # end
37
+ # end
38
+ # end
39
+ #
40
+ # ssh.loop
41
+ #
42
+ # Channels also have a basic hash-like interface, that allows programs to
43
+ # store arbitrary state information on a channel object. This helps simplify
44
+ # the writing of state machines, especially when you may be juggling
45
+ # multiple open channels at the same time.
46
+ #
47
+ # Note that data sent across SSH channels are governed by maximum packet
48
+ # sizes and maximum window sizes. These details are managed internally
49
+ # by Net::SSH::Connection::Channel, so you may remain blissfully ignorant
50
+ # if you so desire, but you can always inspect the current maximums, as
51
+ # well as the remaining window size, using the reader attributes for those
52
+ # values.
53
+ class Channel
54
+ include Constants, Loggable
55
+
56
+ # The local id for this channel, assigned by the Net::SSH::Connection::Session instance.
57
+ attr_reader :local_id
58
+
59
+ # The remote id for this channel, assigned by the remote host.
60
+ attr_reader :remote_id
61
+
62
+ # The type of this channel, usually "session".
63
+ attr_reader :type
64
+
65
+ # The underlying Net::SSH::Connection::Session instance that supports this channel.
66
+ attr_reader :connection
67
+
68
+ # The maximum packet size that the local host can receive.
69
+ attr_reader :local_maximum_packet_size
70
+
71
+ # The maximum amount of data that the local end of this channel can
72
+ # receive. This is a total, not per-packet.
73
+ attr_reader :local_maximum_window_size
74
+
75
+ # The maximum packet size that the remote host can receive.
76
+ attr_reader :remote_maximum_packet_size
77
+
78
+ # The maximum amount of data that the remote end of this channel can
79
+ # receive. This is a total, not per-packet.
80
+ attr_reader :remote_maximum_window_size
81
+
82
+ # This is the remaining window size on the local end of this channel. When
83
+ # this reaches zero, no more data can be received.
84
+ attr_reader :local_window_size
85
+
86
+ # This is the remaining window size on the remote end of this channel. When
87
+ # this reaches zero, no more data can be sent.
88
+ attr_reader :remote_window_size
89
+
90
+ # A hash of properties for this channel. These can be used to store state
91
+ # information about this channel. See also #[] and #[]=.
92
+ attr_reader :properties
93
+
94
+ # The output buffer for this channel. Data written to the channel is
95
+ # enqueued here, to be written as CHANNEL_DATA packets during each pass of
96
+ # the event loop. See Connection::Session#process and #enqueue_pending_output.
97
+ attr_reader :output #:nodoc:
98
+
99
+ # The list of pending requests. Each time a request is sent which requires
100
+ # a reply, the corresponding callback is pushed onto this queue. As responses
101
+ # arrive, they are shifted off the front and handled.
102
+ attr_reader :pending_requests #:nodoc:
103
+
104
+ # Instantiates a new channel on the given connection, of the given type,
105
+ # and with the given id. If a block is given, it will be remembered until
106
+ # the channel is confirmed open by the server, and will be invoked at
107
+ # that time (see #do_open_confirmation).
108
+ #
109
+ # This also sets the default maximum packet size and maximum window size.
110
+ def initialize(connection, type, local_id, &on_confirm_open)
111
+ self.logger = connection.logger
112
+
113
+ @connection = connection
114
+ @type = type
115
+ @local_id = local_id
116
+
117
+ @local_maximum_packet_size = 0x10000
118
+ @local_window_size = @local_maximum_window_size = 0x20000
119
+
120
+ @on_confirm_open = on_confirm_open
121
+
122
+ @output = Buffer.new
123
+
124
+ @properties = {}
125
+
126
+ @pending_requests = []
127
+ @on_open_failed = @on_data = @on_extended_data = @on_process = @on_close = @on_eof = nil
128
+ @on_request = {}
129
+ @closing = @eof = @sent_eof = false
130
+ end
131
+
132
+ # A shortcut for accessing properties of the channel (see #properties).
133
+ def [](name)
134
+ @properties[name]
135
+ end
136
+
137
+ # A shortcut for setting properties of the channel (see #properties).
138
+ def []=(name, value)
139
+ @properties[name] = value
140
+ end
141
+
142
+ # Syntactic sugar for executing a command. Sends a channel request asking
143
+ # that the given command be invoked. If the block is given, it will be
144
+ # called when the server responds. The first parameter will be the
145
+ # channel, and the second will be true or false, indicating whether the
146
+ # request succeeded or not. In this case, success means that the command
147
+ # is being executed, not that it has completed, and failure means that the
148
+ # command altogether failed to be executed.
149
+ #
150
+ # channel.exec "ls -l /home" do |ch, success|
151
+ # if success
152
+ # puts "command has begun executing..."
153
+ # # this is a good place to hang callbacks like #on_data...
154
+ # else
155
+ # puts "alas! the command could not be invoked!"
156
+ # end
157
+ # end
158
+ def exec(command, &block)
159
+ send_channel_request("exec", :string, command, &block)
160
+ end
161
+
162
+ # Syntactic sugar for requesting that a subsystem be started. Subsystems
163
+ # are a way for other protocols (like SFTP) to be run, using SSH as
164
+ # the transport. Generally, you'll never need to call this directly unless
165
+ # you are the implementor of something that consumes an SSH subsystem, like
166
+ # SFTP.
167
+ #
168
+ # channel.subsystem("sftp") do |ch, success|
169
+ # if success
170
+ # puts "subsystem successfully started"
171
+ # else
172
+ # puts "subsystem could not be started"
173
+ # end
174
+ # end
175
+ def subsystem(subsystem, &block)
176
+ send_channel_request("subsystem", :string, subsystem, &block)
177
+ end
178
+
179
+ # Syntactic sugar for setting an environment variable in the remote
180
+ # process' environment. Note that for security reasons, the server may
181
+ # refuse to set certain environment variables, or all, at the server's
182
+ # discretion. If you are connecting to an OpenSSH server, you will
183
+ # need to update the AcceptEnv setting in the sshd_config to include the
184
+ # environment variables you want to send.
185
+ #
186
+ # channel.env "PATH", "/usr/local/bin"
187
+ def env(variable_name, variable_value, &block)
188
+ send_channel_request("env", :string, variable_name, :string, variable_value, &block)
189
+ end
190
+
191
+ # A hash of the valid PTY options (see #request_pty).
192
+ VALID_PTY_OPTIONS = { :term => "xterm",
193
+ :chars_wide => 80,
194
+ :chars_high => 24,
195
+ :pixels_wide => 640,
196
+ :pixels_high => 480,
197
+ :modes => {} }
198
+
199
+ # Requests that a pseudo-tty (or "pty") be made available for this channel.
200
+ # This is useful when you want to invoke and interact with some kind of
201
+ # screen-based program (e.g., vim, or some menuing system).
202
+ #
203
+ # Note, that without a pty some programs (e.g. sudo, or subversion) on
204
+ # some systems, will not be able to run interactively, and will error
205
+ # instead of prompt if they ever need some user interaction.
206
+ #
207
+ # Note, too, that when a pty is requested, user's shell configuration
208
+ # scripts (.bashrc and such) are not run by default, whereas they are
209
+ # run when a pty is not present.
210
+ #
211
+ # channel.request_pty do |ch, success|
212
+ # if success
213
+ # puts "pty successfully obtained"
214
+ # else
215
+ # puts "could not obtain pty"
216
+ # end
217
+ # end
218
+ def request_pty(opts={}, &block)
219
+ extra = opts.keys - VALID_PTY_OPTIONS.keys
220
+ raise ArgumentError, "invalid option(s) to request_pty: #{extra.inspect}" if extra.any?
221
+
222
+ opts = VALID_PTY_OPTIONS.merge(opts)
223
+
224
+ modes = opts[:modes].inject(Buffer.new) do |memo, (mode, data)|
225
+ memo.write_byte(mode).write_long(data)
226
+ end
227
+ # mark the end of the mode opcode list with a 0 byte
228
+ modes.write_byte(0)
229
+
230
+ send_channel_request("pty-req", :string, opts[:term],
231
+ :long, opts[:chars_wide], :long, opts[:chars_high],
232
+ :long, opts[:pixels_wide], :long, opts[:pixels_high],
233
+ :string, modes.to_s, &block)
234
+ end
235
+
236
+ # Sends data to the channel's remote endpoint. This usually has the
237
+ # effect of sending the given string to the remote process' stdin stream.
238
+ # Note that it does not immediately send the data across the channel,
239
+ # but instead merely appends the given data to the channel's output buffer,
240
+ # preparatory to being packaged up and sent out the next time the connection
241
+ # is accepting data. (A connection might not be accepting data if, for
242
+ # instance, it has filled its data window and has not yet been resized by
243
+ # the remote end-point.)
244
+ #
245
+ # This will raise an exception if the channel has previously declared
246
+ # that no more data will be sent (see #eof!).
247
+ #
248
+ # channel.send_data("the password\n")
249
+ def send_data(data)
250
+ raise EOFError, "cannot send data if channel has declared eof" if eof?
251
+ output.append(data.to_s)
252
+ end
253
+
254
+ # Returns true if the channel exists in the channel list of the session,
255
+ # and false otherwise. This can be used to determine whether a channel has
256
+ # been closed or not.
257
+ #
258
+ # ssh.loop { channel.active? }
259
+ def active?
260
+ connection.channels.key?(local_id)
261
+ end
262
+
263
+ # Runs the SSH event loop until the channel is no longer active. This is
264
+ # handy for blocking while you wait for some channel to finish.
265
+ #
266
+ # channel.exec("grep ...") { ... }
267
+ # channel.wait
268
+ def wait
269
+ connection.loop { active? }
270
+ end
271
+
272
+ # Returns true if the channel is currently closing, but not actually
273
+ # closed. A channel is closing when, for instance, #close has been
274
+ # invoked, but the server has not yet responded with a CHANNEL_CLOSE
275
+ # packet of its own.
276
+ def closing?
277
+ @closing
278
+ end
279
+
280
+ # Requests that the channel be closed. If the channel is already closing,
281
+ # this does nothing, nor does it do anything if the channel has not yet
282
+ # been confirmed open (see #do_open_confirmation). Otherwise, it sends a
283
+ # CHANNEL_CLOSE message and marks the channel as closing.
284
+ def close
285
+ return if @closing
286
+ if remote_id
287
+ @closing = true
288
+ connection.send_message(Buffer.from(:byte, CHANNEL_CLOSE, :long, remote_id))
289
+ end
290
+ end
291
+
292
+ # Returns true if the local end of the channel has declared that no more
293
+ # data is forthcoming (see #eof!). Trying to send data via #send_data when
294
+ # this is true will result in an exception being raised.
295
+ def eof?
296
+ @eof
297
+ end
298
+
299
+ # Tells the remote end of the channel that no more data is forthcoming
300
+ # from this end of the channel. The remote end may still send data.
301
+ # The CHANNEL_EOF packet will be sent once the output buffer is empty.
302
+ def eof!
303
+ return if eof?
304
+ @eof = true
305
+ end
306
+
307
+ # If an #on_process handler has been set up, this will cause it to be
308
+ # invoked (passing the channel itself as an argument). It also causes all
309
+ # pending output to be enqueued as CHANNEL_DATA packets (see #enqueue_pending_output).
310
+ def process
311
+ @on_process.call(self) if @on_process
312
+ enqueue_pending_output
313
+
314
+ if @eof and not @sent_eof and output.empty? and remote_id
315
+ connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
316
+ @sent_eof = true
317
+ end
318
+ end
319
+
320
+ # Registers a callback to be invoked when data packets are received by the
321
+ # channel. The callback is called with the channel as the first argument,
322
+ # and the data as the second.
323
+ #
324
+ # channel.on_data do |ch, data|
325
+ # puts "got data: #{data.inspect}"
326
+ # end
327
+ #
328
+ # Data received this way is typically the data written by the remote
329
+ # process to its +stdout+ stream.
330
+ def on_data(&block)
331
+ old, @on_data = @on_data, block
332
+ old
333
+ end
334
+
335
+ # Registers a callback to be invoked when extended data packets are received
336
+ # by the channel. The callback is called with the channel as the first
337
+ # argument, the data type (as an integer) as the second, and the data as
338
+ # the third. Extended data is almost exclusively used to send +stderr+ data
339
+ # (+type+ == 1). Other extended data types are not defined by the SSH
340
+ # protocol.
341
+ #
342
+ # channel.on_extended_data do |ch, type, data|
343
+ # puts "got stderr: #{data.inspect}"
344
+ # end
345
+ def on_extended_data(&block)
346
+ old, @on_extended_data = @on_extended_data, block
347
+ old
348
+ end
349
+
350
+ # Registers a callback to be invoked for each pass of the event loop for
351
+ # this channel. There are no guarantees on timeliness in the event loop,
352
+ # but it will be called roughly once for each packet received by the
353
+ # connection (not the channel). This callback is invoked with the channel
354
+ # as the sole argument.
355
+ #
356
+ # Here's an example that accumulates the channel data into a variable on
357
+ # the channel itself, and displays individual lines in the input one
358
+ # at a time when the channel is processed:
359
+ #
360
+ # channel[:data] = ""
361
+ #
362
+ # channel.on_data do |ch, data|
363
+ # channel[:data] << data
364
+ # end
365
+ #
366
+ # channel.on_process do |ch|
367
+ # if channel[:data] =~ /^.*?\n/
368
+ # puts $&
369
+ # channel[:data] = $'
370
+ # end
371
+ # end
372
+ def on_process(&block)
373
+ old, @on_process = @on_process, block
374
+ old
375
+ end
376
+
377
+ # Registers a callback to be invoked when the server acknowledges that a
378
+ # channel is closed. This is invoked with the channel as the sole argument.
379
+ #
380
+ # channel.on_close do |ch|
381
+ # puts "remote end is closing!"
382
+ # end
383
+ def on_close(&block)
384
+ old, @on_close = @on_close, block
385
+ old
386
+ end
387
+
388
+ # Registers a callback to be invoked when the server indicates that no more
389
+ # data will be sent to the channel (although the channel can still send
390
+ # data to the server). The channel is the sole argument to the callback.
391
+ #
392
+ # channel.on_eof do |ch|
393
+ # puts "remote end is done sending data"
394
+ # end
395
+ def on_eof(&block)
396
+ old, @on_eof = @on_eof, block
397
+ old
398
+ end
399
+
400
+ # Registers a callback to be invoked when the server was unable to open
401
+ # the requested channel. The channel itself will be passed to the block,
402
+ # along with the integer "reason code" for the failure, and a textual
403
+ # description of the failure from the server.
404
+ #
405
+ # channel = session.open_channel do |ch|
406
+ # # ..
407
+ # end
408
+ #
409
+ # channel.on_open_failed { |ch, code, desc| ... }
410
+ def on_open_failed(&block)
411
+ old, @on_open_failed = @on_open_failed, block
412
+ old
413
+ end
414
+
415
+ # Registers a callback to be invoked when a channel request of the given
416
+ # type is received. The callback will receive the channel as the first
417
+ # argument, and the associated (unparsed) data as the second. The data
418
+ # will be a Net::SSH::Buffer that you will need to parse, yourself,
419
+ # according to the kind of request you are watching.
420
+ #
421
+ # By default, if the request wants a reply, Net::SSH will send a
422
+ # CHANNEL_SUCCESS response for any request that was handled by a registered
423
+ # callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
424
+ # registered callback to result in a CHANNEL_FAILURE response, just raise
425
+ # Net::SSH::ChannelRequestFailed.
426
+ #
427
+ # Some common channel requests that your programs might want to listen
428
+ # for are:
429
+ #
430
+ # * "exit-status" : the exit status of the remote process will be reported
431
+ # as a long integer in the data buffer, which you can grab via
432
+ # data.read_long.
433
+ # * "exit-signal" : if the remote process died as a result of a signal
434
+ # being sent to it, the signal will be reported as a string in the
435
+ # data, via data.read_string. (Not all SSH servers support this channel
436
+ # request type.)
437
+ #
438
+ # channel.on_request "exit-status" do |ch, data|
439
+ # puts "process terminated with exit status: #{data.read_long}"
440
+ # end
441
+ def on_request(type, &block)
442
+ old, @on_request[type] = @on_request[type], block
443
+ old
444
+ end
445
+
446
+ # Sends a new channel request with the given name. The extra +data+
447
+ # parameter must either be empty, or consist of an even number of
448
+ # arguments. See Net::SSH::Buffer.from for a description of their format.
449
+ # If a block is given, it is registered as a callback for a pending
450
+ # request, and the packet will be flagged so that the server knows a
451
+ # reply is required. If no block is given, the server will send no
452
+ # response to this request. Responses, where required, will cause the
453
+ # callback to be invoked with the channel as the first argument, and
454
+ # either true or false as the second, depending on whether the request
455
+ # succeeded or not. The meaning of "success" and "failure" in this context
456
+ # is dependent on the specific request that was sent.
457
+ #
458
+ # channel.send_channel_request "shell" do |ch, success|
459
+ # if success
460
+ # puts "user shell started successfully"
461
+ # else
462
+ # puts "could not start user shell"
463
+ # end
464
+ # end
465
+ #
466
+ # Most channel requests you'll want to send are already wrapped in more
467
+ # convenient helper methods (see #exec and #subsystem).
468
+ def send_channel_request(request_name, *data, &callback)
469
+ info { "sending channel request #{request_name.inspect}" }
470
+ msg = Buffer.from(:byte, CHANNEL_REQUEST,
471
+ :long, remote_id, :string, request_name,
472
+ :bool, !callback.nil?, *data)
473
+ connection.send_message(msg)
474
+ pending_requests << callback if callback
475
+ end
476
+
477
+ public # these methods are public, but for Net::SSH internal use only
478
+
479
+ # Enqueues pending output at the connection as CHANNEL_DATA packets. This
480
+ # does nothing if the channel has not yet been confirmed open (see
481
+ # #do_open_confirmation). This is called automatically by #process, which
482
+ # is called from the event loop (Connection::Session#process). You will
483
+ # generally not need to invoke it directly.
484
+ def enqueue_pending_output #:nodoc:
485
+ return unless remote_id
486
+
487
+ while output.length > 0
488
+ length = output.length
489
+ length = remote_window_size if length > remote_window_size
490
+ length = remote_maximum_packet_size if length > remote_maximum_packet_size
491
+
492
+ if length > 0
493
+ connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
494
+ output.consume!
495
+ @remote_window_size -= length
496
+ else
497
+ break
498
+ end
499
+ end
500
+ end
501
+
502
+ # Invoked when the server confirms that a channel has been opened.
503
+ # The remote_id is the id of the channel as assigned by the remote host,
504
+ # and max_window and max_packet are the maximum window and maximum
505
+ # packet sizes, respectively. If an open-confirmation callback was
506
+ # given when the channel was created, it is invoked at this time with
507
+ # the channel itself as the sole argument.
508
+ def do_open_confirmation(remote_id, max_window, max_packet) #:nodoc:
509
+ @remote_id = remote_id
510
+ @remote_window_size = @remote_maximum_window_size = max_window
511
+ @remote_maximum_packet_size = max_packet
512
+ connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
513
+ @on_confirm_open.call(self) if @on_confirm_open
514
+ end
515
+
516
+ # Invoked when the server failed to open the channel. If an #on_open_failed
517
+ # callback was specified, it will be invoked with the channel, reason code,
518
+ # and description as arguments. Otherwise, a ChannelOpenFailed exception
519
+ # will be raised.
520
+ def do_open_failed(reason_code, description)
521
+ if @on_open_failed
522
+ @on_open_failed.call(self, reason_code, description)
523
+ else
524
+ raise ChannelOpenFailed.new(reason_code, description)
525
+ end
526
+ end
527
+
528
+ # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
529
+ # causes the remote window size to be adjusted upwards by the given
530
+ # number of bytes. This has the effect of allowing more data to be sent
531
+ # from the local end to the remote end of the channel.
532
+ def do_window_adjust(bytes) #:nodoc:
533
+ @remote_maximum_window_size += bytes
534
+ @remote_window_size += bytes
535
+ end
536
+
537
+ # Invoked when the server sends a channel request. If any #on_request
538
+ # callback has been registered for the specific type of this request,
539
+ # it is invoked. If +want_reply+ is true, a packet will be sent of
540
+ # either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
541
+ # to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
542
+ # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
543
+ # callback should accept the channel as the first argument, and the
544
+ # request-specific data as the second.
545
+ def do_request(request, want_reply, data) #:nodoc:
546
+ result = true
547
+
548
+ begin
549
+ callback = @on_request[request] or raise ChannelRequestFailed
550
+ callback.call(self, data)
551
+ rescue ChannelRequestFailed
552
+ result = false
553
+ end
554
+
555
+ if want_reply
556
+ msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
557
+ connection.send_message(msg)
558
+ end
559
+ end
560
+
561
+ # Invokes the #on_data callback when the server sends data to the
562
+ # channel. This will reduce the available window size on the local end,
563
+ # but does not actually throttle requests that come in illegally when
564
+ # the window size is too small. The callback is invoked with the channel
565
+ # as the first argument, and the data as the second.
566
+ def do_data(data) #:nodoc:
567
+ update_local_window_size(data.length)
568
+ @on_data.call(self, data) if @on_data
569
+ end
570
+
571
+ # Invokes the #on_extended_data callback when the server sends
572
+ # extended data to the channel. This will reduce the available window
573
+ # size on the local end. The callback is invoked with the channel,
574
+ # type, and data.
575
+ def do_extended_data(type, data)
576
+ update_local_window_size(data.length)
577
+ @on_extended_data.call(self, type, data) if @on_extended_data
578
+ end
579
+
580
+ # Invokes the #on_eof callback when the server indicates that no
581
+ # further data is forthcoming. The callback is invoked with the channel
582
+ # as the argument.
583
+ def do_eof
584
+ @on_eof.call(self) if @on_eof
585
+ end
586
+
587
+ # Invokes the #on_close callback when the server closes a channel.
588
+ # The channel is the only argument.
589
+ def do_close
590
+ @on_close.call(self) if @on_close
591
+ end
592
+
593
+ # Invokes the next pending request callback with +false+ as the second
594
+ # argument.
595
+ def do_failure
596
+ if callback = pending_requests.shift
597
+ callback.call(self, false)
598
+ else
599
+ error { "channel failure recieved with no pending request to handle it (bug?)" }
600
+ end
601
+ end
602
+
603
+ # Invokes the next pending request callback with +true+ as the second
604
+ # argument.
605
+ def do_success
606
+ if callback = pending_requests.shift
607
+ callback.call(self, true)
608
+ else
609
+ error { "channel success recieved with no pending request to handle it (bug?)" }
610
+ end
611
+ end
612
+
613
+ private
614
+
615
+ # Updates the local window size by the given amount. If the window
616
+ # size drops to less than half of the local maximum (an arbitrary
617
+ # threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
618
+ # server telling it that the window size has grown.
619
+ def update_local_window_size(size)
620
+ @local_window_size -= size
621
+ if local_window_size < local_maximum_window_size/2
622
+ connection.send_message(Buffer.from(:byte, CHANNEL_WINDOW_ADJUST,
623
+ :long, remote_id, :long, 0x20000))
624
+ @local_window_size += 0x20000
625
+ @local_maximum_window_size += 0x20000
626
+ end
627
+ end
628
+ end
629
+
630
+ end; end; end