net-ssh-net-ssh 2.0.12

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 (105) hide show
  1. data/CHANGELOG.rdoc +137 -0
  2. data/Manifest +104 -0
  3. data/README.rdoc +110 -0
  4. data/Rakefile +79 -0
  5. data/THANKS.rdoc +16 -0
  6. data/lib/net/ssh.rb +215 -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 +149 -0
  19. data/lib/net/ssh/config.rb +181 -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 +596 -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 +129 -0
  34. data/lib/net/ssh/ruby_compat.rb +7 -0
  35. data/lib/net/ssh/service/forward.rb +267 -0
  36. data/lib/net/ssh/test.rb +89 -0
  37. data/lib/net/ssh/test/channel.rb +129 -0
  38. data/lib/net/ssh/test/extensions.rb +152 -0
  39. data/lib/net/ssh/test/kex.rb +44 -0
  40. data/lib/net/ssh/test/local_packet.rb +51 -0
  41. data/lib/net/ssh/test/packet.rb +81 -0
  42. data/lib/net/ssh/test/remote_packet.rb +38 -0
  43. data/lib/net/ssh/test/script.rb +157 -0
  44. data/lib/net/ssh/test/socket.rb +59 -0
  45. data/lib/net/ssh/transport/algorithms.rb +384 -0
  46. data/lib/net/ssh/transport/cipher_factory.rb +84 -0
  47. data/lib/net/ssh/transport/constants.rb +30 -0
  48. data/lib/net/ssh/transport/hmac.rb +31 -0
  49. data/lib/net/ssh/transport/hmac/abstract.rb +78 -0
  50. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  51. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  52. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  53. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  54. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  55. data/lib/net/ssh/transport/identity_cipher.rb +55 -0
  56. data/lib/net/ssh/transport/kex.rb +13 -0
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  58. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
  59. data/lib/net/ssh/transport/openssl.rb +128 -0
  60. data/lib/net/ssh/transport/packet_stream.rb +230 -0
  61. data/lib/net/ssh/transport/server_version.rb +69 -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/net-ssh.gemspec +128 -0
  69. data/setup.rb +1585 -0
  70. data/test/authentication/methods/common.rb +28 -0
  71. data/test/authentication/methods/test_abstract.rb +51 -0
  72. data/test/authentication/methods/test_hostbased.rb +114 -0
  73. data/test/authentication/methods/test_keyboard_interactive.rb +98 -0
  74. data/test/authentication/methods/test_password.rb +50 -0
  75. data/test/authentication/methods/test_publickey.rb +127 -0
  76. data/test/authentication/test_agent.rb +205 -0
  77. data/test/authentication/test_key_manager.rb +105 -0
  78. data/test/authentication/test_session.rb +93 -0
  79. data/test/common.rb +106 -0
  80. data/test/configs/eqsign +3 -0
  81. data/test/configs/exact_match +8 -0
  82. data/test/configs/wild_cards +14 -0
  83. data/test/connection/test_channel.rb +452 -0
  84. data/test/connection/test_session.rb +488 -0
  85. data/test/test_all.rb +6 -0
  86. data/test/test_buffer.rb +336 -0
  87. data/test/test_buffered_io.rb +63 -0
  88. data/test/test_config.rb +84 -0
  89. data/test/test_key_factory.rb +67 -0
  90. data/test/transport/hmac/test_md5.rb +39 -0
  91. data/test/transport/hmac/test_md5_96.rb +25 -0
  92. data/test/transport/hmac/test_none.rb +34 -0
  93. data/test/transport/hmac/test_sha1.rb +34 -0
  94. data/test/transport/hmac/test_sha1_96.rb +25 -0
  95. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  96. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  97. data/test/transport/test_algorithms.rb +302 -0
  98. data/test/transport/test_cipher_factory.rb +171 -0
  99. data/test/transport/test_hmac.rb +34 -0
  100. data/test/transport/test_identity_cipher.rb +40 -0
  101. data/test/transport/test_packet_stream.rb +435 -0
  102. data/test/transport/test_server_version.rb +68 -0
  103. data/test/transport/test_session.rb +315 -0
  104. data/test/transport/test_state.rb +173 -0
  105. metadata +162 -0
data/lib/net/ssh.rb ADDED
@@ -0,0 +1,215 @@
1
+ # Make sure HOME is set, regardless of OS, so that File.expand_path works
2
+ # as expected with tilde characters.
3
+ ENV['HOME'] ||= ENV['HOMEPATH'] ? "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" : "."
4
+
5
+ require 'logger'
6
+
7
+ require 'net/ssh/config'
8
+ require 'net/ssh/errors'
9
+ require 'net/ssh/loggable'
10
+ require 'net/ssh/transport/session'
11
+ require 'net/ssh/authentication/session'
12
+ require 'net/ssh/connection/session'
13
+
14
+ module Net
15
+
16
+ # Net::SSH is a library for interacting, programmatically, with remote
17
+ # processes via the SSH2 protocol. Sessions are always initiated via
18
+ # Net::SSH.start. From there, a program interacts with the new SSH session
19
+ # via the convenience methods on Net::SSH::Connection::Session, by opening
20
+ # and interacting with new channels (Net::SSH::Connection:Session#open_channel
21
+ # and Net::SSH::Connection::Channel), or by forwarding local and/or
22
+ # remote ports through the connection (Net::SSH::Service::Forward).
23
+ #
24
+ # The SSH protocol is very event-oriented. Requests are sent from the client
25
+ # to the server, and are answered asynchronously. This gives great flexibility
26
+ # (since clients can have multiple requests pending at a time), but it also
27
+ # adds complexity. Net::SSH tries to manage this complexity by providing
28
+ # some simpler methods of synchronous communication (see Net::SSH::Connection::Session#exec!).
29
+ #
30
+ # In general, though, and if you want to do anything more complicated than
31
+ # simply executing commands and capturing their output, you'll need to use
32
+ # channels (Net::SSH::Connection::Channel) to build state machines that are
33
+ # executed while the event loop runs (Net::SSH::Connection::Session#loop).
34
+ #
35
+ # Net::SSH::Connection::Session and Net::SSH::Connection::Channel have more
36
+ # information about this technique.
37
+ #
38
+ # = "Um, all I want to do is X, just show me how!"
39
+ #
40
+ # == X == "execute a command and capture the output"
41
+ #
42
+ # Net::SSH.start("host", "user", :password => "password") do |ssh|
43
+ # result = ssh.exec!("ls -l")
44
+ # puts result
45
+ # end
46
+ #
47
+ # == X == "forward connections on a local port to a remote host"
48
+ #
49
+ # Net::SSH.start("host", "user", :password => "password") do |ssh|
50
+ # ssh.forward.local(1234, "www.google.com", 80)
51
+ # ssh.loop { true }
52
+ # end
53
+ #
54
+ # == X == "forward connections on a remote port to the local host"
55
+ #
56
+ # Net::SSH.start("host", "user", :password => "password") do |ssh|
57
+ # ssh.forward.remote(80, "www.google.com", 1234)
58
+ # ssh.loop { true }
59
+ # end
60
+ module SSH
61
+ # This is the set of options that Net::SSH.start recognizes. See
62
+ # Net::SSH.start for a description of each option.
63
+ VALID_OPTIONS = [
64
+ :auth_methods, :compression, :compression_level, :config, :encryption,
65
+ :forward_agent, :hmac, :host_key, :kex, :keys, :key_data, :languages,
66
+ :logger, :paranoid, :password, :port, :proxy, :rekey_blocks_limit,
67
+ :rekey_limit, :rekey_packet_limit, :timeout, :verbose,
68
+ :global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
69
+ :host_name, :user, :properties, :passphrase
70
+ ]
71
+
72
+ # The standard means of starting a new SSH connection. When used with a
73
+ # block, the connection will be closed when the block terminates, otherwise
74
+ # the connection will just be returned. The yielded (or returned) value
75
+ # will be an instance of Net::SSH::Connection::Session (q.v.). (See also
76
+ # Net::SSH::Connection::Channel and Net::SSH::Service::Forward.)
77
+ #
78
+ # Net::SSH.start("host", "user") do |ssh|
79
+ # ssh.exec! "cp /some/file /another/location"
80
+ # hostname = ssh.exec!("hostname")
81
+ #
82
+ # ssh.open_channel do |ch|
83
+ # ch.exec "sudo -p 'sudo password: ' ls" do |ch, success|
84
+ # abort "could not execute sudo ls" unless success
85
+ #
86
+ # ch.on_data do |ch, data|
87
+ # print data
88
+ # if data =~ /sudo password: /
89
+ # ch.send_data("password\n")
90
+ # end
91
+ # end
92
+ # end
93
+ # end
94
+ #
95
+ # ssh.loop
96
+ # end
97
+ #
98
+ # This method accepts the following options (all are optional):
99
+ #
100
+ # * :auth_methods => an array of authentication methods to try
101
+ # * :compression => the compression algorithm to use, or +true+ to use
102
+ # whatever is supported.
103
+ # * :compression_level => the compression level to use when sending data
104
+ # * :config => set to +true+ to load the default OpenSSH config files
105
+ # (~/.ssh/config, /etc/ssh_config), or to +false+ to not load them, or to
106
+ # a file-name (or array of file-names) to load those specific configuration
107
+ # files. Defaults to +true+.
108
+ # * :encryption => the encryption cipher (or ciphers) to use
109
+ # * :forward_agent => set to true if you want the SSH agent connection to
110
+ # be forwarded
111
+ # * :global_known_hosts_file => the location of the global known hosts
112
+ # file. Set to an array if you want to specify multiple global known
113
+ # hosts files. Defaults to %w(/etc/ssh/known_hosts /etc/ssh/known_hosts2).
114
+ # * :hmac => the hmac algorithm (or algorithms) to use
115
+ # * :host_key => the host key algorithm (or algorithms) to use
116
+ # * :host_key_alias => the host name to use when looking up or adding a
117
+ # host to a known_hosts dictionary file
118
+ # * :host_name => the real host name or IP to log into. This is used
119
+ # instead of the +host+ parameter, and is primarily only useful when
120
+ # specified in an SSH configuration file. It lets you specify an
121
+ # "alias", similarly to adding an entry in /etc/hosts but without needing
122
+ # to modify /etc/hosts.
123
+ # * :kex => the key exchange algorithm (or algorithms) to use
124
+ # * :keys => an array of file names of private keys to use for publickey
125
+ # and hostbased authentication
126
+ # * :key_data => an array of strings, with each element of the array being
127
+ # a raw private key in PEM format.
128
+ # * :logger => the logger instance to use when logging
129
+ # * :paranoid => either true, false, or :very, specifying how strict
130
+ # host-key verification should be
131
+ # * :passphrase => the passphrase to use when loading a private key (default
132
+ # is +nil+, for no passphrase)
133
+ # * :password => the password to use to login
134
+ # * :port => the port to use when connecting to the remote host
135
+ # * :properties => a hash of key/value pairs to add to the new connection's
136
+ # properties (see Net::SSH::Connection::Session#properties)
137
+ # * :proxy => a proxy instance (see Proxy) to use when connecting
138
+ # * :rekey_blocks_limit => the max number of blocks to process before rekeying
139
+ # * :rekey_limit => the max number of bytes to process before rekeying
140
+ # * :rekey_packet_limit => the max number of packets to process before rekeying
141
+ # * :timeout => how long to wait for the initial connection to be made
142
+ # * :user => the user name to log in as; this overrides the +user+
143
+ # parameter, and is primarily only useful when provided via an SSH
144
+ # configuration file.
145
+ # * :user_known_hosts_file => the location of the user known hosts file.
146
+ # Set to an array to specify multiple user known hosts files.
147
+ # Defaults to %w(~/.ssh/known_hosts ~/.ssh/known_hosts2).
148
+ # * :verbose => how verbose to be (Logger verbosity constants, Logger::DEBUG
149
+ # is very verbose, Logger::FATAL is all but silent). Logger::FATAL is the
150
+ # default. The symbols :debug, :info, :warn, :error, and :fatal are also
151
+ # supported and are translated to the corresponding Logger constant.
152
+ def self.start(host, user, options={}, &block)
153
+ invalid_options = options.keys - VALID_OPTIONS
154
+ if invalid_options.any?
155
+ raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}"
156
+ end
157
+
158
+ options[:user] = user if user
159
+ options = configuration_for(host, options.fetch(:config, true)).merge(options)
160
+ host = options.fetch(:host_name, host)
161
+
162
+ if !options.key?(:logger)
163
+ options[:logger] = Logger.new(STDERR)
164
+ options[:logger].level = Logger::FATAL
165
+ end
166
+
167
+ if options[:verbose]
168
+ options[:logger].level = case options[:verbose]
169
+ when Fixnum then options[:verbose]
170
+ when :debug then Logger::DEBUG
171
+ when :info then Logger::INFO
172
+ when :warn then Logger::WARN
173
+ when :error then Logger::ERROR
174
+ when :fatal then Logger::FATAL
175
+ else raise ArgumentError, "can't convert #{options[:verbose].inspect} to any of the Logger level constants"
176
+ end
177
+ end
178
+
179
+ transport = Transport::Session.new(host, options)
180
+ auth = Authentication::Session.new(transport, options)
181
+
182
+ user = options.fetch(:user, user)
183
+ if auth.authenticate("ssh-connection", user, options[:password])
184
+ connection = Connection::Session.new(transport, options)
185
+ if block_given?
186
+ yield connection
187
+ connection.close
188
+ else
189
+ return connection
190
+ end
191
+ else
192
+ raise AuthenticationFailed, user
193
+ end
194
+ end
195
+
196
+ # Returns a hash of the configuration options for the given host, as read
197
+ # from the SSH configuration file(s). If +use_ssh_config+ is true (the
198
+ # default), this will load configuration from both ~/.ssh/config and
199
+ # /etc/ssh_config. If +use_ssh_config+ is nil or false, nothing will be
200
+ # loaded (and an empty hash returned). Otherwise, +use_ssh_config+ may
201
+ # be a file name (or array of file names) of SSH configuration file(s)
202
+ # to read.
203
+ #
204
+ # See Net::SSH::Config for the full description of all supported options.
205
+ def self.configuration_for(host, use_ssh_config=true)
206
+ files = case use_ssh_config
207
+ when true then Net::SSH::Config.default_files
208
+ when false, nil then return {}
209
+ else Array(use_ssh_config)
210
+ end
211
+
212
+ Net::SSH::Config.for(host, files)
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,176 @@
1
+ require 'net/ssh/buffer'
2
+ require 'net/ssh/errors'
3
+ require 'net/ssh/loggable'
4
+ require 'net/ssh/transport/server_version'
5
+
6
+ require 'net/ssh/authentication/pageant' if File::ALT_SEPARATOR && !(RUBY_PLATFORM =~ /java/)
7
+
8
+ module Net; module SSH; module Authentication
9
+
10
+ # A trivial exception class for representing agent-specific errors.
11
+ class AgentError < Net::SSH::Exception; end
12
+
13
+ # An exception for indicating that the SSH agent is not available.
14
+ class AgentNotAvailable < AgentError; end
15
+
16
+ # This class implements a simple client for the ssh-agent protocol. It
17
+ # does not implement any specific protocol, but instead copies the
18
+ # behavior of the ssh-agent functions in the OpenSSH library (3.8).
19
+ #
20
+ # This means that although it behaves like a SSH1 client, it also has
21
+ # some SSH2 functionality (like signing data).
22
+ class Agent
23
+ include Loggable
24
+
25
+ # A simple module for extending keys, to allow comments to be specified
26
+ # for them.
27
+ module Comment
28
+ attr_accessor :comment
29
+ end
30
+
31
+ SSH2_AGENT_REQUEST_VERSION = 1
32
+ SSH2_AGENT_REQUEST_IDENTITIES = 11
33
+ SSH2_AGENT_IDENTITIES_ANSWER = 12
34
+ SSH2_AGENT_SIGN_REQUEST = 13
35
+ SSH2_AGENT_SIGN_RESPONSE = 14
36
+ SSH2_AGENT_FAILURE = 30
37
+ SSH2_AGENT_VERSION_RESPONSE = 103
38
+
39
+ SSH_COM_AGENT2_FAILURE = 102
40
+
41
+ SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
42
+ SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
43
+ SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
44
+ SSH_AGENT_FAILURE = 5
45
+
46
+ # The underlying socket being used to communicate with the SSH agent.
47
+ attr_reader :socket
48
+
49
+ # Instantiates a new agent object, connects to a running SSH agent,
50
+ # negotiates the agent protocol version, and returns the agent object.
51
+ def self.connect(logger=nil)
52
+ agent = new(logger)
53
+ agent.connect!
54
+ agent.negotiate!
55
+ agent
56
+ end
57
+
58
+ # Creates a new Agent object, using the optional logger instance to
59
+ # report status.
60
+ def initialize(logger=nil)
61
+ self.logger = logger
62
+ end
63
+
64
+ # Connect to the agent process using the socket factory and socket name
65
+ # given by the attribute writers. If the agent on the other end of the
66
+ # socket reports that it is an SSH2-compatible agent, this will fail
67
+ # (it only supports the ssh-agent distributed by OpenSSH).
68
+ def connect!
69
+ begin
70
+ debug { "connecting to ssh-agent" }
71
+ @socket = agent_socket_factory.open(ENV['SSH_AUTH_SOCK'])
72
+ rescue
73
+ error { "could not connect to ssh-agent" }
74
+ raise AgentNotAvailable, $!.message
75
+ end
76
+ end
77
+
78
+ # Attempts to negotiate the SSH agent protocol version. Raises an error
79
+ # if the version could not be negotiated successfully.
80
+ def negotiate!
81
+ # determine what type of agent we're communicating with
82
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
83
+
84
+ if type == SSH2_AGENT_VERSION_RESPONSE
85
+ raise NotImplementedError, "SSH2 agents are not yet supported"
86
+ elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
87
+ raise AgentError, "unknown response from agent: #{type}, #{body.to_s.inspect}"
88
+ end
89
+ end
90
+
91
+ # Return an array of all identities (public keys) known to the agent.
92
+ # Each key returned is augmented with a +comment+ property which is set
93
+ # to the comment returned by the agent for that key.
94
+ def identities
95
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
96
+ raise AgentError, "could not get identity count" if agent_failed(type)
97
+ raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
98
+
99
+ identities = []
100
+ body.read_long.times do
101
+ key = Buffer.new(body.read_string).read_key
102
+ key.extend(Comment)
103
+ key.comment = body.read_string
104
+ identities.push key
105
+ end
106
+
107
+ return identities
108
+ end
109
+
110
+ # Closes this socket. This agent reference is no longer able to
111
+ # query the agent.
112
+ def close
113
+ @socket.close
114
+ end
115
+
116
+ # Using the agent and the given public key, sign the given data. The
117
+ # signature is returned in SSH2 format.
118
+ def sign(key, data)
119
+ type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, 0)
120
+
121
+ if agent_failed(type)
122
+ raise AgentError, "agent could not sign data with requested identity"
123
+ elsif type != SSH2_AGENT_SIGN_RESPONSE
124
+ raise AgentError, "bad authentication response #{type}"
125
+ end
126
+
127
+ return reply.read_string
128
+ end
129
+
130
+ private
131
+
132
+ # Returns the agent socket factory to use.
133
+ def agent_socket_factory
134
+ if File::ALT_SEPARATOR
135
+ Pageant::Socket
136
+ else
137
+ UNIXSocket
138
+ end
139
+ end
140
+
141
+ # Send a new packet of the given type, with the associated data.
142
+ def send_packet(type, *args)
143
+ buffer = Buffer.from(*args)
144
+ data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
145
+ debug { "sending agent request #{type} len #{buffer.length}" }
146
+ @socket.send data, 0
147
+ end
148
+
149
+ # Read the next packet from the agent. This will return a two-part
150
+ # tuple consisting of the packet type, and the packet's body (which
151
+ # is returned as a Net::SSH::Buffer).
152
+ def read_packet
153
+ buffer = Net::SSH::Buffer.new(@socket.read(4))
154
+ buffer.append(@socket.read(buffer.read_long))
155
+ type = buffer.read_byte
156
+ debug { "received agent packet #{type} len #{buffer.length-4}" }
157
+ return type, buffer
158
+ end
159
+
160
+ # Send the given packet and return the subsequent reply from the agent.
161
+ # (See #send_packet and #read_packet).
162
+ def send_and_wait(type, *args)
163
+ send_packet(type, *args)
164
+ read_packet
165
+ end
166
+
167
+ # Returns +true+ if the parameter indicates a "failure" response from
168
+ # the agent, and +false+ otherwise.
169
+ def agent_failed(type)
170
+ type == SSH_AGENT_FAILURE ||
171
+ type == SSH2_AGENT_FAILURE ||
172
+ type == SSH_COM_AGENT2_FAILURE
173
+ end
174
+ end
175
+
176
+ end; end; end
@@ -0,0 +1,18 @@
1
+ module Net; module SSH; module Authentication
2
+
3
+ # Describes the constants used by the Net::SSH::Authentication components
4
+ # of the Net::SSH library. Individual authentication method implemenations
5
+ # may define yet more constants that are specific to their implementation.
6
+ module Constants
7
+ USERAUTH_REQUEST = 50
8
+ USERAUTH_FAILURE = 51
9
+ USERAUTH_SUCCESS = 52
10
+ USERAUTH_BANNER = 53
11
+
12
+ USERAUTH_PASSWD_CHANGEREQ = 60
13
+ USERAUTH_PK_OK = 60
14
+
15
+ USERAUTH_METHOD_RANGE = 60..79
16
+ end
17
+
18
+ end; end; end
@@ -0,0 +1,193 @@
1
+ require 'net/ssh/errors'
2
+ require 'net/ssh/key_factory'
3
+ require 'net/ssh/loggable'
4
+ require 'net/ssh/authentication/agent'
5
+
6
+ module Net
7
+ module SSH
8
+ module Authentication
9
+
10
+ # A trivial exception class used to report errors in the key manager.
11
+ class KeyManagerError < Net::SSH::Exception; end
12
+
13
+ # This class encapsulates all operations done by clients on a user's
14
+ # private keys. In practice, the client should never need a reference
15
+ # to a private key; instead, they grab a list of "identities" (public
16
+ # keys) that are available from the KeyManager, and then use
17
+ # the KeyManager to do various private key operations using those
18
+ # identities.
19
+ #
20
+ # The KeyManager also uses the Agent class to encapsulate the
21
+ # ssh-agent. Thus, from a client's perspective it is completely
22
+ # hidden whether an identity comes from the ssh-agent or from a file
23
+ # on disk.
24
+ class KeyManager
25
+ include Loggable
26
+
27
+ # The list of user key files that will be examined
28
+ attr_reader :key_files
29
+
30
+ # The list of user key data that will be examined
31
+ attr_reader :key_data
32
+
33
+ # The map of loaded identities
34
+ attr_reader :known_identities
35
+
36
+ # The map of options that were passed to the key-manager
37
+ attr_reader :options
38
+
39
+ # Create a new KeyManager. By default, the manager will
40
+ # use the ssh-agent (if it is running).
41
+ def initialize(logger, options={})
42
+ self.logger = logger
43
+ @key_files = []
44
+ @key_data = []
45
+ @use_agent = true
46
+ @known_identities = {}
47
+ @agent = nil
48
+ @options = options
49
+ end
50
+
51
+ # Clear all knowledge of any loaded user keys. This also clears the list
52
+ # of default identity files that are to be loaded, thus making it
53
+ # appropriate to use if a client wishes to NOT use the default identity
54
+ # files.
55
+ def clear!
56
+ key_files.clear
57
+ key_data.clear
58
+ known_identities.clear
59
+ self
60
+ end
61
+
62
+ # Add the given key_file to the list of key files that will be used.
63
+ def add(key_file)
64
+ key_files.push(File.expand_path(key_file)).uniq!
65
+ self
66
+ end
67
+
68
+ # Add the given key_file to the list of keys that will be used.
69
+ def add_key_data(key_data_)
70
+ key_data.push(key_data_).uniq!
71
+ self
72
+ end
73
+
74
+ # This is used as a hint to the KeyManager indicating that the agent
75
+ # connection is no longer needed. Any other open resources may be closed
76
+ # at this time.
77
+ #
78
+ # Calling this does NOT indicate that the KeyManager will no longer
79
+ # be used. Identities may still be requested and operations done on
80
+ # loaded identities, in which case, the agent will be automatically
81
+ # reconnected. This method simply allows the client connection to be
82
+ # closed when it will not be used in the immediate future.
83
+ def finish
84
+ @agent.close if @agent
85
+ @agent = nil
86
+ end
87
+
88
+ # Iterates over all available identities (public keys) known to this
89
+ # manager. As it finds one, it will then yield it to the caller.
90
+ # The origin of the identities may be from files on disk or from an
91
+ # ssh-agent. Note that identities from an ssh-agent are always listed
92
+ # first in the array, with other identities coming after.
93
+ def each_identity
94
+ if agent
95
+ agent.identities.each do |key|
96
+ known_identities[key] = { :from => :agent }
97
+ yield key
98
+ end
99
+ end
100
+
101
+ key_files.each do |file|
102
+ public_key_file = file + ".pub"
103
+ if File.readable?(public_key_file)
104
+ begin
105
+ key = KeyFactory.load_public_key(public_key_file)
106
+ known_identities[key] = { :from => :file, :file => file }
107
+ yield key
108
+ rescue Exception => e
109
+ error { "could not load public key file `#{public_key_file}': #{e.class} (#{e.message})" }
110
+ end
111
+ elsif File.readable?(file)
112
+ begin
113
+ private_key = KeyFactory.load_private_key(file, options[:passphrase])
114
+ key = private_key.send(:public_key)
115
+ known_identities[key] = { :from => :file, :file => file, :key => private_key }
116
+ yield key
117
+ rescue Exception => e
118
+ error { "could not load private key file `#{file}': #{e.class} (#{e.message})" }
119
+ end
120
+ end
121
+ end
122
+
123
+ key_data.each do |data|
124
+ private_key = KeyFactory.load_data_private_key(data)
125
+ key = private_key.send(:public_key)
126
+ known_identities[key] = { :from => :key_data, :data => data, :key => private_key }
127
+ yield key
128
+ end
129
+
130
+ self
131
+ end
132
+
133
+ # Sign the given data, using the corresponding private key of the given
134
+ # identity. If the identity was originally obtained from an ssh-agent,
135
+ # then the ssh-agent will be used to sign the data, otherwise the
136
+ # private key for the identity will be loaded from disk (if it hasn't
137
+ # been loaded already) and will then be used to sign the data.
138
+ #
139
+ # Regardless of the identity's origin or who does the signing, this
140
+ # will always return the signature in an SSH2-specified "signature
141
+ # blob" format.
142
+ def sign(identity, data)
143
+ info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager"
144
+
145
+ if info[:key].nil? && info[:from] == :file
146
+ begin
147
+ info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase])
148
+ rescue Exception => e
149
+ raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
150
+ end
151
+ end
152
+
153
+ if info[:key]
154
+ return Net::SSH::Buffer.from(:string, identity.ssh_type,
155
+ :string, info[:key].ssh_do_sign(data.to_s)).to_s
156
+ end
157
+
158
+ if info[:from] == :agent
159
+ raise KeyManagerError, "the agent is no longer available" unless agent
160
+ return agent.sign(identity, data.to_s)
161
+ end
162
+
163
+ raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
164
+ end
165
+
166
+ # Identifies whether the ssh-agent will be used or not.
167
+ def use_agent?
168
+ @use_agent
169
+ end
170
+
171
+ # Toggles whether the ssh-agent will be used or not. If true, an
172
+ # attempt will be made to use the ssh-agent. If false, any existing
173
+ # connection to an agent is closed and the agent will not be used.
174
+ def use_agent=(use_agent)
175
+ finish if !use_agent
176
+ @use_agent = use_agent
177
+ end
178
+
179
+ # Returns an Agent instance to use for communicating with an SSH
180
+ # agent process. Returns nil if use of an SSH agent has been disabled,
181
+ # or if the agent is otherwise not available.
182
+ def agent
183
+ return unless use_agent?
184
+ @agent ||= Agent.connect(logger)
185
+ rescue AgentNotAvailable
186
+ @use_agent = false
187
+ nil
188
+ end
189
+ end
190
+
191
+ end
192
+ end
193
+ end