sonixlabs-net-ssh 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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