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
@@ -0,0 +1,60 @@
1
+ require 'net/ssh/buffer'
2
+ require 'net/ssh/errors'
3
+ require 'net/ssh/loggable'
4
+ require 'net/ssh/authentication/constants'
5
+
6
+ module Net; module SSH; module Authentication; module Methods
7
+
8
+ # The base class of all user authentication methods. It provides a few
9
+ # bits of common functionality.
10
+ class Abstract
11
+ include Constants, Loggable
12
+
13
+ # The authentication session object
14
+ attr_reader :session
15
+
16
+ # The key manager object. Not all authentication methods will require
17
+ # this.
18
+ attr_reader :key_manager
19
+
20
+ # Instantiates a new authentication method.
21
+ def initialize(session, options={})
22
+ @session = session
23
+ @key_manager = options[:key_manager]
24
+ @options = options
25
+ self.logger = session.logger
26
+ end
27
+
28
+ # Returns the session-id, as generated during the first key exchange of
29
+ # an SSH connection.
30
+ def session_id
31
+ session.transport.algorithms.session_id
32
+ end
33
+
34
+ # Sends a message via the underlying transport layer abstraction. This
35
+ # will block until the message is completely sent.
36
+ def send_message(msg)
37
+ session.transport.send_message(msg)
38
+ end
39
+
40
+ # Creates a new USERAUTH_REQUEST packet. The extra arguments on the end
41
+ # must be either boolean values or strings, and are tacked onto the end
42
+ # of the packet. The new packet is returned, ready for sending.
43
+ def userauth_request(username, next_service, auth_method, *others)
44
+ buffer = Net::SSH::Buffer.from(:byte, USERAUTH_REQUEST,
45
+ :string, username, :string, next_service, :string, auth_method)
46
+
47
+ others.each do |value|
48
+ case value
49
+ when true, false then buffer.write_bool(value)
50
+ when String then buffer.write_string(value)
51
+ else raise ArgumentError, "don't know how to write #{value.inspect}"
52
+ end
53
+ end
54
+
55
+ buffer
56
+ end
57
+
58
+ end
59
+
60
+ end; end; end; end
@@ -0,0 +1,71 @@
1
+ require 'net/ssh/authentication/methods/abstract'
2
+
3
+ module Net
4
+ module SSH
5
+ module Authentication
6
+ module Methods
7
+
8
+ # Implements the host-based SSH authentication method.
9
+ class Hostbased < Abstract
10
+ include Constants
11
+
12
+ # Attempts to perform host-based authorization of the user by trying
13
+ # all known keys.
14
+ def authenticate(next_service, username, password=nil)
15
+ return false unless key_manager
16
+
17
+ key_manager.each_identity do |identity|
18
+ return true if authenticate_with(identity, next_service,
19
+ username, key_manager)
20
+ end
21
+
22
+ return false
23
+ end
24
+
25
+ private
26
+
27
+ # Returns the hostname as reported by the underlying socket.
28
+ def hostname
29
+ session.transport.socket.client_name
30
+ end
31
+
32
+ # Attempts to perform host-based authentication of the user, using
33
+ # the given host identity (key).
34
+ def authenticate_with(identity, next_service, username, key_manager)
35
+ debug { "trying hostbased (#{identity.fingerprint})" }
36
+ client_username = ENV['USER'] || username
37
+
38
+ req = build_request(identity, next_service, username, "#{hostname}.", client_username)
39
+ sig_data = Buffer.from(:string, session_id, :raw, req)
40
+
41
+ sig = key_manager.sign(identity, sig_data.to_s)
42
+
43
+ message = Buffer.from(:raw, req, :string, sig)
44
+
45
+ send_message(message)
46
+ message = session.next_message
47
+
48
+ case message.type
49
+ when USERAUTH_SUCCESS
50
+ info { "hostbased succeeded (#{identity.fingerprint})" }
51
+ return true
52
+ when USERAUTH_FAILURE
53
+ info { "hostbased failed (#{identity.fingerprint})" }
54
+ return false
55
+ else
56
+ raise Net::SSH::Exception, "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
57
+ end
58
+ end
59
+
60
+ # Build the "core" hostbased request string.
61
+ def build_request(identity, next_service, username, hostname, client_username)
62
+ userauth_request(username, next_service, "hostbased", identity.ssh_type,
63
+ Buffer.from(:key, identity).to_s, hostname, client_username).to_s
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,66 @@
1
+ require 'net/ssh/prompt'
2
+ require 'net/ssh/authentication/methods/abstract'
3
+
4
+ module Net
5
+ module SSH
6
+ module Authentication
7
+ module Methods
8
+
9
+ # Implements the "keyboard-interactive" SSH authentication method.
10
+ class KeyboardInteractive < Abstract
11
+ include Prompt
12
+
13
+ USERAUTH_INFO_REQUEST = 60
14
+ USERAUTH_INFO_RESPONSE = 61
15
+
16
+ # Attempt to authenticate the given user for the given service.
17
+ def authenticate(next_service, username, password=nil)
18
+ debug { "trying keyboard-interactive" }
19
+ send_message(userauth_request(username, next_service, "keyboard-interactive", "", ""))
20
+
21
+ loop do
22
+ message = session.next_message
23
+
24
+ case message.type
25
+ when USERAUTH_SUCCESS
26
+ debug { "keyboard-interactive succeeded" }
27
+ return true
28
+ when USERAUTH_FAILURE
29
+ debug { "keyboard-interactive failed" }
30
+ return false
31
+ when USERAUTH_INFO_REQUEST
32
+ name = message.read_string
33
+ instruction = message.read_string
34
+ debug { "keyboard-interactive info request" }
35
+
36
+ unless password
37
+ puts(name) unless name.empty?
38
+ puts(instruction) unless instruction.empty?
39
+ end
40
+
41
+ lang_tag = message.read_string
42
+ responses =[]
43
+
44
+ message.read_long.times do
45
+ text = message.read_string
46
+ echo = message.read_bool
47
+ responses << (password || prompt(text, echo))
48
+ end
49
+
50
+ # if the password failed the first time around, don't try
51
+ # and use it on subsequent requests.
52
+ password = nil
53
+
54
+ msg = Buffer.from(:byte, USERAUTH_INFO_RESPONSE, :long, responses.length, :string, responses)
55
+ send_message(msg)
56
+ else
57
+ raise Net::SSH::Exception, "unexpected reply in keyboard interactive: #{message.type} (#{message.inspect})"
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,39 @@
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
+ return false
27
+ when USERAUTH_PASSWD_CHANGEREQ
28
+ debug { "password change request received, failing" }
29
+ return false
30
+ else
31
+ raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,92 @@
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
+ return false
74
+ else
75
+ raise Net::SSH::Exception,
76
+ "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
77
+ end
78
+
79
+ when USERAUTH_FAILURE
80
+ return false
81
+
82
+ else
83
+ raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,183 @@
1
+ require 'dl/import'
2
+ require 'dl/struct'
3
+
4
+ require 'net/ssh/errors'
5
+
6
+ module Net; module SSH; module Authentication
7
+
8
+ # This module encapsulates the implementation of a socket factory that
9
+ # uses the PuTTY "pageant" utility to obtain information about SSH
10
+ # identities.
11
+ #
12
+ # This code is a slightly modified version of the original implementation
13
+ # by Guillaume Mar�ais (guillaume.marcais@free.fr). It is used and
14
+ # relicensed by permission.
15
+ module Pageant
16
+
17
+ # From Putty pageant.c
18
+ AGENT_MAX_MSGLEN = 8192
19
+ AGENT_COPYDATA_ID = 0x804e50ba
20
+
21
+ # The definition of the Windows methods and data structures used in
22
+ # communicating with the pageant process.
23
+ module Win
24
+ extend DL::Importable
25
+
26
+ dlload 'user32'
27
+ dlload 'kernel32'
28
+
29
+ typealias("LPCTSTR", "char *") # From winnt.h
30
+ typealias("LPVOID", "void *") # From winnt.h
31
+ typealias("LPCVOID", "const void *") # From windef.h
32
+ typealias("LRESULT", "long") # From windef.h
33
+ typealias("WPARAM", "unsigned int *") # From windef.h
34
+ typealias("LPARAM", "long *") # From windef.h
35
+ typealias("PDWORD_PTR", "long *") # From basetsd.h
36
+
37
+ # From winbase.h, winnt.h
38
+ INVALID_HANDLE_VALUE = -1
39
+ NULL = nil
40
+ PAGE_READWRITE = 0x0004
41
+ FILE_MAP_WRITE = 2
42
+ WM_COPYDATA = 74
43
+
44
+ SMTO_NORMAL = 0 # From winuser.h
45
+
46
+ # args: lpClassName, lpWindowName
47
+ extern 'HWND FindWindow(LPCTSTR, LPCTSTR)'
48
+
49
+ # args: none
50
+ extern 'DWORD GetCurrentThreadId()'
51
+
52
+ # args: hFile, (ignored), flProtect, dwMaximumSizeHigh,
53
+ # dwMaximumSizeLow, lpName
54
+ extern 'HANDLE CreateFileMapping(HANDLE, void *, DWORD, DWORD, ' +
55
+ 'DWORD, LPCTSTR)'
56
+
57
+ # args: hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
58
+ # dwfileOffsetLow, dwNumberOfBytesToMap
59
+ extern 'LPVOID MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, DWORD)'
60
+
61
+ # args: lpBaseAddress
62
+ extern 'BOOL UnmapViewOfFile(LPCVOID)'
63
+
64
+ # args: hObject
65
+ extern 'BOOL CloseHandle(HANDLE)'
66
+
67
+ # args: hWnd, Msg, wParam, lParam, fuFlags, uTimeout, lpdwResult
68
+ extern 'LRESULT SendMessageTimeout(HWND, UINT, WPARAM, LPARAM, ' +
69
+ 'UINT, UINT, PDWORD_PTR)'
70
+ end
71
+
72
+ # This is the pseudo-socket implementation that mimics the interface of
73
+ # a socket, translating each request into a Windows messaging call to
74
+ # the pageant daemon. This allows pageant support to be implemented
75
+ # simply by replacing the socket factory used by the Agent class.
76
+ class Socket
77
+
78
+ private_class_method :new
79
+
80
+ # The factory method for creating a new Socket instance. The location
81
+ # parameter is ignored, and is only needed for compatibility with
82
+ # the general Socket interface.
83
+ def self.open(location=nil)
84
+ new
85
+ end
86
+
87
+ # Create a new instance that communicates with the running pageant
88
+ # instance. If no such instance is running, this will cause an error.
89
+ def initialize
90
+ @win = Win.findWindow("Pageant", "Pageant")
91
+
92
+ if @win == 0
93
+ raise Net::SSH::Exception,
94
+ "pageant process not running"
95
+ end
96
+
97
+ @res = nil
98
+ @pos = 0
99
+ end
100
+
101
+ # Forwards the data to #send_query, ignoring any arguments after
102
+ # the first. Returns 0.
103
+ def send(data, *args)
104
+ @res = send_query(data)
105
+ @pos = 0
106
+ end
107
+
108
+ # Packages the given query string and sends it to the pageant
109
+ # process via the Windows messaging subsystem. The result is
110
+ # cached, to be returned piece-wise when #read is called.
111
+ def send_query(query)
112
+ res = nil
113
+ filemap = 0
114
+ ptr = nil
115
+ id = DL::PtrData.malloc(DL.sizeof("L"))
116
+
117
+ mapname = "PageantRequest%08x\000" % Win.getCurrentThreadId()
118
+ filemap = Win.createFileMapping(Win::INVALID_HANDLE_VALUE,
119
+ Win::NULL,
120
+ Win::PAGE_READWRITE, 0,
121
+ AGENT_MAX_MSGLEN, mapname)
122
+ if filemap == 0
123
+ raise Net::SSH::Exception,
124
+ "Creation of file mapping failed"
125
+ end
126
+
127
+ ptr = Win.mapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0,
128
+ AGENT_MAX_MSGLEN)
129
+
130
+ if ptr.nil? || ptr.null?
131
+ raise Net::SSH::Exception, "Mapping of file failed"
132
+ end
133
+
134
+ ptr[0] = query
135
+
136
+ cds = [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
137
+ pack("LLp").to_ptr
138
+ succ = Win.sendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
139
+ cds, Win::SMTO_NORMAL, 5000, id)
140
+
141
+ if succ > 0
142
+ retlen = 4 + ptr.to_s(4).unpack("N")[0]
143
+ res = ptr.to_s(retlen)
144
+ end
145
+
146
+ return res
147
+ ensure
148
+ Win.unmapViewOfFile(ptr) unless ptr.nil? || ptr.null?
149
+ Win.closeHandle(filemap) if filemap != 0
150
+ end
151
+
152
+ # Conceptually close the socket. This doesn't really do anthing
153
+ # significant, but merely complies with the Socket interface.
154
+ def close
155
+ @res = nil
156
+ @pos = 0
157
+ end
158
+
159
+ # Conceptually asks if the socket is closed. As with #close,
160
+ # this doesn't really do anything significant, but merely
161
+ # complies with the Socket interface.
162
+ def closed?
163
+ @res.nil? && @pos.zero?
164
+ end
165
+
166
+ # Reads +n+ bytes from the cached result of the last query. If +n+
167
+ # is +nil+, returns all remaining data from the last query.
168
+ def read(n = nil)
169
+ return nil unless @res
170
+ if n.nil?
171
+ start, @pos = @pos, @res.size
172
+ return @res[start..-1]
173
+ else
174
+ start, @pos = @pos, @pos + n
175
+ return @res[start, n]
176
+ end
177
+ end
178
+
179
+ end
180
+
181
+ end
182
+
183
+ end; end; end