orthrus-ssh 0.5.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/.autotest +23 -0
- data/.gemtest +0 -0
- data/History.txt +6 -0
- data/Manifest.txt +31 -0
- data/README.txt +61 -0
- data/Rakefile +12 -0
- data/bin/orthrus +16 -0
- data/lib/orthrus.rb +2 -0
- data/lib/orthrus/key.rb +12 -0
- data/lib/orthrus/key_holder.rb +15 -0
- data/lib/orthrus/ssh.rb +50 -0
- data/lib/orthrus/ssh/agent.rb +176 -0
- data/lib/orthrus/ssh/buffer.rb +343 -0
- data/lib/orthrus/ssh/dsa.rb +88 -0
- data/lib/orthrus/ssh/http_agent.rb +83 -0
- data/lib/orthrus/ssh/key.rb +50 -0
- data/lib/orthrus/ssh/public_key_set.rb +30 -0
- data/lib/orthrus/ssh/rack_app.rb +62 -0
- data/lib/orthrus/ssh/rsa.rb +51 -0
- data/lib/orthrus/ssh/utils.rb +26 -0
- data/test/data/authorized_keys +2 -0
- data/test/data/id_dsa +12 -0
- data/test/data/id_dsa.pub +1 -0
- data/test/data/id_rsa +27 -0
- data/test/data/id_rsa.pub +1 -0
- data/test/sessions.rb +28 -0
- data/test/test_orthrus_ssh_agent.rb +31 -0
- data/test/test_orthrus_ssh_dsa.rb +46 -0
- data/test/test_orthrus_ssh_http_agent.rb +71 -0
- data/test/test_orthrus_ssh_public_key_set.rb +29 -0
- data/test/test_orthrus_ssh_rackapp.rb +84 -0
- data/test/test_orthrus_ssh_rsa.rb +46 -0
- metadata +149 -0
data/.autotest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.extra_files << "../some/external/dependency.rb"
|
7
|
+
#
|
8
|
+
# at.libs << ":../some/external"
|
9
|
+
#
|
10
|
+
# at.add_exception 'vendor'
|
11
|
+
#
|
12
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
+
# at.files_matching(/test_.*rb$/)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# %w(TestA TestB).each do |klass|
|
17
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Autotest.add_hook :run_command do |at|
|
22
|
+
# system "rake build"
|
23
|
+
# end
|
data/.gemtest
ADDED
File without changes
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
.autotest
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
bin/orthrus
|
7
|
+
lib/orthrus.rb
|
8
|
+
lib/orthrus/key.rb
|
9
|
+
lib/orthrus/key_holder.rb
|
10
|
+
lib/orthrus/ssh.rb
|
11
|
+
lib/orthrus/ssh/agent.rb
|
12
|
+
lib/orthrus/ssh/buffer.rb
|
13
|
+
lib/orthrus/ssh/dsa.rb
|
14
|
+
lib/orthrus/ssh/http_agent.rb
|
15
|
+
lib/orthrus/ssh/key.rb
|
16
|
+
lib/orthrus/ssh/public_key_set.rb
|
17
|
+
lib/orthrus/ssh/rack_app.rb
|
18
|
+
lib/orthrus/ssh/rsa.rb
|
19
|
+
lib/orthrus/ssh/utils.rb
|
20
|
+
test/data/authorized_keys
|
21
|
+
test/data/id_dsa
|
22
|
+
test/data/id_dsa.pub
|
23
|
+
test/data/id_rsa
|
24
|
+
test/data/id_rsa.pub
|
25
|
+
test/sessions.rb
|
26
|
+
test/test_orthrus_ssh_agent.rb
|
27
|
+
test/test_orthrus_ssh_dsa.rb
|
28
|
+
test/test_orthrus_ssh_http_agent.rb
|
29
|
+
test/test_orthrus_ssh_public_key_set.rb
|
30
|
+
test/test_orthrus_ssh_rackapp.rb
|
31
|
+
test/test_orthrus_ssh_rsa.rb
|
data/README.txt
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
= orthrus-ssh
|
2
|
+
|
3
|
+
* http://github.com/evanphx/orthrus
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
A user authentication system built on SSH's key
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* Uses ssh keys to authenticate users
|
12
|
+
* Can use the ssh-agent for added security
|
13
|
+
* Includes HTTPAgent and sample RackApp
|
14
|
+
|
15
|
+
== SYNOPSIS:
|
16
|
+
|
17
|
+
agent = Orthrus::SSH::HTTPAgent.new url
|
18
|
+
h.start user
|
19
|
+
p h.access_token
|
20
|
+
|
21
|
+
== REQUIREMENTS:
|
22
|
+
|
23
|
+
* OpenSSL
|
24
|
+
|
25
|
+
== INSTALL:
|
26
|
+
|
27
|
+
* gem install orthrus
|
28
|
+
|
29
|
+
== DEVELOPERS:
|
30
|
+
|
31
|
+
After checking out the source, run:
|
32
|
+
|
33
|
+
$ rake newb
|
34
|
+
|
35
|
+
This task will install any missing dependencies, run the tests/specs,
|
36
|
+
and generate the RDoc.
|
37
|
+
|
38
|
+
== LICENSE:
|
39
|
+
|
40
|
+
(The MIT License)
|
41
|
+
|
42
|
+
Copyright (c) 2012 FIX
|
43
|
+
|
44
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
45
|
+
a copy of this software and associated documentation files (the
|
46
|
+
'Software'), to deal in the Software without restriction, including
|
47
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
48
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
49
|
+
permit persons to whom the Software is furnished to do so, subject to
|
50
|
+
the following conditions:
|
51
|
+
|
52
|
+
The above copyright notice and this permission notice shall be
|
53
|
+
included in all copies or substantial portions of the Software.
|
54
|
+
|
55
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
56
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
57
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
58
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
59
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
60
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
61
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/bin/orthrus
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
case cmd = ARGV.shift
|
4
|
+
when "ids"
|
5
|
+
require 'orthrus/ssh/agent'
|
6
|
+
|
7
|
+
agent = Orthrus::SSH::Agent.connect
|
8
|
+
|
9
|
+
puts "Agent identities:"
|
10
|
+
agent.identities.each do |i|
|
11
|
+
puts "#{i.type}: #{i.fingerprint}"
|
12
|
+
end
|
13
|
+
|
14
|
+
else
|
15
|
+
abort "Unsupported option - #{cmd}"
|
16
|
+
end
|
data/lib/orthrus.rb
ADDED
data/lib/orthrus/key.rb
ADDED
data/lib/orthrus/ssh.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Orthrus; end
|
4
|
+
|
5
|
+
require 'orthrus/ssh/rsa'
|
6
|
+
require 'orthrus/ssh/dsa'
|
7
|
+
require 'orthrus/ssh/utils'
|
8
|
+
|
9
|
+
module Orthrus::SSH
|
10
|
+
VERSION = '0.5.0'
|
11
|
+
|
12
|
+
def self.load_private(path)
|
13
|
+
data = File.read(path)
|
14
|
+
if data.index("-----BEGIN RSA PRIVATE KEY-----") == 0
|
15
|
+
k = OpenSSL::PKey::RSA.new data
|
16
|
+
return RSAPrivateKey.new k
|
17
|
+
elsif data.index("-----BEGIN DSA PRIVATE KEY-----") == 0
|
18
|
+
k = OpenSSL::PKey::DSA.new data
|
19
|
+
return DSAPrivateKey.new k
|
20
|
+
else
|
21
|
+
raise "Unknown key type in '#{path}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.parse_public(data)
|
26
|
+
type, key, comment = data.split " ", 3
|
27
|
+
|
28
|
+
case type
|
29
|
+
when "ssh-rsa"
|
30
|
+
RSAPublicKey.parse key
|
31
|
+
when "ssh-dss"
|
32
|
+
DSAPublicKey.parse key
|
33
|
+
else
|
34
|
+
raise "Unknown key type - #{type}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.load_public(path)
|
39
|
+
parse_public File.read(path)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
# For 1.8/1.9 compat
|
45
|
+
class String
|
46
|
+
unless method_defined? :getbytpe
|
47
|
+
alias_method :getbyte, :[]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'orthrus/ssh'
|
2
|
+
require 'orthrus/ssh/buffer'
|
3
|
+
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
# Adapted from agent.rb in net-ssh
|
7
|
+
|
8
|
+
module Orthrus::SSH
|
9
|
+
# A trivial exception class for representing agent-specific errors.
|
10
|
+
class AgentError < StandardError; end
|
11
|
+
|
12
|
+
# An exception for indicating that the SSH agent is not available.
|
13
|
+
class AgentNotAvailable < AgentError; end
|
14
|
+
|
15
|
+
# This class implements a simple client for the ssh-agent protocol. It
|
16
|
+
# does not implement any specific protocol, but instead copies the
|
17
|
+
# behavior of the ssh-agent functions in the OpenSSH library (3.8).
|
18
|
+
#
|
19
|
+
# This means that although it behaves like a SSH1 client, it also has
|
20
|
+
# some SSH2 functionality (like signing data).
|
21
|
+
class Agent
|
22
|
+
SSH2_AGENT_REQUEST_VERSION = 1
|
23
|
+
SSH2_AGENT_REQUEST_IDENTITIES = 11
|
24
|
+
SSH2_AGENT_IDENTITIES_ANSWER = 12
|
25
|
+
SSH2_AGENT_SIGN_REQUEST = 13
|
26
|
+
SSH2_AGENT_SIGN_RESPONSE = 14
|
27
|
+
SSH2_AGENT_FAILURE = 30
|
28
|
+
SSH2_AGENT_VERSION_RESPONSE = 103
|
29
|
+
|
30
|
+
SSH_COM_AGENT2_FAILURE = 102
|
31
|
+
|
32
|
+
SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
|
33
|
+
SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
|
34
|
+
SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
|
35
|
+
SSH_AGENT_FAILURE = 5
|
36
|
+
|
37
|
+
# The underlying socket being used to communicate with the SSH agent.
|
38
|
+
attr_reader :socket
|
39
|
+
|
40
|
+
# Instantiates a new agent object, connects to a running SSH agent,
|
41
|
+
# negotiates the agent protocol version, and returns the agent object.
|
42
|
+
def self.connect
|
43
|
+
agent = new
|
44
|
+
agent.connect!
|
45
|
+
agent.negotiate!
|
46
|
+
agent
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.available?
|
50
|
+
ENV.key?("SSH_AUTH_SOCK") && File.exists?(ENV['SSH_AUTH_SOCK'])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Creates a new Agent object.
|
54
|
+
def initialize
|
55
|
+
@socket = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Connect to the agent process using the socket factory and socket name
|
59
|
+
# given by the attribute writers. If the agent on the other end of the
|
60
|
+
# socket reports that it is an SSH2-compatible agent, this will fail
|
61
|
+
# (it only supports the ssh-agent distributed by OpenSSH).
|
62
|
+
def connect!
|
63
|
+
begin
|
64
|
+
@socket = UNIXSocket.open(ENV['SSH_AUTH_SOCK'])
|
65
|
+
rescue
|
66
|
+
raise AgentNotAvailable, $!.message
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
ID = "SSH-2.0-Ruby/Orthrus #{RUBY_PLATFORM}"
|
71
|
+
|
72
|
+
# Attempts to negotiate the SSH agent protocol version. Raises an error
|
73
|
+
# if the version could not be negotiated successfully.
|
74
|
+
def negotiate!
|
75
|
+
# determine what type of agent we're communicating with
|
76
|
+
type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, ID)
|
77
|
+
|
78
|
+
if type == SSH2_AGENT_VERSION_RESPONSE
|
79
|
+
raise NotImplementedError, "SSH2 agents are not yet supported"
|
80
|
+
elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
|
81
|
+
raise AgentError, "unknown response from agent: #{type}, #{body.to_s.inspect}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return an array of all identities (public keys) known to the agent.
|
86
|
+
# Each key returned is augmented with a +comment+ property which is set
|
87
|
+
# to the comment returned by the agent for that key.
|
88
|
+
def identities
|
89
|
+
type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
|
90
|
+
raise AgentError, "could not get identity count" if agent_failed(type)
|
91
|
+
raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
|
92
|
+
|
93
|
+
identities = []
|
94
|
+
body.read_long.times do
|
95
|
+
key = Buffer.new(body.read_string).read_key
|
96
|
+
case key
|
97
|
+
when OpenSSL::PKey::RSA
|
98
|
+
key = RSAPublicKey.new key
|
99
|
+
when OpenSSL::PKey::DSA
|
100
|
+
key = DSAPublicKey.new key
|
101
|
+
else
|
102
|
+
raise AgentError, "Unknown key type - #{key.class}"
|
103
|
+
end
|
104
|
+
|
105
|
+
key.comment = body.read_string
|
106
|
+
identities.push key
|
107
|
+
end
|
108
|
+
|
109
|
+
return identities
|
110
|
+
end
|
111
|
+
|
112
|
+
# Closes this socket. This agent reference is no longer able to
|
113
|
+
# query the agent.
|
114
|
+
def close
|
115
|
+
@socket.close
|
116
|
+
end
|
117
|
+
|
118
|
+
# Using the agent and the given public key, sign the given data. The
|
119
|
+
# signature is returned in SSH2 format.
|
120
|
+
def sign(key, data)
|
121
|
+
type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST,
|
122
|
+
:string, Buffer.from(:key, key),
|
123
|
+
:string, data,
|
124
|
+
:long, 0)
|
125
|
+
|
126
|
+
if agent_failed(type)
|
127
|
+
raise AgentError, "agent could not sign data with requested identity"
|
128
|
+
elsif type != SSH2_AGENT_SIGN_RESPONSE
|
129
|
+
raise AgentError, "bad authentication response #{type}"
|
130
|
+
end
|
131
|
+
|
132
|
+
b = Buffer.new reply.read_string
|
133
|
+
[b.read_string, b.read_string]
|
134
|
+
end
|
135
|
+
|
136
|
+
def hexsign(key, data)
|
137
|
+
type, sig = sign key, data
|
138
|
+
|
139
|
+
[type, [sig].pack("m").gsub("\n","")]
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# Send a new packet of the given type, with the associated data.
|
145
|
+
def send_packet(type, *args)
|
146
|
+
buffer = Buffer.from(*args)
|
147
|
+
data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
|
148
|
+
@socket.send data, 0
|
149
|
+
end
|
150
|
+
|
151
|
+
# Read the next packet from the agent. This will return a two-part
|
152
|
+
# tuple consisting of the packet type, and the packet's body (which
|
153
|
+
# is returned as a Net::SSH::Buffer).
|
154
|
+
def read_packet
|
155
|
+
buffer = Buffer.new @socket.read(4)
|
156
|
+
buffer.append @socket.read(buffer.read_long)
|
157
|
+
type = buffer.read_byte
|
158
|
+
return type, buffer
|
159
|
+
end
|
160
|
+
|
161
|
+
# Send the given packet and return the subsequent reply from the agent.
|
162
|
+
# (See #send_packet and #read_packet).
|
163
|
+
def send_and_wait(type, *args)
|
164
|
+
send_packet(type, *args)
|
165
|
+
read_packet
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns +true+ if the parameter indicates a "failure" response from
|
169
|
+
# the agent, and +false+ otherwise.
|
170
|
+
def agent_failed(type)
|
171
|
+
type == SSH_AGENT_FAILURE ||
|
172
|
+
type == SSH2_AGENT_FAILURE ||
|
173
|
+
type == SSH_COM_AGENT2_FAILURE
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
require 'orthrus/ssh/utils'
|
4
|
+
|
5
|
+
# Adapted from buffer.rb in net-ssh
|
6
|
+
|
7
|
+
module Orthrus::SSH
|
8
|
+
|
9
|
+
# Net::SSH::Buffer is a flexible class for building and parsing binary
|
10
|
+
# data packets. It provides a stream-like interface for sequentially
|
11
|
+
# reading data items from the buffer, as well as a useful helper method
|
12
|
+
# for building binary packets given a signature.
|
13
|
+
#
|
14
|
+
# Writing to a buffer always appends to the end, regardless of where the
|
15
|
+
# read cursor is. Reading, on the other hand, always begins at the first
|
16
|
+
# byte of the buffer and increments the read cursor, with subsequent reads
|
17
|
+
# taking up where the last left off.
|
18
|
+
#
|
19
|
+
# As a consumer of the Net::SSH library, you will rarely come into contact
|
20
|
+
# with these buffer objects directly, but it could happen. Also, if you
|
21
|
+
# are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
|
22
|
+
# class can be quite handy.
|
23
|
+
class Buffer
|
24
|
+
# This is a convenience method for creating and populating a new buffer
|
25
|
+
# from a single command. The arguments must be even in length, with the
|
26
|
+
# first of each pair of arguments being a symbol naming the type of the
|
27
|
+
# data that follows. If the type is :raw, the value is written directly
|
28
|
+
# to the hash.
|
29
|
+
#
|
30
|
+
# b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
|
31
|
+
# #-> "\1\0\0\0\5hello\1\2\3\4"
|
32
|
+
#
|
33
|
+
# The supported data types are:
|
34
|
+
#
|
35
|
+
# * :raw => write the next value verbatim (#write)
|
36
|
+
# * :int64 => write an 8-byte integer (#write_int64)
|
37
|
+
# * :long => write a 4-byte integer (#write_long)
|
38
|
+
# * :byte => write a single byte (#write_byte)
|
39
|
+
# * :string => write a 4-byte length followed by character data (#write_string)
|
40
|
+
# * :bool => write a single byte, interpreted as a boolean (#write_bool)
|
41
|
+
# * :bignum => write an SSH-encoded bignum (#write_bignum)
|
42
|
+
# * :key => write an SSH-encoded key value (#write_key)
|
43
|
+
#
|
44
|
+
# Any of these, except for :raw, accepts an Array argument, to make it
|
45
|
+
# easier to write multiple values of the same type in a briefer manner.
|
46
|
+
def self.from(*args)
|
47
|
+
raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0
|
48
|
+
|
49
|
+
buffer = new
|
50
|
+
0.step(args.length-1, 2) do |index|
|
51
|
+
type = args[index]
|
52
|
+
value = args[index+1]
|
53
|
+
if type == :raw
|
54
|
+
buffer.append(value.to_s)
|
55
|
+
elsif Array === value
|
56
|
+
buffer.send("write_#{type}", *value)
|
57
|
+
else
|
58
|
+
buffer.send("write_#{type}", value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
buffer
|
63
|
+
end
|
64
|
+
|
65
|
+
# exposes the raw content of the buffer
|
66
|
+
attr_reader :content
|
67
|
+
|
68
|
+
# the current position of the pointer in the buffer
|
69
|
+
attr_accessor :position
|
70
|
+
|
71
|
+
# Creates a new buffer, initialized to the given content. The position
|
72
|
+
# is initialized to the beginning of the buffer.
|
73
|
+
def initialize(content="")
|
74
|
+
@content = content.to_s
|
75
|
+
@position = 0
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the length of the buffer's content.
|
79
|
+
def length
|
80
|
+
@content.length
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the number of bytes available to be read (e.g., how many bytes
|
84
|
+
# remain between the current position and the end of the buffer).
|
85
|
+
def available
|
86
|
+
length - position
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a copy of the buffer's content.
|
90
|
+
def to_s
|
91
|
+
(@content || "").dup
|
92
|
+
end
|
93
|
+
|
94
|
+
# Compares the contents of the two buffers, returning +true+ only if they
|
95
|
+
# are identical in size and content.
|
96
|
+
def ==(buffer)
|
97
|
+
to_s == buffer.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns +true+ if the buffer contains no data (e.g., it is of zero length).
|
101
|
+
def empty?
|
102
|
+
@content.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
# Resets the pointer to the start of the buffer. Subsequent reads will
|
106
|
+
# begin at position 0.
|
107
|
+
def reset!
|
108
|
+
@position = 0
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns true if the pointer is at the end of the buffer. Subsequent
|
112
|
+
# reads will return nil, in this case.
|
113
|
+
def eof?
|
114
|
+
@position >= length
|
115
|
+
end
|
116
|
+
|
117
|
+
# Resets the buffer, making it empty. Also, resets the read position to
|
118
|
+
# 0.
|
119
|
+
def clear!
|
120
|
+
@content = ""
|
121
|
+
@position = 0
|
122
|
+
end
|
123
|
+
|
124
|
+
# Consumes n bytes from the buffer, where n is the current position
|
125
|
+
# unless otherwise specified. This is useful for removing data from the
|
126
|
+
# buffer that has previously been read, when you are expecting more data
|
127
|
+
# to be appended. It helps to keep the size of buffers down when they
|
128
|
+
# would otherwise tend to grow without bound.
|
129
|
+
#
|
130
|
+
# Returns the buffer object itself.
|
131
|
+
def consume!(n=position)
|
132
|
+
if n >= length
|
133
|
+
# optimize for a fairly common case
|
134
|
+
clear!
|
135
|
+
elsif n > 0
|
136
|
+
@content = @content[n..-1] || ""
|
137
|
+
@position -= n
|
138
|
+
@position = 0 if @position < 0
|
139
|
+
end
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
# Appends the given text to the end of the buffer. Does not alter the
|
144
|
+
# read position. Returns the buffer object itself.
|
145
|
+
def append(text)
|
146
|
+
@content << text
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns all text from the current pointer to the end of the buffer as
|
151
|
+
# a new Net::SSH::Buffer object.
|
152
|
+
def remainder_as_buffer
|
153
|
+
Buffer.new(@content[@position..-1])
|
154
|
+
end
|
155
|
+
|
156
|
+
# Reads all data up to and including the given pattern, which may be a
|
157
|
+
# String, Fixnum, or Regexp and is interpreted exactly as String#index
|
158
|
+
# does. Returns nil if nothing matches. Increments the position to point
|
159
|
+
# immediately after the pattern, if it does match. Returns all data up to
|
160
|
+
# and including the text that matched the pattern.
|
161
|
+
def read_to(pattern)
|
162
|
+
index = @content.index(pattern, @position) or return nil
|
163
|
+
length = case pattern
|
164
|
+
when String then pattern.length
|
165
|
+
when Fixnum then 1
|
166
|
+
when Regexp then $&.length
|
167
|
+
end
|
168
|
+
index && read(index+length)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Reads and returns the next +count+ bytes from the buffer, starting from
|
172
|
+
# the read position. If +count+ is +nil+, this will return all remaining
|
173
|
+
# text in the buffer. This method will increment the pointer.
|
174
|
+
def read(count=nil)
|
175
|
+
count ||= length
|
176
|
+
count = length - @position if @position + count > length
|
177
|
+
@position += count
|
178
|
+
@content[@position-count, count]
|
179
|
+
end
|
180
|
+
|
181
|
+
# Reads (as #read) and returns the given number of bytes from the buffer,
|
182
|
+
# and then consumes (as #consume!) all data up to the new read position.
|
183
|
+
def read!(count=nil)
|
184
|
+
data = read(count)
|
185
|
+
consume!
|
186
|
+
data
|
187
|
+
end
|
188
|
+
|
189
|
+
# Return the next 8 bytes as a 64-bit integer (in network byte order).
|
190
|
+
# Returns nil if there are less than 8 bytes remaining to be read in the
|
191
|
+
# buffer.
|
192
|
+
def read_int64
|
193
|
+
hi = read_long or return nil
|
194
|
+
lo = read_long or return nil
|
195
|
+
return (hi << 32) + lo
|
196
|
+
end
|
197
|
+
|
198
|
+
# Return the next four bytes as a long integer (in network byte order).
|
199
|
+
# Returns nil if there are less than 4 bytes remaining to be read in the
|
200
|
+
# buffer.
|
201
|
+
def read_long
|
202
|
+
b = read(4) or return nil
|
203
|
+
b.unpack("N").first
|
204
|
+
end
|
205
|
+
|
206
|
+
# Read and return the next byte in the buffer. Returns nil if called at
|
207
|
+
# the end of the buffer.
|
208
|
+
def read_byte
|
209
|
+
b = read(1) or return nil
|
210
|
+
b.getbyte(0)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Read and return an SSH2-encoded string. The string starts with a long
|
214
|
+
# integer that describes the number of bytes remaining in the string.
|
215
|
+
# Returns nil if there are not enough bytes to satisfy the request.
|
216
|
+
def read_string
|
217
|
+
length = read_long or return nil
|
218
|
+
read(length)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Read a single byte and convert it into a boolean, using 'C' rules
|
222
|
+
# (i.e., zero is false, non-zero is true).
|
223
|
+
def read_bool
|
224
|
+
b = read_byte or return nil
|
225
|
+
b != 0
|
226
|
+
end
|
227
|
+
|
228
|
+
# Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is
|
229
|
+
# essentially just a string, which is reinterpreted to be a bignum in
|
230
|
+
# binary format.
|
231
|
+
def read_bignum
|
232
|
+
data = read_string
|
233
|
+
return unless data
|
234
|
+
OpenSSL::BN.new(data, 2)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Read a key from the buffer. The key will start with a string
|
238
|
+
# describing its type. The remainder of the key is defined by the
|
239
|
+
# type that was read.
|
240
|
+
def read_key
|
241
|
+
type = read_string
|
242
|
+
return (type ? read_keyblob(type) : nil)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Read a keyblob of the given type from the buffer, and return it as
|
246
|
+
# a key. Only RSA and DSA keys are supported.
|
247
|
+
def read_keyblob(type)
|
248
|
+
case type
|
249
|
+
when "ssh-dss"
|
250
|
+
key = OpenSSL::PKey::DSA.new
|
251
|
+
key.p = read_bignum
|
252
|
+
key.q = read_bignum
|
253
|
+
key.g = read_bignum
|
254
|
+
key.pub_key = read_bignum
|
255
|
+
|
256
|
+
when "ssh-rsa"
|
257
|
+
key = OpenSSL::PKey::RSA.new
|
258
|
+
key.e = read_bignum
|
259
|
+
key.n = read_bignum
|
260
|
+
|
261
|
+
else
|
262
|
+
raise NotImplementedError, "unsupported key type `#{type}'"
|
263
|
+
end
|
264
|
+
|
265
|
+
return key
|
266
|
+
end
|
267
|
+
|
268
|
+
# Reads the next string from the buffer, and returns a new Buffer
|
269
|
+
# object that wraps it.
|
270
|
+
def read_buffer
|
271
|
+
Buffer.new(read_string)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Writes the given data literally into the string. Does not alter the
|
275
|
+
# read position. Returns the buffer object.
|
276
|
+
def write(*data)
|
277
|
+
data.each { |datum| @content << datum }
|
278
|
+
self
|
279
|
+
end
|
280
|
+
|
281
|
+
# Writes each argument to the buffer as a network-byte-order-encoded
|
282
|
+
# 64-bit integer (8 bytes). Does not alter the read position. Returns the
|
283
|
+
# buffer object.
|
284
|
+
def write_int64(*n)
|
285
|
+
n.each do |i|
|
286
|
+
hi = (i >> 32) & 0xFFFFFFFF
|
287
|
+
lo = i & 0xFFFFFFFF
|
288
|
+
@content << [hi, lo].pack("N2")
|
289
|
+
end
|
290
|
+
self
|
291
|
+
end
|
292
|
+
|
293
|
+
# Writes each argument to the buffer as a network-byte-order-encoded
|
294
|
+
# long (4-byte) integer. Does not alter the read position. Returns the
|
295
|
+
# buffer object.
|
296
|
+
def write_long(*n)
|
297
|
+
@content << n.pack("N*")
|
298
|
+
self
|
299
|
+
end
|
300
|
+
|
301
|
+
# Writes each argument to the buffer as a byte. Does not alter the read
|
302
|
+
# position. Returns the buffer object.
|
303
|
+
def write_byte(*n)
|
304
|
+
n.each { |b| @content << b.chr }
|
305
|
+
self
|
306
|
+
end
|
307
|
+
|
308
|
+
# Writes each argument to the buffer as an SSH2-encoded string. Each
|
309
|
+
# string is prefixed by its length, encoded as a 4-byte long integer.
|
310
|
+
# Does not alter the read position. Returns the buffer object.
|
311
|
+
def write_string(*text)
|
312
|
+
text.each do |string|
|
313
|
+
s = string.to_s
|
314
|
+
write_long(s.length)
|
315
|
+
write(s)
|
316
|
+
end
|
317
|
+
self
|
318
|
+
end
|
319
|
+
|
320
|
+
# Writes each argument to the buffer as a (C-style) boolean, with 1
|
321
|
+
# meaning true, and 0 meaning false. Does not alter the read position.
|
322
|
+
# Returns the buffer object.
|
323
|
+
def write_bool(*b)
|
324
|
+
b.each { |v| @content << (v ? "\1" : "\0") }
|
325
|
+
self
|
326
|
+
end
|
327
|
+
|
328
|
+
# Writes each argument to the buffer as a bignum (SSH2-style). No
|
329
|
+
# checking is done to ensure that the arguments are, in fact, bignums.
|
330
|
+
# Does not alter the read position. Returns the buffer object.
|
331
|
+
def write_bignum(*n)
|
332
|
+
@content << n.map { |b| Utils.write_bignum(b) }.join
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
# Writes the given arguments to the buffer as SSH2-encoded keys. Does not
|
337
|
+
# alter the read position. Returns the buffer object.
|
338
|
+
def write_key(*key)
|
339
|
+
key.each { |k| append(k.public_identity(false)) }
|
340
|
+
self
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|