sonixlabs-net-ssh 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +262 -0
- data/Manifest +121 -0
- data/README.rdoc +184 -0
- data/Rakefile +86 -0
- data/Rudyfile +96 -0
- data/THANKS.rdoc +19 -0
- data/lib/net/ssh.rb +223 -0
- data/lib/net/ssh/authentication/agent.rb +179 -0
- data/lib/net/ssh/authentication/constants.rb +18 -0
- data/lib/net/ssh/authentication/key_manager.rb +253 -0
- data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
- data/lib/net/ssh/authentication/methods/hostbased.rb +75 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +70 -0
- data/lib/net/ssh/authentication/methods/password.rb +43 -0
- data/lib/net/ssh/authentication/methods/publickey.rb +96 -0
- data/lib/net/ssh/authentication/pageant.rb +264 -0
- data/lib/net/ssh/authentication/session.rb +146 -0
- data/lib/net/ssh/buffer.rb +340 -0
- data/lib/net/ssh/buffered_io.rb +198 -0
- data/lib/net/ssh/config.rb +207 -0
- data/lib/net/ssh/connection/channel.rb +630 -0
- data/lib/net/ssh/connection/constants.rb +33 -0
- data/lib/net/ssh/connection/session.rb +597 -0
- data/lib/net/ssh/connection/term.rb +178 -0
- data/lib/net/ssh/errors.rb +88 -0
- data/lib/net/ssh/key_factory.rb +102 -0
- data/lib/net/ssh/known_hosts.rb +129 -0
- data/lib/net/ssh/loggable.rb +61 -0
- data/lib/net/ssh/packet.rb +102 -0
- data/lib/net/ssh/prompt.rb +93 -0
- data/lib/net/ssh/proxy/command.rb +75 -0
- data/lib/net/ssh/proxy/errors.rb +14 -0
- data/lib/net/ssh/proxy/http.rb +94 -0
- data/lib/net/ssh/proxy/socks4.rb +70 -0
- data/lib/net/ssh/proxy/socks5.rb +142 -0
- data/lib/net/ssh/ruby_compat.rb +43 -0
- data/lib/net/ssh/service/forward.rb +298 -0
- data/lib/net/ssh/test.rb +89 -0
- data/lib/net/ssh/test/channel.rb +129 -0
- data/lib/net/ssh/test/extensions.rb +152 -0
- data/lib/net/ssh/test/kex.rb +44 -0
- data/lib/net/ssh/test/local_packet.rb +51 -0
- data/lib/net/ssh/test/packet.rb +81 -0
- data/lib/net/ssh/test/remote_packet.rb +38 -0
- data/lib/net/ssh/test/script.rb +157 -0
- data/lib/net/ssh/test/socket.rb +64 -0
- data/lib/net/ssh/transport/algorithms.rb +386 -0
- data/lib/net/ssh/transport/cipher_factory.rb +79 -0
- data/lib/net/ssh/transport/constants.rb +30 -0
- data/lib/net/ssh/transport/hmac.rb +42 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +79 -0
- data/lib/net/ssh/transport/hmac/md5.rb +12 -0
- data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
- data/lib/net/ssh/transport/hmac/none.rb +15 -0
- data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
- data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_256.rb +15 -0
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +13 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +14 -0
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +13 -0
- data/lib/net/ssh/transport/identity_cipher.rb +55 -0
- data/lib/net/ssh/transport/kex.rb +17 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +80 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +15 -0
- data/lib/net/ssh/transport/key_expander.rb +26 -0
- data/lib/net/ssh/transport/openssl.rb +127 -0
- data/lib/net/ssh/transport/packet_stream.rb +235 -0
- data/lib/net/ssh/transport/server_version.rb +71 -0
- data/lib/net/ssh/transport/session.rb +278 -0
- data/lib/net/ssh/transport/state.rb +206 -0
- data/lib/net/ssh/verifiers/lenient.rb +30 -0
- data/lib/net/ssh/verifiers/null.rb +12 -0
- data/lib/net/ssh/verifiers/strict.rb +53 -0
- data/lib/net/ssh/version.rb +62 -0
- data/lib/sonixlabs-net-ssh.rb +1 -0
- data/net-ssh.gemspec +145 -0
- data/setup.rb +1585 -0
- data/support/arcfour_check.rb +20 -0
- data/support/ssh_tunnel_bug.rb +65 -0
- data/test/authentication/methods/common.rb +28 -0
- data/test/authentication/methods/test_abstract.rb +51 -0
- data/test/authentication/methods/test_hostbased.rb +114 -0
- data/test/authentication/methods/test_keyboard_interactive.rb +100 -0
- data/test/authentication/methods/test_password.rb +52 -0
- data/test/authentication/methods/test_publickey.rb +148 -0
- data/test/authentication/test_agent.rb +205 -0
- data/test/authentication/test_key_manager.rb +171 -0
- data/test/authentication/test_session.rb +106 -0
- data/test/common.rb +107 -0
- data/test/configs/eqsign +3 -0
- data/test/configs/exact_match +8 -0
- data/test/configs/host_plus +10 -0
- data/test/configs/multihost +4 -0
- data/test/configs/wild_cards +14 -0
- data/test/connection/test_channel.rb +467 -0
- data/test/connection/test_session.rb +488 -0
- data/test/test_all.rb +9 -0
- data/test/test_buffer.rb +336 -0
- data/test/test_buffered_io.rb +63 -0
- data/test/test_config.rb +120 -0
- data/test/test_key_factory.rb +79 -0
- data/test/transport/hmac/test_md5.rb +39 -0
- data/test/transport/hmac/test_md5_96.rb +25 -0
- data/test/transport/hmac/test_none.rb +34 -0
- data/test/transport/hmac/test_sha1.rb +34 -0
- data/test/transport/hmac/test_sha1_96.rb +25 -0
- data/test/transport/hmac/test_sha2_256.rb +35 -0
- data/test/transport/hmac/test_sha2_256_96.rb +25 -0
- data/test/transport/hmac/test_sha2_512.rb +35 -0
- data/test/transport/hmac/test_sha2_512_96.rb +25 -0
- data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +33 -0
- data/test/transport/test_algorithms.rb +308 -0
- data/test/transport/test_cipher_factory.rb +213 -0
- data/test/transport/test_hmac.rb +34 -0
- data/test/transport/test_identity_cipher.rb +40 -0
- data/test/transport/test_packet_stream.rb +736 -0
- data/test/transport/test_server_version.rb +78 -0
- data/test/transport/test_session.rb +315 -0
- data/test/transport/test_state.rb +179 -0
- metadata +178 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'net/ssh/errors'
|
|
2
|
+
require 'net/ssh/authentication/methods/abstract'
|
|
3
|
+
|
|
4
|
+
module Net
|
|
5
|
+
module SSH
|
|
6
|
+
module Authentication
|
|
7
|
+
module Methods
|
|
8
|
+
|
|
9
|
+
# Implements the "password" SSH authentication method.
|
|
10
|
+
class Password < Abstract
|
|
11
|
+
# Attempt to authenticate the given user for the given service. If
|
|
12
|
+
# the password parameter is nil, this will never do anything except
|
|
13
|
+
# return false.
|
|
14
|
+
def authenticate(next_service, username, password=nil)
|
|
15
|
+
return false unless password
|
|
16
|
+
|
|
17
|
+
send_message(userauth_request(username, next_service, "password", false, password))
|
|
18
|
+
message = session.next_message
|
|
19
|
+
|
|
20
|
+
case message.type
|
|
21
|
+
when USERAUTH_SUCCESS
|
|
22
|
+
debug { "password succeeded" }
|
|
23
|
+
return true
|
|
24
|
+
when USERAUTH_FAILURE
|
|
25
|
+
debug { "password failed" }
|
|
26
|
+
|
|
27
|
+
raise Net::SSH::Authentication::DisallowedMethod unless
|
|
28
|
+
message[:authentications].split(/,/).include? 'password'
|
|
29
|
+
|
|
30
|
+
return false
|
|
31
|
+
when USERAUTH_PASSWD_CHANGEREQ
|
|
32
|
+
debug { "password change request received, failing" }
|
|
33
|
+
return false
|
|
34
|
+
else
|
|
35
|
+
raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'net/ssh/buffer'
|
|
2
|
+
require 'net/ssh/errors'
|
|
3
|
+
require 'net/ssh/authentication/methods/abstract'
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module SSH
|
|
7
|
+
module Authentication
|
|
8
|
+
module Methods
|
|
9
|
+
|
|
10
|
+
# Implements the "publickey" SSH authentication method.
|
|
11
|
+
class Publickey < Abstract
|
|
12
|
+
# Attempts to perform public-key authentication for the given
|
|
13
|
+
# username, trying each identity known to the key manager. If any of
|
|
14
|
+
# them succeed, returns +true+, otherwise returns +false+. This
|
|
15
|
+
# requires the presence of a key manager.
|
|
16
|
+
def authenticate(next_service, username, password=nil)
|
|
17
|
+
return false unless key_manager
|
|
18
|
+
|
|
19
|
+
key_manager.each_identity do |identity|
|
|
20
|
+
return true if authenticate_with(identity, next_service, username)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
# Builds a packet that contains the request formatted for sending
|
|
29
|
+
# a public-key request to the server.
|
|
30
|
+
def build_request(pub_key, username, next_service, has_sig)
|
|
31
|
+
blob = Net::SSH::Buffer.new
|
|
32
|
+
blob.write_key pub_key
|
|
33
|
+
|
|
34
|
+
userauth_request(username, next_service, "publickey", has_sig,
|
|
35
|
+
pub_key.ssh_type, blob.to_s)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Builds and sends a request formatted for a public-key
|
|
39
|
+
# authentication request.
|
|
40
|
+
def send_request(pub_key, username, next_service, signature=nil)
|
|
41
|
+
msg = build_request(pub_key, username, next_service, !signature.nil?)
|
|
42
|
+
msg.write_string(signature) if signature
|
|
43
|
+
send_message(msg)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Attempts to perform public-key authentication for the given
|
|
47
|
+
# username, with the given identity (public key). Returns +true+ if
|
|
48
|
+
# successful, or +false+ otherwise.
|
|
49
|
+
def authenticate_with(identity, next_service, username)
|
|
50
|
+
debug { "trying publickey (#{identity.fingerprint})" }
|
|
51
|
+
send_request(identity, username, next_service)
|
|
52
|
+
|
|
53
|
+
message = session.next_message
|
|
54
|
+
|
|
55
|
+
case message.type
|
|
56
|
+
when USERAUTH_PK_OK
|
|
57
|
+
buffer = build_request(identity, username, next_service, true)
|
|
58
|
+
sig_data = Net::SSH::Buffer.new
|
|
59
|
+
sig_data.write_string(session_id)
|
|
60
|
+
sig_data.append(buffer.to_s)
|
|
61
|
+
|
|
62
|
+
sig_blob = key_manager.sign(identity, sig_data)
|
|
63
|
+
|
|
64
|
+
send_request(identity, username, next_service, sig_blob.to_s)
|
|
65
|
+
message = session.next_message
|
|
66
|
+
|
|
67
|
+
case message.type
|
|
68
|
+
when USERAUTH_SUCCESS
|
|
69
|
+
debug { "publickey succeeded (#{identity.fingerprint})" }
|
|
70
|
+
return true
|
|
71
|
+
when USERAUTH_FAILURE
|
|
72
|
+
debug { "publickey failed (#{identity.fingerprint})" }
|
|
73
|
+
|
|
74
|
+
raise Net::SSH::Authentication::DisallowedMethod unless
|
|
75
|
+
message[:authentications].split(/,/).include? 'publickey'
|
|
76
|
+
|
|
77
|
+
return false
|
|
78
|
+
else
|
|
79
|
+
raise Net::SSH::Exception,
|
|
80
|
+
"unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
when USERAUTH_FAILURE
|
|
84
|
+
return false
|
|
85
|
+
|
|
86
|
+
else
|
|
87
|
+
raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
require 'dl/import'
|
|
2
|
+
|
|
3
|
+
if RUBY_VERSION < "1.9"
|
|
4
|
+
require 'dl/struct'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
if RUBY_VERSION =~ /^1.9/
|
|
8
|
+
require 'dl/types'
|
|
9
|
+
require 'dl'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require 'net/ssh/errors'
|
|
13
|
+
|
|
14
|
+
module Net; module SSH; module Authentication
|
|
15
|
+
|
|
16
|
+
# This module encapsulates the implementation of a socket factory that
|
|
17
|
+
# uses the PuTTY "pageant" utility to obtain information about SSH
|
|
18
|
+
# identities.
|
|
19
|
+
#
|
|
20
|
+
# This code is a slightly modified version of the original implementation
|
|
21
|
+
# by Guillaume Mar�ais (guillaume.marcais@free.fr). It is used and
|
|
22
|
+
# relicensed by permission.
|
|
23
|
+
module Pageant
|
|
24
|
+
|
|
25
|
+
# From Putty pageant.c
|
|
26
|
+
AGENT_MAX_MSGLEN = 8192
|
|
27
|
+
AGENT_COPYDATA_ID = 0x804e50ba
|
|
28
|
+
|
|
29
|
+
# The definition of the Windows methods and data structures used in
|
|
30
|
+
# communicating with the pageant process.
|
|
31
|
+
module Win
|
|
32
|
+
if RUBY_VERSION < "1.9"
|
|
33
|
+
extend DL::Importable
|
|
34
|
+
|
|
35
|
+
dlload 'user32'
|
|
36
|
+
dlload 'kernel32'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if RUBY_VERSION =~ /^1.9/
|
|
40
|
+
extend DL::Importer
|
|
41
|
+
dlload 'user32','kernel32'
|
|
42
|
+
include DL::Win32Types
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
typealias("LPCTSTR", "char *") # From winnt.h
|
|
46
|
+
typealias("LPVOID", "void *") # From winnt.h
|
|
47
|
+
typealias("LPCVOID", "const void *") # From windef.h
|
|
48
|
+
typealias("LRESULT", "long") # From windef.h
|
|
49
|
+
typealias("WPARAM", "unsigned int *") # From windef.h
|
|
50
|
+
typealias("LPARAM", "long *") # From windef.h
|
|
51
|
+
typealias("PDWORD_PTR", "long *") # From basetsd.h
|
|
52
|
+
|
|
53
|
+
# From winbase.h, winnt.h
|
|
54
|
+
INVALID_HANDLE_VALUE = -1
|
|
55
|
+
NULL = nil
|
|
56
|
+
PAGE_READWRITE = 0x0004
|
|
57
|
+
FILE_MAP_WRITE = 2
|
|
58
|
+
WM_COPYDATA = 74
|
|
59
|
+
|
|
60
|
+
SMTO_NORMAL = 0 # From winuser.h
|
|
61
|
+
|
|
62
|
+
# args: lpClassName, lpWindowName
|
|
63
|
+
extern 'HWND FindWindow(LPCTSTR, LPCTSTR)'
|
|
64
|
+
|
|
65
|
+
# args: none
|
|
66
|
+
extern 'DWORD GetCurrentThreadId()'
|
|
67
|
+
|
|
68
|
+
# args: hFile, (ignored), flProtect, dwMaximumSizeHigh,
|
|
69
|
+
# dwMaximumSizeLow, lpName
|
|
70
|
+
extern 'HANDLE CreateFileMapping(HANDLE, void *, DWORD, DWORD, ' +
|
|
71
|
+
'DWORD, LPCTSTR)'
|
|
72
|
+
|
|
73
|
+
# args: hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
|
|
74
|
+
# dwfileOffsetLow, dwNumberOfBytesToMap
|
|
75
|
+
extern 'LPVOID MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, DWORD)'
|
|
76
|
+
|
|
77
|
+
# args: lpBaseAddress
|
|
78
|
+
extern 'BOOL UnmapViewOfFile(LPCVOID)'
|
|
79
|
+
|
|
80
|
+
# args: hObject
|
|
81
|
+
extern 'BOOL CloseHandle(HANDLE)'
|
|
82
|
+
|
|
83
|
+
# args: hWnd, Msg, wParam, lParam, fuFlags, uTimeout, lpdwResult
|
|
84
|
+
extern 'LRESULT SendMessageTimeout(HWND, UINT, WPARAM, LPARAM, ' +
|
|
85
|
+
'UINT, UINT, PDWORD_PTR)'
|
|
86
|
+
if RUBY_VERSION < "1.9"
|
|
87
|
+
alias_method :FindWindow,:findWindow
|
|
88
|
+
module_function :FindWindow
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# This is the pseudo-socket implementation that mimics the interface of
|
|
93
|
+
# a socket, translating each request into a Windows messaging call to
|
|
94
|
+
# the pageant daemon. This allows pageant support to be implemented
|
|
95
|
+
# simply by replacing the socket factory used by the Agent class.
|
|
96
|
+
class Socket
|
|
97
|
+
|
|
98
|
+
private_class_method :new
|
|
99
|
+
|
|
100
|
+
# The factory method for creating a new Socket instance. The location
|
|
101
|
+
# parameter is ignored, and is only needed for compatibility with
|
|
102
|
+
# the general Socket interface.
|
|
103
|
+
def self.open(location=nil)
|
|
104
|
+
new
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Create a new instance that communicates with the running pageant
|
|
108
|
+
# instance. If no such instance is running, this will cause an error.
|
|
109
|
+
def initialize
|
|
110
|
+
@win = Win.FindWindow("Pageant", "Pageant")
|
|
111
|
+
|
|
112
|
+
if @win == 0
|
|
113
|
+
raise Net::SSH::Exception,
|
|
114
|
+
"pageant process not running"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
@res = nil
|
|
118
|
+
@pos = 0
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Forwards the data to #send_query, ignoring any arguments after
|
|
122
|
+
# the first. Returns 0.
|
|
123
|
+
def send(data, *args)
|
|
124
|
+
@res = send_query(data)
|
|
125
|
+
@pos = 0
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Packages the given query string and sends it to the pageant
|
|
129
|
+
# process via the Windows messaging subsystem. The result is
|
|
130
|
+
# cached, to be returned piece-wise when #read is called.
|
|
131
|
+
def send_query(query)
|
|
132
|
+
res = nil
|
|
133
|
+
filemap = 0
|
|
134
|
+
ptr = nil
|
|
135
|
+
id = DL::PtrData.malloc(DL.sizeof("L"))
|
|
136
|
+
|
|
137
|
+
mapname = "PageantRequest%08x\000" % Win.getCurrentThreadId()
|
|
138
|
+
filemap = Win.createFileMapping(Win::INVALID_HANDLE_VALUE,
|
|
139
|
+
Win::NULL,
|
|
140
|
+
Win::PAGE_READWRITE, 0,
|
|
141
|
+
AGENT_MAX_MSGLEN, mapname)
|
|
142
|
+
if filemap == 0
|
|
143
|
+
raise Net::SSH::Exception,
|
|
144
|
+
"Creation of file mapping failed"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
ptr = Win.mapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0,
|
|
148
|
+
AGENT_MAX_MSGLEN)
|
|
149
|
+
|
|
150
|
+
if ptr.nil? || ptr.null?
|
|
151
|
+
raise Net::SSH::Exception, "Mapping of file failed"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
ptr[0] = query
|
|
155
|
+
|
|
156
|
+
cds = [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
|
|
157
|
+
pack("LLp").to_ptr
|
|
158
|
+
succ = Win.sendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
|
|
159
|
+
cds, Win::SMTO_NORMAL, 5000, id)
|
|
160
|
+
|
|
161
|
+
if succ > 0
|
|
162
|
+
retlen = 4 + ptr.to_s(4).unpack("N")[0]
|
|
163
|
+
res = ptr.to_s(retlen)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
return res
|
|
167
|
+
ensure
|
|
168
|
+
Win.unmapViewOfFile(ptr) unless ptr.nil? || ptr.null?
|
|
169
|
+
Win.closeHandle(filemap) if filemap != 0
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Conceptually close the socket. This doesn't really do anthing
|
|
173
|
+
# significant, but merely complies with the Socket interface.
|
|
174
|
+
def close
|
|
175
|
+
@res = nil
|
|
176
|
+
@pos = 0
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Conceptually asks if the socket is closed. As with #close,
|
|
180
|
+
# this doesn't really do anything significant, but merely
|
|
181
|
+
# complies with the Socket interface.
|
|
182
|
+
def closed?
|
|
183
|
+
@res.nil? && @pos.zero?
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Reads +n+ bytes from the cached result of the last query. If +n+
|
|
187
|
+
# is +nil+, returns all remaining data from the last query.
|
|
188
|
+
def read(n = nil)
|
|
189
|
+
return nil unless @res
|
|
190
|
+
if n.nil?
|
|
191
|
+
start, @pos = @pos, @res.size
|
|
192
|
+
return @res[start..-1]
|
|
193
|
+
else
|
|
194
|
+
start, @pos = @pos, @pos + n
|
|
195
|
+
return @res[start, n]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Socket changes for Ruby 1.9
|
|
202
|
+
# Functionality is the same as Ruby 1.8 but it includes the new calls to
|
|
203
|
+
# the DL module as well as other pointer transformations
|
|
204
|
+
class Socket19 < Socket
|
|
205
|
+
# Packages the given query string and sends it to the pageant
|
|
206
|
+
# process via the Windows messaging subsystem. The result is
|
|
207
|
+
# cached, to be returned piece-wise when #read is called.
|
|
208
|
+
def send_query(query)
|
|
209
|
+
res = nil
|
|
210
|
+
filemap = 0
|
|
211
|
+
ptr = nil
|
|
212
|
+
id = DL.malloc(DL::SIZEOF_LONG)
|
|
213
|
+
|
|
214
|
+
mapname = "PageantRequest%08x\000" % Win.GetCurrentThreadId()
|
|
215
|
+
|
|
216
|
+
filemap = Win.CreateFileMapping(Win::INVALID_HANDLE_VALUE,
|
|
217
|
+
Win::NULL,
|
|
218
|
+
Win::PAGE_READWRITE, 0,
|
|
219
|
+
AGENT_MAX_MSGLEN, mapname)
|
|
220
|
+
|
|
221
|
+
if filemap == 0 || filemap == Win::INVALID_HANDLE_VALUE
|
|
222
|
+
raise Net::SSH::Exception,
|
|
223
|
+
"Creation of file mapping failed"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
ptr = Win.MapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0,
|
|
227
|
+
0)
|
|
228
|
+
|
|
229
|
+
if ptr.nil? || ptr.null?
|
|
230
|
+
raise Net::SSH::Exception, "Mapping of file failed"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
DL::CPtr.new(ptr)[0,query.size]=query
|
|
234
|
+
|
|
235
|
+
cds = DL::CPtr.to_ptr [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
|
|
236
|
+
pack("LLp")
|
|
237
|
+
succ = Win.SendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
|
|
238
|
+
cds, Win::SMTO_NORMAL, 5000, id)
|
|
239
|
+
|
|
240
|
+
if succ > 0
|
|
241
|
+
retlen = 4 + ptr.to_s(4).unpack("N")[0]
|
|
242
|
+
res = ptr.to_s(retlen)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
return res
|
|
246
|
+
ensure
|
|
247
|
+
Win.UnmapViewOfFile(ptr) unless ptr.nil? || ptr.null?
|
|
248
|
+
Win.CloseHandle(filemap) if filemap != 0
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Selects which socket to use depending on the ruby version
|
|
253
|
+
# This is needed due changes in the DL module.
|
|
254
|
+
def self.socket_factory
|
|
255
|
+
if RUBY_VERSION < "1.9"
|
|
256
|
+
Socket
|
|
257
|
+
else
|
|
258
|
+
Socket19
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
end; end; end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
require 'net/ssh/loggable'
|
|
2
|
+
require 'net/ssh/transport/constants'
|
|
3
|
+
require 'net/ssh/authentication/constants'
|
|
4
|
+
require 'net/ssh/authentication/key_manager'
|
|
5
|
+
require 'net/ssh/authentication/methods/publickey'
|
|
6
|
+
require 'net/ssh/authentication/methods/hostbased'
|
|
7
|
+
require 'net/ssh/authentication/methods/password'
|
|
8
|
+
require 'net/ssh/authentication/methods/keyboard_interactive'
|
|
9
|
+
|
|
10
|
+
module Net; module SSH; module Authentication
|
|
11
|
+
|
|
12
|
+
# Raised if the current authentication method is not allowed
|
|
13
|
+
class DisallowedMethod < Net::SSH::Exception
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Represents an authentication session. It manages the authentication of
|
|
17
|
+
# a user over an established connection (the "transport" object, see
|
|
18
|
+
# Net::SSH::Transport::Session).
|
|
19
|
+
#
|
|
20
|
+
# The use of an authentication session to manage user authentication is
|
|
21
|
+
# internal to Net::SSH (specifically Net::SSH.start). Consumers of the
|
|
22
|
+
# Net::SSH library will never need to access this class directly.
|
|
23
|
+
class Session
|
|
24
|
+
include Transport::Constants, Constants, Loggable
|
|
25
|
+
|
|
26
|
+
# transport layer abstraction
|
|
27
|
+
attr_reader :transport
|
|
28
|
+
|
|
29
|
+
# the list of authentication methods to try
|
|
30
|
+
attr_reader :auth_methods
|
|
31
|
+
|
|
32
|
+
# the list of authentication methods that are allowed
|
|
33
|
+
attr_reader :allowed_auth_methods
|
|
34
|
+
|
|
35
|
+
# a hash of options, given at construction time
|
|
36
|
+
attr_reader :options
|
|
37
|
+
|
|
38
|
+
# Instantiates a new Authentication::Session object over the given
|
|
39
|
+
# transport layer abstraction.
|
|
40
|
+
def initialize(transport, options={})
|
|
41
|
+
self.logger = transport.logger
|
|
42
|
+
@transport = transport
|
|
43
|
+
|
|
44
|
+
@auth_methods = options[:auth_methods] || %w(publickey hostbased password keyboard-interactive)
|
|
45
|
+
@options = options
|
|
46
|
+
|
|
47
|
+
@allowed_auth_methods = @auth_methods
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Attempts to authenticate the given user, in preparation for the next
|
|
51
|
+
# service request. Returns true if an authentication method succeeds in
|
|
52
|
+
# authenticating the user, and false otherwise.
|
|
53
|
+
def authenticate(next_service, username, password=nil)
|
|
54
|
+
debug { "beginning authentication of `#{username}'" }
|
|
55
|
+
|
|
56
|
+
transport.send_message(transport.service_request("ssh-userauth"))
|
|
57
|
+
message = expect_message(SERVICE_ACCEPT)
|
|
58
|
+
|
|
59
|
+
key_manager = KeyManager.new(logger, options)
|
|
60
|
+
keys.each { |key| key_manager.add(key) } unless keys.empty?
|
|
61
|
+
key_data.each { |key2| key_manager.add_key_data(key2) } unless key_data.empty?
|
|
62
|
+
|
|
63
|
+
attempted = []
|
|
64
|
+
|
|
65
|
+
@auth_methods.each do |name|
|
|
66
|
+
begin
|
|
67
|
+
next unless @allowed_auth_methods.include?(name)
|
|
68
|
+
attempted << name
|
|
69
|
+
|
|
70
|
+
debug { "trying #{name}" }
|
|
71
|
+
begin
|
|
72
|
+
method = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join).new(self, :key_manager => key_manager)
|
|
73
|
+
rescue NameError => ne
|
|
74
|
+
debug{"Mechanism #{name} was requested, but isn't a known type. Ignoring it."}
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
return true if method.authenticate(next_service, username, password)
|
|
79
|
+
rescue Net::SSH::Authentication::DisallowedMethod
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
error { "all authorization methods failed (tried #{attempted.join(', ')})" }
|
|
84
|
+
return false
|
|
85
|
+
ensure
|
|
86
|
+
key_manager.finish if key_manager
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Blocks until a packet is received. It silently handles USERAUTH_BANNER
|
|
90
|
+
# packets, and will raise an error if any packet is received that is not
|
|
91
|
+
# valid during user authentication.
|
|
92
|
+
def next_message
|
|
93
|
+
loop do
|
|
94
|
+
packet = transport.next_message
|
|
95
|
+
|
|
96
|
+
case packet.type
|
|
97
|
+
when USERAUTH_BANNER
|
|
98
|
+
info { packet[:message] }
|
|
99
|
+
# TODO add a hook for people to retrieve the banner when it is sent
|
|
100
|
+
|
|
101
|
+
when USERAUTH_FAILURE
|
|
102
|
+
@allowed_auth_methods = packet[:authentications].split(/,/)
|
|
103
|
+
debug { "allowed methods: #{packet[:authentications]}" }
|
|
104
|
+
return packet
|
|
105
|
+
|
|
106
|
+
when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT
|
|
107
|
+
return packet
|
|
108
|
+
|
|
109
|
+
when USERAUTH_SUCCESS
|
|
110
|
+
transport.hint :authenticated
|
|
111
|
+
return packet
|
|
112
|
+
|
|
113
|
+
else
|
|
114
|
+
raise Net::SSH::Exception, "unexpected message #{packet.type} (#{packet})"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Blocks until a packet is received, and returns it if it is of the given
|
|
120
|
+
# type. If it is not, an exception is raised.
|
|
121
|
+
def expect_message(type)
|
|
122
|
+
message = next_message
|
|
123
|
+
unless message.type == type
|
|
124
|
+
raise Net::SSH::Exception, "expected #{type}, got #{message.type} (#{message})"
|
|
125
|
+
end
|
|
126
|
+
message
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
# Returns an array of paths to the key files that should be used when
|
|
132
|
+
# attempting any key-based authentication mechanism.
|
|
133
|
+
def keys
|
|
134
|
+
Array(
|
|
135
|
+
options[:keys] ||
|
|
136
|
+
%w(~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa)
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Returns an array of the key data that should be used when
|
|
141
|
+
# attempting any key-based authentication mechanism.
|
|
142
|
+
def key_data
|
|
143
|
+
Array(options[:key_data])
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end; end; end
|