jayniz-net-ssh 2.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/CHANGELOG.rdoc +161 -0
  2. data/Manifest +107 -0
  3. data/README.rdoc +140 -0
  4. data/Rakefile +79 -0
  5. data/Rudyfile +110 -0
  6. data/THANKS.rdoc +16 -0
  7. data/lib/net/ssh/authentication/agent.rb +176 -0
  8. data/lib/net/ssh/authentication/constants.rb +18 -0
  9. data/lib/net/ssh/authentication/key_manager.rb +193 -0
  10. data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  11. data/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
  12. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
  13. data/lib/net/ssh/authentication/methods/password.rb +39 -0
  14. data/lib/net/ssh/authentication/methods/publickey.rb +92 -0
  15. data/lib/net/ssh/authentication/pageant.rb +183 -0
  16. data/lib/net/ssh/authentication/session.rb +134 -0
  17. data/lib/net/ssh/buffer.rb +340 -0
  18. data/lib/net/ssh/buffered_io.rb +150 -0
  19. data/lib/net/ssh/config.rb +185 -0
  20. data/lib/net/ssh/connection/channel.rb +625 -0
  21. data/lib/net/ssh/connection/constants.rb +33 -0
  22. data/lib/net/ssh/connection/session.rb +597 -0
  23. data/lib/net/ssh/connection/term.rb +178 -0
  24. data/lib/net/ssh/errors.rb +85 -0
  25. data/lib/net/ssh/key_factory.rb +102 -0
  26. data/lib/net/ssh/known_hosts.rb +129 -0
  27. data/lib/net/ssh/loggable.rb +61 -0
  28. data/lib/net/ssh/packet.rb +102 -0
  29. data/lib/net/ssh/prompt.rb +93 -0
  30. data/lib/net/ssh/proxy/errors.rb +14 -0
  31. data/lib/net/ssh/proxy/http.rb +94 -0
  32. data/lib/net/ssh/proxy/socks4.rb +70 -0
  33. data/lib/net/ssh/proxy/socks5.rb +142 -0
  34. data/lib/net/ssh/ruby_compat.rb +43 -0
  35. data/lib/net/ssh/service/forward.rb +267 -0
  36. data/lib/net/ssh/test/channel.rb +129 -0
  37. data/lib/net/ssh/test/extensions.rb +152 -0
  38. data/lib/net/ssh/test/kex.rb +44 -0
  39. data/lib/net/ssh/test/local_packet.rb +51 -0
  40. data/lib/net/ssh/test/packet.rb +81 -0
  41. data/lib/net/ssh/test/remote_packet.rb +38 -0
  42. data/lib/net/ssh/test/script.rb +157 -0
  43. data/lib/net/ssh/test/socket.rb +59 -0
  44. data/lib/net/ssh/test.rb +89 -0
  45. data/lib/net/ssh/transport/algorithms.rb +384 -0
  46. data/lib/net/ssh/transport/cipher_factory.rb +97 -0
  47. data/lib/net/ssh/transport/constants.rb +30 -0
  48. data/lib/net/ssh/transport/hmac/abstract.rb +79 -0
  49. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  50. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  51. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  52. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  53. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  54. data/lib/net/ssh/transport/hmac.rb +31 -0
  55. data/lib/net/ssh/transport/identity_cipher.rb +55 -0
  56. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
  58. data/lib/net/ssh/transport/kex.rb +13 -0
  59. data/lib/net/ssh/transport/openssl.rb +128 -0
  60. data/lib/net/ssh/transport/packet_stream.rb +232 -0
  61. data/lib/net/ssh/transport/server_version.rb +70 -0
  62. data/lib/net/ssh/transport/session.rb +276 -0
  63. data/lib/net/ssh/transport/state.rb +206 -0
  64. data/lib/net/ssh/verifiers/lenient.rb +30 -0
  65. data/lib/net/ssh/verifiers/null.rb +12 -0
  66. data/lib/net/ssh/verifiers/strict.rb +53 -0
  67. data/lib/net/ssh/version.rb +62 -0
  68. data/lib/net/ssh.rb +215 -0
  69. data/net-ssh.gemspec +131 -0
  70. data/setup.rb +1585 -0
  71. data/support/arcfour_check.rb +20 -0
  72. data/test/authentication/methods/common.rb +28 -0
  73. data/test/authentication/methods/test_abstract.rb +51 -0
  74. data/test/authentication/methods/test_hostbased.rb +114 -0
  75. data/test/authentication/methods/test_keyboard_interactive.rb +98 -0
  76. data/test/authentication/methods/test_password.rb +50 -0
  77. data/test/authentication/methods/test_publickey.rb +127 -0
  78. data/test/authentication/test_agent.rb +205 -0
  79. data/test/authentication/test_key_manager.rb +105 -0
  80. data/test/authentication/test_session.rb +93 -0
  81. data/test/common.rb +107 -0
  82. data/test/configs/eqsign +3 -0
  83. data/test/configs/exact_match +8 -0
  84. data/test/configs/multihost +4 -0
  85. data/test/configs/wild_cards +14 -0
  86. data/test/connection/test_channel.rb +452 -0
  87. data/test/connection/test_session.rb +488 -0
  88. data/test/test_all.rb +8 -0
  89. data/test/test_buffer.rb +336 -0
  90. data/test/test_buffered_io.rb +63 -0
  91. data/test/test_config.rb +99 -0
  92. data/test/test_key_factory.rb +67 -0
  93. data/test/transport/hmac/test_md5.rb +39 -0
  94. data/test/transport/hmac/test_md5_96.rb +25 -0
  95. data/test/transport/hmac/test_none.rb +34 -0
  96. data/test/transport/hmac/test_sha1.rb +34 -0
  97. data/test/transport/hmac/test_sha1_96.rb +25 -0
  98. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  99. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  100. data/test/transport/test_algorithms.rb +302 -0
  101. data/test/transport/test_cipher_factory.rb +213 -0
  102. data/test/transport/test_hmac.rb +34 -0
  103. data/test/transport/test_identity_cipher.rb +40 -0
  104. data/test/transport/test_packet_stream.rb +441 -0
  105. data/test/transport/test_server_version.rb +68 -0
  106. data/test/transport/test_session.rb +315 -0
  107. data/test/transport/test_state.rb +173 -0
  108. metadata +168 -0
@@ -0,0 +1,150 @@
1
+ require 'net/ssh/buffer'
2
+ require 'net/ssh/loggable'
3
+ require 'net/ssh/ruby_compat'
4
+
5
+ module Net; module SSH
6
+
7
+ # This module is used to extend sockets and other IO objects, to allow
8
+ # them to be buffered for both read and write. This abstraction makes it
9
+ # quite easy to write a select-based event loop
10
+ # (see Net::SSH::Connection::Session#listen_to).
11
+ #
12
+ # The general idea is that instead of calling #read directly on an IO that
13
+ # has been extended with this module, you call #fill (to add pending input
14
+ # to the internal read buffer), and then #read_available (to read from that
15
+ # buffer). Likewise, you don't call #write directly, you call #enqueue to
16
+ # add data to the write buffer, and then #send_pending or #wait_for_pending_sends
17
+ # to actually send the data across the wire.
18
+ #
19
+ # In this way you can easily use the object as an argument to IO.select,
20
+ # calling #fill when it is available for read, or #send_pending when it is
21
+ # available for write, and then call #enqueue and #read_available during
22
+ # the idle times.
23
+ #
24
+ # socket = TCPSocket.new(address, port)
25
+ # socket.extend(Net::SSH::BufferedIo)
26
+ #
27
+ # ssh.listen_to(socket)
28
+ #
29
+ # ssh.loop do
30
+ # if socket.available > 0
31
+ # puts socket.read_available
32
+ # socket.enqueue("response\n")
33
+ # end
34
+ # end
35
+ #
36
+ # Note that this module must be used to extend an instance, and should not
37
+ # be included in a class. If you do want to use it via an include, then you
38
+ # must make sure to invoke the private #initialize_buffered_io method in
39
+ # your class' #initialize method:
40
+ #
41
+ # class Foo < IO
42
+ # include Net::SSH::BufferedIo
43
+ #
44
+ # def initialize
45
+ # initialize_buffered_io
46
+ # # ...
47
+ # end
48
+ # end
49
+ module BufferedIo
50
+ include Loggable
51
+
52
+ # Called when the #extend is called on an object, with this module as the
53
+ # argument. It ensures that the modules instance variables are all properly
54
+ # initialized.
55
+ def self.extended(object) #:nodoc:
56
+ # need to use __send__ because #send is overridden in Socket
57
+ object.__send__(:initialize_buffered_io)
58
+ end
59
+
60
+ # Tries to read up to +n+ bytes of data from the remote end, and appends
61
+ # the data to the input buffer. It returns the number of bytes read, or 0
62
+ # if no data was available to be read.
63
+ def fill(n=8192)
64
+ input.consume!
65
+ data = recv(n)
66
+ debug { "read #{data.length} bytes" }
67
+ input.append(data)
68
+ return data.length
69
+ end
70
+
71
+ # Read up to +length+ bytes from the input buffer. If +length+ is nil,
72
+ # all available data is read from the buffer. (See #available.)
73
+ def read_available(length=nil)
74
+ input.read(length || available)
75
+ end
76
+
77
+ # Returns the number of bytes available to be read from the input buffer.
78
+ # (See #read_available.)
79
+ def available
80
+ input.available
81
+ end
82
+
83
+ # Enqueues data in the output buffer, to be written when #send_pending
84
+ # is called. Note that the data is _not_ sent immediately by this method!
85
+ def enqueue(data)
86
+ output.append(data)
87
+ end
88
+
89
+ # Returns +true+ if there is data waiting in the output buffer, and
90
+ # +false+ otherwise.
91
+ def pending_write?
92
+ output.length > 0
93
+ end
94
+
95
+ # Sends as much of the pending output as possible. Returns +true+ if any
96
+ # data was sent, and +false+ otherwise.
97
+ def send_pending
98
+ if output.length > 0
99
+ sent = send(output.to_s, 0)
100
+ debug { "sent #{sent} bytes" }
101
+ output.consume!(sent)
102
+ return sent > 0
103
+ else
104
+ return false
105
+ end
106
+ end
107
+
108
+ # Calls #send_pending repeatedly, if necessary, blocking until the output
109
+ # buffer is empty.
110
+ def wait_for_pending_sends
111
+ send_pending
112
+ while output.length > 0
113
+ result = Net::SSH::Compat.io_select(nil, [self]) or next
114
+ next unless result[1].any?
115
+ send_pending
116
+ end
117
+ end
118
+
119
+ public # these methods are primarily for use in tests
120
+
121
+ def write_buffer #:nodoc:
122
+ output.to_s
123
+ end
124
+
125
+ def read_buffer #:nodoc:
126
+ input.to_s
127
+ end
128
+
129
+ private
130
+
131
+ #--
132
+ # Can't use attr_reader here (after +private+) without incurring the
133
+ # wrath of "ruby -w". We hates it.
134
+ #++
135
+
136
+ def input; @input; end
137
+ def output; @output; end
138
+
139
+ # Initializes the intput and output buffers for this object. This method
140
+ # is called automatically when the module is mixed into an object via
141
+ # Object#extend (see Net::SSH::BufferedIo.extended), but must be called
142
+ # explicitly in the +initialize+ method of any class that uses
143
+ # Module#include to add this module.
144
+ def initialize_buffered_io
145
+ @input = Net::SSH::Buffer.new
146
+ @output = Net::SSH::Buffer.new
147
+ end
148
+ end
149
+
150
+ end; end
@@ -0,0 +1,185 @@
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
+ # * Macs => maps to the :hmac option
23
+ # * PasswordAuthentication => maps to the :auth_methods option
24
+ # * Port => :port
25
+ # * PreferredAuthentications => maps to the :auth_methods option
26
+ # * RekeyLimit => :rekey_limit
27
+ # * User => :user
28
+ # * UserKnownHostsFile => :user_known_hosts_file
29
+ #
30
+ # Note that you will never need to use this class directly--you can control
31
+ # whether the OpenSSH configuration files are read by passing the :config
32
+ # option to Net::SSH.start. (They are, by default.)
33
+ class Config
34
+ class <<self
35
+ @@default_files = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config)
36
+
37
+ # Returns an array of locations of OpenSSH configuration files
38
+ # to parse by default.
39
+ def default_files
40
+ @@default_files
41
+ end
42
+
43
+ # Loads the configuration data for the given +host+ from all of the
44
+ # given +files+ (defaulting to the list of files returned by
45
+ # #default_files), translates the resulting hash into the options
46
+ # recognized by Net::SSH, and returns them.
47
+ def for(host, files=default_files)
48
+ translate(files.inject({}) { |settings, file| load(file, host, settings) })
49
+ end
50
+
51
+ # Load the OpenSSH configuration settings in the given +file+ for the
52
+ # given +host+. If +settings+ is given, the options are merged into
53
+ # that hash, with existing values taking precedence over newly parsed
54
+ # ones. Returns a hash containing the OpenSSH options. (See
55
+ # #translate for how to convert the OpenSSH options into Net::SSH
56
+ # options.)
57
+ def load(file, host, settings={})
58
+ file = File.expand_path(file)
59
+ return settings unless File.readable?(file)
60
+
61
+ matched_host = nil
62
+ multi_host = []
63
+ IO.foreach(file) do |line|
64
+ next if line =~ /^\s*(?:#.*)?$/
65
+
66
+ if line =~ /^\s*(\S+)\s*=(.*)$/
67
+ key, value = $1, $2
68
+ else
69
+ key, value = line.strip.split(/\s+/, 2)
70
+ end
71
+
72
+ # silently ignore malformed entries
73
+ next if value.nil?
74
+
75
+ key.downcase!
76
+ value = $1 if value =~ /^"(.*)"$/
77
+
78
+ value = case value.strip
79
+ when /^\d+$/ then value.to_i
80
+ when /^no$/i then false
81
+ when /^yes$/i then true
82
+ else value
83
+ end
84
+
85
+ if key == 'host'
86
+ # Support "Host host1,host2,hostN".
87
+ # See http://github.com/net-ssh/net-ssh/issues#issue/6
88
+ multi_host = value.split(/,\s+/)
89
+ matched_host = multi_host.select { |h| host =~ pattern2regex(h) }.first
90
+ elsif !matched_host.nil?
91
+ if key == 'identityfile'
92
+ settings[key] ||= []
93
+ settings[key] << value
94
+ else
95
+ settings[key] = value unless settings.key?(key)
96
+ end
97
+ end
98
+ end
99
+
100
+ return settings
101
+ end
102
+
103
+ # Given a hash of OpenSSH configuration options, converts them into
104
+ # a hash of Net::SSH options. Unrecognized options are ignored. The
105
+ # +settings+ hash must have Strings for keys, all downcased, and
106
+ # the returned hash will have Symbols for keys.
107
+ def translate(settings)
108
+ settings.inject({}) do |hash, (key, value)|
109
+ case key
110
+ when 'ciphers' then
111
+ hash[:encryption] = value.split(/,/)
112
+ when 'compression' then
113
+ hash[:compression] = value
114
+ when 'compressionlevel' then
115
+ hash[:compression_level] = value
116
+ when 'connecttimeout' then
117
+ hash[:timeout] = value
118
+ when 'forwardagent' then
119
+ hash[:forward_agent] = value
120
+ when 'globalknownhostsfile'
121
+ hash[:global_known_hosts_file] = value
122
+ when 'hostbasedauthentication' then
123
+ if value
124
+ hash[:auth_methods] ||= []
125
+ hash[:auth_methods] << "hostbased"
126
+ end
127
+ when 'hostkeyalgorithms' then
128
+ hash[:host_key] = value.split(/,/)
129
+ when 'hostkeyalias' then
130
+ hash[:host_key_alias] = value
131
+ when 'hostname' then
132
+ hash[:host_name] = value
133
+ when 'identityfile' then
134
+ hash[:keys] = value
135
+ when 'macs' then
136
+ hash[:hmac] = value.split(/,/)
137
+ when 'passwordauthentication'
138
+ if value
139
+ hash[:auth_methods] ||= []
140
+ hash[:auth_methods] << "password"
141
+ end
142
+ when 'port'
143
+ hash[:port] = value
144
+ when 'preferredauthentications'
145
+ hash[:auth_methods] = value.split(/,/)
146
+ when 'pubkeyauthentication'
147
+ if value
148
+ hash[:auth_methods] ||= []
149
+ hash[:auth_methods] << "publickey"
150
+ end
151
+ when 'rekeylimit'
152
+ hash[:rekey_limit] = interpret_size(value)
153
+ when 'user'
154
+ hash[:user] = value
155
+ when 'userknownhostsfile'
156
+ hash[:user_known_hosts_file] = value
157
+ end
158
+ hash
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ # Converts an ssh_config pattern into a regex for matching against
165
+ # host names.
166
+ def pattern2regex(pattern)
167
+ pattern = "^" + pattern.to_s.gsub(/\./, "\\.").
168
+ gsub(/\?/, '.').
169
+ gsub(/\*/, '.*') + "$"
170
+ Regexp.new(pattern, true)
171
+ end
172
+
173
+ # Converts the given size into an integer number of bytes.
174
+ def interpret_size(size)
175
+ case size
176
+ when /k$/i then size.to_i * 1024
177
+ when /m$/i then size.to_i * 1024 * 1024
178
+ when /g$/i then size.to_i * 1024 * 1024 * 1024
179
+ else size.to_i
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ end; end
@@ -0,0 +1,625 @@
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 = 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
+ def eof!
302
+ return if eof?
303
+ @eof = true
304
+ connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
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
+ end
314
+
315
+ # Registers a callback to be invoked when data packets are received by the
316
+ # channel. The callback is called with the channel as the first argument,
317
+ # and the data as the second.
318
+ #
319
+ # channel.on_data do |ch, data|
320
+ # puts "got data: #{data.inspect}"
321
+ # end
322
+ #
323
+ # Data received this way is typically the data written by the remote
324
+ # process to its +stdout+ stream.
325
+ def on_data(&block)
326
+ old, @on_data = @on_data, block
327
+ old
328
+ end
329
+
330
+ # Registers a callback to be invoked when extended data packets are received
331
+ # by the channel. The callback is called with the channel as the first
332
+ # argument, the data type (as an integer) as the second, and the data as
333
+ # the third. Extended data is almost exclusively used to send +stderr+ data
334
+ # (+type+ == 1). Other extended data types are not defined by the SSH
335
+ # protocol.
336
+ #
337
+ # channel.on_extended_data do |ch, type, data|
338
+ # puts "got stderr: #{data.inspect}"
339
+ # end
340
+ def on_extended_data(&block)
341
+ old, @on_extended_data = @on_extended_data, block
342
+ old
343
+ end
344
+
345
+ # Registers a callback to be invoked for each pass of the event loop for
346
+ # this channel. There are no guarantees on timeliness in the event loop,
347
+ # but it will be called roughly once for each packet received by the
348
+ # connection (not the channel). This callback is invoked with the channel
349
+ # as the sole argument.
350
+ #
351
+ # Here's an example that accumulates the channel data into a variable on
352
+ # the channel itself, and displays individual lines in the input one
353
+ # at a time when the channel is processed:
354
+ #
355
+ # channel[:data] = ""
356
+ #
357
+ # channel.on_data do |ch, data|
358
+ # channel[:data] << data
359
+ # end
360
+ #
361
+ # channel.on_process do |ch|
362
+ # if channel[:data] =~ /^.*?\n/
363
+ # puts $&
364
+ # channel[:data] = $'
365
+ # end
366
+ # end
367
+ def on_process(&block)
368
+ old, @on_process = @on_process, block
369
+ old
370
+ end
371
+
372
+ # Registers a callback to be invoked when the server acknowledges that a
373
+ # channel is closed. This is invoked with the channel as the sole argument.
374
+ #
375
+ # channel.on_close do |ch|
376
+ # puts "remote end is closing!"
377
+ # end
378
+ def on_close(&block)
379
+ old, @on_close = @on_close, block
380
+ old
381
+ end
382
+
383
+ # Registers a callback to be invoked when the server indicates that no more
384
+ # data will be sent to the channel (although the channel can still send
385
+ # data to the server). The channel is the sole argument to the callback.
386
+ #
387
+ # channel.on_eof do |ch|
388
+ # puts "remote end is done sending data"
389
+ # end
390
+ def on_eof(&block)
391
+ old, @on_eof = @on_eof, block
392
+ old
393
+ end
394
+
395
+ # Registers a callback to be invoked when the server was unable to open
396
+ # the requested channel. The channel itself will be passed to the block,
397
+ # along with the integer "reason code" for the failure, and a textual
398
+ # description of the failure from the server.
399
+ #
400
+ # channel = session.open_channel do |ch|
401
+ # # ..
402
+ # end
403
+ #
404
+ # channel.on_open_failed { |ch, code, desc| ... }
405
+ def on_open_failed(&block)
406
+ old, @on_open_failed = @on_open_failed, block
407
+ old
408
+ end
409
+
410
+ # Registers a callback to be invoked when a channel request of the given
411
+ # type is received. The callback will receive the channel as the first
412
+ # argument, and the associated (unparsed) data as the second. The data
413
+ # will be a Net::SSH::Buffer that you will need to parse, yourself,
414
+ # according to the kind of request you are watching.
415
+ #
416
+ # By default, if the request wants a reply, Net::SSH will send a
417
+ # CHANNEL_SUCCESS response for any request that was handled by a registered
418
+ # callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
419
+ # registered callback to result in a CHANNEL_FAILURE response, just raise
420
+ # Net::SSH::ChannelRequestFailed.
421
+ #
422
+ # Some common channel requests that your programs might want to listen
423
+ # for are:
424
+ #
425
+ # * "exit-status" : the exit status of the remote process will be reported
426
+ # as a long integer in the data buffer, which you can grab via
427
+ # data.read_long.
428
+ # * "exit-signal" : if the remote process died as a result of a signal
429
+ # being sent to it, the signal will be reported as a string in the
430
+ # data, via data.read_string. (Not all SSH servers support this channel
431
+ # request type.)
432
+ #
433
+ # channel.on_request "exit-status" do |ch, data|
434
+ # puts "process terminated with exit status: #{data.read_long}"
435
+ # end
436
+ def on_request(type, &block)
437
+ old, @on_request[type] = @on_request[type], block
438
+ old
439
+ end
440
+
441
+ # Sends a new channel request with the given name. The extra +data+
442
+ # parameter must either be empty, or consist of an even number of
443
+ # arguments. See Net::SSH::Buffer.from for a description of their format.
444
+ # If a block is given, it is registered as a callback for a pending
445
+ # request, and the packet will be flagged so that the server knows a
446
+ # reply is required. If no block is given, the server will send no
447
+ # response to this request. Responses, where required, will cause the
448
+ # callback to be invoked with the channel as the first argument, and
449
+ # either true or false as the second, depending on whether the request
450
+ # succeeded or not. The meaning of "success" and "failure" in this context
451
+ # is dependent on the specific request that was sent.
452
+ #
453
+ # channel.send_channel_request "shell" do |ch, success|
454
+ # if success
455
+ # puts "user shell started successfully"
456
+ # else
457
+ # puts "could not start user shell"
458
+ # end
459
+ # end
460
+ #
461
+ # Most channel requests you'll want to send are already wrapped in more
462
+ # convenient helper methods (see #exec and #subsystem).
463
+ def send_channel_request(request_name, *data, &callback)
464
+ info { "sending channel request #{request_name.inspect}" }
465
+ msg = Buffer.from(:byte, CHANNEL_REQUEST,
466
+ :long, remote_id, :string, request_name,
467
+ :bool, !callback.nil?, *data)
468
+ connection.send_message(msg)
469
+ pending_requests << callback if callback
470
+ end
471
+
472
+ public # these methods are public, but for Net::SSH internal use only
473
+
474
+ # Enqueues pending output at the connection as CHANNEL_DATA packets. This
475
+ # does nothing if the channel has not yet been confirmed open (see
476
+ # #do_open_confirmation). This is called automatically by #process, which
477
+ # is called from the event loop (Connection::Session#process). You will
478
+ # generally not need to invoke it directly.
479
+ def enqueue_pending_output #:nodoc:
480
+ return unless remote_id
481
+
482
+ while output.length > 0
483
+ length = output.length
484
+ length = remote_window_size if length > remote_window_size
485
+ length = remote_maximum_packet_size if length > remote_maximum_packet_size
486
+
487
+ if length > 0
488
+ connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
489
+ output.consume!
490
+ @remote_window_size -= length
491
+ else
492
+ break
493
+ end
494
+ end
495
+ end
496
+
497
+ # Invoked when the server confirms that a channel has been opened.
498
+ # The remote_id is the id of the channel as assigned by the remote host,
499
+ # and max_window and max_packet are the maximum window and maximum
500
+ # packet sizes, respectively. If an open-confirmation callback was
501
+ # given when the channel was created, it is invoked at this time with
502
+ # the channel itself as the sole argument.
503
+ def do_open_confirmation(remote_id, max_window, max_packet) #:nodoc:
504
+ @remote_id = remote_id
505
+ @remote_window_size = @remote_maximum_window_size = max_window
506
+ @remote_maximum_packet_size = max_packet
507
+ connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
508
+ @on_confirm_open.call(self) if @on_confirm_open
509
+ end
510
+
511
+ # Invoked when the server failed to open the channel. If an #on_open_failed
512
+ # callback was specified, it will be invoked with the channel, reason code,
513
+ # and description as arguments. Otherwise, a ChannelOpenFailed exception
514
+ # will be raised.
515
+ def do_open_failed(reason_code, description)
516
+ if @on_open_failed
517
+ @on_open_failed.call(self, reason_code, description)
518
+ else
519
+ raise ChannelOpenFailed.new(reason_code, description)
520
+ end
521
+ end
522
+
523
+ # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
524
+ # causes the remote window size to be adjusted upwards by the given
525
+ # number of bytes. This has the effect of allowing more data to be sent
526
+ # from the local end to the remote end of the channel.
527
+ def do_window_adjust(bytes) #:nodoc:
528
+ @remote_maximum_window_size += bytes
529
+ @remote_window_size += bytes
530
+ end
531
+
532
+ # Invoked when the server sends a channel request. If any #on_request
533
+ # callback has been registered for the specific type of this request,
534
+ # it is invoked. If +want_reply+ is true, a packet will be sent of
535
+ # either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
536
+ # to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
537
+ # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
538
+ # callback should accept the channel as the first argument, and the
539
+ # request-specific data as the second.
540
+ def do_request(request, want_reply, data) #:nodoc:
541
+ result = true
542
+
543
+ begin
544
+ callback = @on_request[request] or raise ChannelRequestFailed
545
+ callback.call(self, data)
546
+ rescue ChannelRequestFailed
547
+ result = false
548
+ end
549
+
550
+ if want_reply
551
+ msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
552
+ connection.send_message(msg)
553
+ end
554
+ end
555
+
556
+ # Invokes the #on_data callback when the server sends data to the
557
+ # channel. This will reduce the available window size on the local end,
558
+ # but does not actually throttle requests that come in illegally when
559
+ # the window size is too small. The callback is invoked with the channel
560
+ # as the first argument, and the data as the second.
561
+ def do_data(data) #:nodoc:
562
+ update_local_window_size(data.length)
563
+ @on_data.call(self, data) if @on_data
564
+ end
565
+
566
+ # Invokes the #on_extended_data callback when the server sends
567
+ # extended data to the channel. This will reduce the available window
568
+ # size on the local end. The callback is invoked with the channel,
569
+ # type, and data.
570
+ def do_extended_data(type, data)
571
+ update_local_window_size(data.length)
572
+ @on_extended_data.call(self, type, data) if @on_extended_data
573
+ end
574
+
575
+ # Invokes the #on_eof callback when the server indicates that no
576
+ # further data is forthcoming. The callback is invoked with the channel
577
+ # as the argument.
578
+ def do_eof
579
+ @on_eof.call(self) if @on_eof
580
+ end
581
+
582
+ # Invokes the #on_close callback when the server closes a channel.
583
+ # The channel is the only argument.
584
+ def do_close
585
+ @on_close.call(self) if @on_close
586
+ end
587
+
588
+ # Invokes the next pending request callback with +false+ as the second
589
+ # argument.
590
+ def do_failure
591
+ if callback = pending_requests.shift
592
+ callback.call(self, false)
593
+ else
594
+ error { "channel failure recieved with no pending request to handle it (bug?)" }
595
+ end
596
+ end
597
+
598
+ # Invokes the next pending request callback with +true+ as the second
599
+ # argument.
600
+ def do_success
601
+ if callback = pending_requests.shift
602
+ callback.call(self, true)
603
+ else
604
+ error { "channel success recieved with no pending request to handle it (bug?)" }
605
+ end
606
+ end
607
+
608
+ private
609
+
610
+ # Updates the local window size by the given amount. If the window
611
+ # size drops to less than half of the local maximum (an arbitrary
612
+ # threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
613
+ # server telling it that the window size has grown.
614
+ def update_local_window_size(size)
615
+ @local_window_size -= size
616
+ if local_window_size < local_maximum_window_size/2
617
+ connection.send_message(Buffer.from(:byte, CHANNEL_WINDOW_ADJUST,
618
+ :long, remote_id, :long, 0x20000))
619
+ @local_window_size += 0x20000
620
+ @local_maximum_window_size += 0x20000
621
+ end
622
+ end
623
+ end
624
+
625
+ end; end; end