poolparty 1.3.4 → 1.3.6
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/Rakefile +2 -2
- data/VERSION.yml +1 -1
- data/bin/cloud-bootstrap +1 -0
- data/bin/cloud-configure +1 -0
- data/bin/cloud-contract +1 -0
- data/bin/cloud-misc +34 -0
- data/bin/cloud-setup +36 -0
- data/bin/cloud-ssh +4 -1
- data/config/jeweler.rb +4 -3
- data/examples/monitored_cloud.rb +1 -1
- data/examples/thrift/thrift_example.rb +5 -3
- data/examples/vmware.rb +28 -0
- data/lib/cloud_providers/cloud_provider_instance.rb +14 -5
- data/lib/cloud_providers/connections.rb +1 -1
- data/lib/core/file.rb +12 -0
- data/lib/core/object.rb +2 -2
- data/lib/dependency_resolvers/base.rb +1 -1
- data/lib/dependency_resolvers/chef.rb +9 -7
- data/lib/dependency_resolvers/proxy_object.rb +11 -3
- data/lib/mixins/askable.rb +16 -7
- data/lib/poolparty/base.rb +8 -7
- data/lib/poolparty/cloud.rb +77 -7
- data/lib/poolparty/default.rb +1 -0
- data/lib/poolparty/installer.rb +8 -4
- data/lib/poolparty/installers/ec2.rb +75 -5
- data/lib/poolparty/installers/vmware.rb +17 -5
- data/lib/poolparty/plugin.rb +1 -5
- data/lib/poolparty/plugins/apache.rb +10 -7
- data/lib/poolparty/plugins/apache2/base.conf.erb +2 -2
- data/lib/poolparty/plugins/apache2/browser_fixes.conf.erb +1 -1
- data/lib/poolparty/plugins/apache2/passenger_site.rb +2 -2
- data/lib/poolparty/plugins/collectd/templates/collectd.conf.erb +369 -0
- data/lib/poolparty/plugins/collectd.rb +24 -0
- data/lib/poolparty/plugins/hermes.rb +89 -0
- data/lib/poolparty/pool.rb +33 -3
- data/lib/poolparty/resource.rb +32 -18
- data/lib/poolparty/resources/directory.rb +5 -1
- data/lib/poolparty/resources/exec.rb +2 -2
- data/lib/poolparty/resources/file.rb +8 -2
- data/lib/poolparty/resources/gem_package.rb +2 -2
- data/lib/poolparty/resources/line.rb +23 -6
- data/lib/poolparty/resources/mount.rb +2 -2
- data/lib/poolparty/resources/package.rb +2 -2
- data/lib/poolparty/resources/service.rb +2 -2
- data/lib/poolparty/resources/user.rb +2 -2
- data/lib/poolparty/resources/variable.rb +4 -3
- data/lib/poolparty.rb +5 -3
- data/lib/proto/command_interface_handler.rb +17 -1
- data/lib/proto/gen-py/cloudthrift/CommandInterface.pyc +0 -0
- data/lib/proto/gen-py/cloudthrift/__init__.pyc +0 -0
- data/lib/proto/gen-py/cloudthrift/constants.pyc +0 -0
- data/lib/proto/gen-py/cloudthrift/ttypes.pyc +0 -0
- data/lib/proto/gen-py/thrift/Thrift.pyc +0 -0
- data/lib/proto/gen-py/thrift/__init__.pyc +0 -0
- data/lib/proto/gen-py/thrift/protocol/TBinaryProtocol.pyc +0 -0
- data/lib/proto/gen-py/thrift/protocol/TProtocol.pyc +0 -0
- data/lib/proto/gen-py/thrift/protocol/__init__.pyc +0 -0
- data/lib/proto/gen-py/thrift/transport/TSocket.pyc +0 -0
- data/lib/proto/gen-py/thrift/transport/TTransport.pyc +0 -0
- data/lib/proto/gen-py/thrift/transport/__init__.pyc +0 -0
- data/test/lib/dependency_resolvers/chef_test.rb +92 -100
- data/test/lib/poolparty/base_test.rb +13 -0
- data/test/lib/poolparty/cloud_test.rb +50 -2
- data/test/lib/poolparty/monitor_test.rb +2 -2
- data/test/lib/poolparty/resource_test.rb +5 -0
- data/test/lib/poolparty/resources/line_test.rb +3 -3
- data/test/lib/poolparty/resources/service_test.rb +1 -1
- data/test/lib/poolparty/resources/variable_test.rb +33 -10
- data/vendor/gems/net-ssh/CHANGELOG.rdoc +127 -0
- data/vendor/gems/net-ssh/Manifest +104 -0
- data/vendor/gems/net-ssh/README.rdoc +110 -0
- data/vendor/gems/net-ssh/Rakefile +26 -0
- data/vendor/gems/net-ssh/THANKS.rdoc +16 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/agent.rb +176 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/constants.rb +18 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/key_manager.rb +193 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/methods/abstract.rb +60 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/methods/password.rb +39 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/methods/publickey.rb +92 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/pageant.rb +183 -0
- data/vendor/gems/net-ssh/lib/net/ssh/authentication/session.rb +134 -0
- data/vendor/gems/net-ssh/lib/net/ssh/buffer.rb +340 -0
- data/vendor/gems/net-ssh/lib/net/ssh/buffered_io.rb +149 -0
- data/vendor/gems/net-ssh/lib/net/ssh/config.rb +181 -0
- data/vendor/gems/net-ssh/lib/net/ssh/connection/channel.rb +625 -0
- data/vendor/gems/net-ssh/lib/net/ssh/connection/constants.rb +33 -0
- data/vendor/gems/net-ssh/lib/net/ssh/connection/session.rb +596 -0
- data/vendor/gems/net-ssh/lib/net/ssh/connection/term.rb +178 -0
- data/vendor/gems/net-ssh/lib/net/ssh/errors.rb +85 -0
- data/vendor/gems/net-ssh/lib/net/ssh/key_factory.rb +102 -0
- data/vendor/gems/net-ssh/lib/net/ssh/known_hosts.rb +129 -0
- data/vendor/gems/net-ssh/lib/net/ssh/loggable.rb +61 -0
- data/vendor/gems/net-ssh/lib/net/ssh/packet.rb +102 -0
- data/vendor/gems/net-ssh/lib/net/ssh/prompt.rb +93 -0
- data/vendor/gems/net-ssh/lib/net/ssh/proxy/errors.rb +14 -0
- data/vendor/gems/net-ssh/lib/net/ssh/proxy/http.rb +94 -0
- data/vendor/gems/net-ssh/lib/net/ssh/proxy/socks4.rb +70 -0
- data/vendor/gems/net-ssh/lib/net/ssh/proxy/socks5.rb +129 -0
- data/vendor/gems/net-ssh/lib/net/ssh/ruby_compat.rb +7 -0
- data/vendor/gems/net-ssh/lib/net/ssh/service/forward.rb +267 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test/channel.rb +129 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test/extensions.rb +152 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test/kex.rb +44 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test/local_packet.rb +51 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test/packet.rb +81 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test/remote_packet.rb +38 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test/script.rb +157 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test/socket.rb +59 -0
- data/vendor/gems/net-ssh/lib/net/ssh/test.rb +89 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/algorithms.rb +384 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/cipher_factory.rb +80 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/constants.rb +30 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/hmac/abstract.rb +78 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/hmac/md5.rb +12 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/hmac/none.rb +15 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/hmac/sha1.rb +13 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/hmac.rb +31 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/identity_cipher.rb +55 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/kex.rb +13 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/openssl.rb +128 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/packet_stream.rb +230 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/server_version.rb +60 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/session.rb +276 -0
- data/vendor/gems/net-ssh/lib/net/ssh/transport/state.rb +201 -0
- data/vendor/gems/net-ssh/lib/net/ssh/verifiers/lenient.rb +30 -0
- data/vendor/gems/net-ssh/lib/net/ssh/verifiers/null.rb +12 -0
- data/vendor/gems/net-ssh/lib/net/ssh/verifiers/strict.rb +53 -0
- data/vendor/gems/net-ssh/lib/net/ssh/version.rb +62 -0
- data/vendor/gems/net-ssh/lib/net/ssh.rb +215 -0
- data/vendor/gems/net-ssh/net-ssh.gemspec +33 -0
- data/vendor/gems/net-ssh/setup.rb +1585 -0
- data/vendor/gems/net-ssh/test/authentication/methods/common.rb +28 -0
- data/vendor/gems/net-ssh/test/authentication/methods/test_abstract.rb +51 -0
- data/vendor/gems/net-ssh/test/authentication/methods/test_hostbased.rb +114 -0
- data/vendor/gems/net-ssh/test/authentication/methods/test_keyboard_interactive.rb +98 -0
- data/vendor/gems/net-ssh/test/authentication/methods/test_password.rb +50 -0
- data/vendor/gems/net-ssh/test/authentication/methods/test_publickey.rb +127 -0
- data/vendor/gems/net-ssh/test/authentication/test_agent.rb +205 -0
- data/vendor/gems/net-ssh/test/authentication/test_key_manager.rb +105 -0
- data/vendor/gems/net-ssh/test/authentication/test_session.rb +93 -0
- data/vendor/gems/net-ssh/test/common.rb +106 -0
- data/vendor/gems/net-ssh/test/configs/eqsign +3 -0
- data/vendor/gems/net-ssh/test/configs/exact_match +8 -0
- data/vendor/gems/net-ssh/test/configs/wild_cards +14 -0
- data/vendor/gems/net-ssh/test/connection/test_channel.rb +452 -0
- data/vendor/gems/net-ssh/test/connection/test_session.rb +488 -0
- data/vendor/gems/net-ssh/test/test_all.rb +6 -0
- data/vendor/gems/net-ssh/test/test_buffer.rb +336 -0
- data/vendor/gems/net-ssh/test/test_buffered_io.rb +63 -0
- data/vendor/gems/net-ssh/test/test_config.rb +84 -0
- data/vendor/gems/net-ssh/test/test_key_factory.rb +67 -0
- data/vendor/gems/net-ssh/test/transport/hmac/test_md5.rb +39 -0
- data/vendor/gems/net-ssh/test/transport/hmac/test_md5_96.rb +25 -0
- data/vendor/gems/net-ssh/test/transport/hmac/test_none.rb +34 -0
- data/vendor/gems/net-ssh/test/transport/hmac/test_sha1.rb +34 -0
- data/vendor/gems/net-ssh/test/transport/hmac/test_sha1_96.rb +25 -0
- data/vendor/gems/net-ssh/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
- data/vendor/gems/net-ssh/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
- data/vendor/gems/net-ssh/test/transport/test_algorithms.rb +302 -0
- data/vendor/gems/net-ssh/test/transport/test_cipher_factory.rb +171 -0
- data/vendor/gems/net-ssh/test/transport/test_hmac.rb +34 -0
- data/vendor/gems/net-ssh/test/transport/test_identity_cipher.rb +40 -0
- data/vendor/gems/net-ssh/test/transport/test_packet_stream.rb +435 -0
- data/vendor/gems/net-ssh/test/transport/test_server_version.rb +57 -0
- data/vendor/gems/net-ssh/test/transport/test_session.rb +315 -0
- data/vendor/gems/net-ssh/test/transport/test_state.rb +173 -0
- metadata +116 -4
- data/bin/install-poolparty +0 -20
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'common'
|
|
2
|
+
require 'transport/hmac/test_sha1'
|
|
3
|
+
require 'net/ssh/transport/hmac/sha1_96'
|
|
4
|
+
|
|
5
|
+
module Transport; module HMAC
|
|
6
|
+
|
|
7
|
+
class TestSHA1_96 < TestSHA1
|
|
8
|
+
def test_expected_mac_length
|
|
9
|
+
assert_equal 12, subject.mac_length
|
|
10
|
+
assert_equal 12, subject.new.mac_length
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_expected_digest
|
|
14
|
+
hmac = subject.new("1234567890123456")
|
|
15
|
+
assert_equal "\000\004W\202\204+&\335\311\251P\266", hmac.digest("hello world")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def subject
|
|
21
|
+
Net::SSH::Transport::HMAC::SHA1_96
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end; end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
require 'common'
|
|
2
|
+
require 'net/ssh/transport/kex/diffie_hellman_group1_sha1'
|
|
3
|
+
require 'ostruct'
|
|
4
|
+
|
|
5
|
+
module Transport; module Kex
|
|
6
|
+
|
|
7
|
+
class TestDiffieHellmanGroup1SHA1 < Test::Unit::TestCase
|
|
8
|
+
include Net::SSH::Transport::Constants
|
|
9
|
+
|
|
10
|
+
def setup
|
|
11
|
+
@dh_options = @dh = @algorithms = @connection = @server_key =
|
|
12
|
+
@packet_data = @shared_secret = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_exchange_keys_should_return_expected_results_when_successful
|
|
16
|
+
result = exchange!
|
|
17
|
+
assert_equal session_id, result[:session_id]
|
|
18
|
+
assert_equal server_key.to_blob, result[:server_key].to_blob
|
|
19
|
+
assert_equal shared_secret, result[:shared_secret]
|
|
20
|
+
assert_equal OpenSSL::Digest::SHA1, result[:hashing_algorithm]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_exchange_keys_with_unverifiable_host_should_raise_exception
|
|
24
|
+
connection.verifier { false }
|
|
25
|
+
assert_raises(Net::SSH::Exception) { exchange! }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_exchange_keys_with_signature_key_type_mismatch_should_raise_exception
|
|
29
|
+
assert_raises(Net::SSH::Exception) { exchange! :key_type => "ssh-dss" }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_exchange_keys_with_host_key_type_mismatch_should_raise_exception
|
|
33
|
+
algorithms :host_key => "ssh-dss"
|
|
34
|
+
assert_raises(Net::SSH::Exception) { exchange! :key_type => "ssh-dss" }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_exchange_keys_when_server_signature_could_not_be_verified_should_raise_exception
|
|
38
|
+
@signature = "1234567890"
|
|
39
|
+
assert_raises(Net::SSH::Exception) { exchange! }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_exchange_keys_should_pass_expected_parameters_to_host_key_verifier
|
|
43
|
+
verified = false
|
|
44
|
+
connection.verifier do |data|
|
|
45
|
+
verified = true
|
|
46
|
+
assert_equal server_key.to_blob, data[:key].to_blob
|
|
47
|
+
|
|
48
|
+
blob = b(:key, data[:key]).to_s
|
|
49
|
+
fingerprint = OpenSSL::Digest::MD5.hexdigest(blob).scan(/../).join(":")
|
|
50
|
+
|
|
51
|
+
assert_equal blob, data[:key_blob]
|
|
52
|
+
assert_equal fingerprint, data[:fingerprint]
|
|
53
|
+
assert_equal connection, data[:session]
|
|
54
|
+
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
assert_nothing_raised { exchange! }
|
|
59
|
+
assert verified
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def exchange!(options={})
|
|
65
|
+
connection.expect do |t, buffer|
|
|
66
|
+
assert_equal KEXDH_INIT, buffer.type
|
|
67
|
+
assert_equal dh.dh.pub_key, buffer.read_bignum
|
|
68
|
+
t.return(KEXDH_REPLY, :string, b(:key, server_key), :bignum, server_dh_pubkey, :string, b(:string, options[:key_type] || "ssh-rsa", :string, signature))
|
|
69
|
+
connection.expect do |t2, buffer2|
|
|
70
|
+
assert_equal NEWKEYS, buffer2.type
|
|
71
|
+
t2.return(NEWKEYS)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
dh.exchange_keys
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def dh_options(options={})
|
|
79
|
+
@dh_options = options
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def dh
|
|
83
|
+
@dh ||= subject.new(algorithms, connection, packet_data.merge(:need_bytes => 20).merge(@dh_options || {}))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def algorithms(options={})
|
|
87
|
+
@algorithms ||= OpenStruct.new(:host_key => options[:host_key] || "ssh-rsa")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def connection
|
|
91
|
+
@connection ||= MockTransport.new
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def subject
|
|
95
|
+
Net::SSH::Transport::Kex::DiffieHellmanGroup1SHA1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 368 bits is the smallest possible key that will work with this, so
|
|
99
|
+
# we use it for speed reasons
|
|
100
|
+
def server_key(bits=368)
|
|
101
|
+
@server_key ||= OpenSSL::PKey::RSA.new(bits)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def packet_data
|
|
105
|
+
@packet_data ||= { :client_version_string => "client version string",
|
|
106
|
+
:server_version_string => "server version string",
|
|
107
|
+
:server_algorithm_packet => "server algorithm packet",
|
|
108
|
+
:client_algorithm_packet => "client algorithm packet" }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def server_dh_pubkey
|
|
112
|
+
@server_dh_pubkey ||= bn(1234567890)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def shared_secret
|
|
116
|
+
@shared_secret ||= OpenSSL::BN.new(dh.dh.compute_key(server_dh_pubkey), 2)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def session_id
|
|
120
|
+
@session_id ||= begin
|
|
121
|
+
buffer = Net::SSH::Buffer.from(:string, packet_data[:client_version_string],
|
|
122
|
+
:string, packet_data[:server_version_string],
|
|
123
|
+
:string, packet_data[:client_algorithm_packet],
|
|
124
|
+
:string, packet_data[:server_algorithm_packet],
|
|
125
|
+
:string, Net::SSH::Buffer.from(:key, server_key),
|
|
126
|
+
:bignum, dh.dh.pub_key,
|
|
127
|
+
:bignum, server_dh_pubkey,
|
|
128
|
+
:bignum, shared_secret)
|
|
129
|
+
OpenSSL::Digest::SHA1.digest(buffer.to_s)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def signature
|
|
134
|
+
@signature ||= server_key.ssh_do_sign(session_id)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def bn(number, base=10)
|
|
138
|
+
OpenSSL::BN.new(number.to_s, base)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def b(*args)
|
|
142
|
+
Net::SSH::Buffer.from(*args)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end; end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'common'
|
|
2
|
+
require 'transport/kex/test_diffie_hellman_group1_sha1'
|
|
3
|
+
require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1'
|
|
4
|
+
|
|
5
|
+
module Transport; module Kex
|
|
6
|
+
|
|
7
|
+
class TestDiffieHellmanGroupExchangeSHA1 < TestDiffieHellmanGroup1SHA1
|
|
8
|
+
KEXDH_GEX_GROUP = 31
|
|
9
|
+
KEXDH_GEX_INIT = 32
|
|
10
|
+
KEXDH_GEX_REPLY = 33
|
|
11
|
+
KEXDH_GEX_REQUEST = 34
|
|
12
|
+
|
|
13
|
+
def test_exchange_with_fewer_than_minimum_bits_uses_minimum_bits
|
|
14
|
+
dh_options :need_bytes => 20
|
|
15
|
+
assert_equal 1024, need_bits
|
|
16
|
+
assert_nothing_raised { exchange! }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_exchange_with_fewer_than_maximum_bits_uses_need_bits
|
|
20
|
+
dh_options :need_bytes => 500
|
|
21
|
+
need_bits(4000)
|
|
22
|
+
assert_nothing_raised { exchange! }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_exchange_with_more_than_maximum_bits_uses_maximum_bits
|
|
26
|
+
dh_options :need_bytes => 2000
|
|
27
|
+
need_bits(8192)
|
|
28
|
+
assert_nothing_raised { exchange! }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_that_p_and_g_are_provided_by_the_server
|
|
32
|
+
assert_nothing_raised { exchange! :p => default_p+2, :g => 3 }
|
|
33
|
+
assert_equal default_p+2, dh.dh.p
|
|
34
|
+
assert_equal 3, dh.dh.g
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def need_bits(bits=1024)
|
|
40
|
+
@need_bits ||= bits
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def default_p
|
|
44
|
+
142326151570335518660743995281621698377057354949884468943021767573608899048361360422513557553514790045512299468953431585300812548859419857171094366358158903433167915517332113861059747425408670144201099811846875730766487278261498262568348338476437200556998366087779709990807518291581860338635288400119315130179
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def exchange!(options={})
|
|
48
|
+
connection.expect do |t, buffer|
|
|
49
|
+
assert_equal KEXDH_GEX_REQUEST, buffer.type
|
|
50
|
+
assert_equal 1024, buffer.read_long
|
|
51
|
+
assert_equal need_bits, buffer.read_long
|
|
52
|
+
assert_equal 8192, buffer.read_long
|
|
53
|
+
t.return(KEXDH_GEX_GROUP, :bignum, bn(options[:p] || default_p), :bignum, bn(options[:g] || 2))
|
|
54
|
+
t.expect do |t2, buffer2|
|
|
55
|
+
assert_equal KEXDH_GEX_INIT, buffer2.type
|
|
56
|
+
assert_equal dh.dh.pub_key, buffer2.read_bignum
|
|
57
|
+
t2.return(KEXDH_GEX_REPLY, :string, b(:key, server_key), :bignum, server_dh_pubkey, :string, b(:string, options[:key_type] || "ssh-rsa", :string, signature))
|
|
58
|
+
t2.expect do |t3, buffer3|
|
|
59
|
+
assert_equal NEWKEYS, buffer3.type
|
|
60
|
+
t3.return(NEWKEYS)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
dh.exchange_keys
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def subject
|
|
69
|
+
Net::SSH::Transport::Kex::DiffieHellmanGroupExchangeSHA1
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def session_id
|
|
73
|
+
@session_id ||= begin
|
|
74
|
+
buffer = Net::SSH::Buffer.from(:string, packet_data[:client_version_string],
|
|
75
|
+
:string, packet_data[:server_version_string],
|
|
76
|
+
:string, packet_data[:client_algorithm_packet],
|
|
77
|
+
:string, packet_data[:server_algorithm_packet],
|
|
78
|
+
:string, Net::SSH::Buffer.from(:key, server_key),
|
|
79
|
+
:long, 1024,
|
|
80
|
+
:long, need_bits, # need bits, figure this part out,
|
|
81
|
+
:long, 8192,
|
|
82
|
+
:bignum, dh.dh.p,
|
|
83
|
+
:bignum, dh.dh.g,
|
|
84
|
+
:bignum, dh.dh.pub_key,
|
|
85
|
+
:bignum, server_dh_pubkey,
|
|
86
|
+
:bignum, shared_secret)
|
|
87
|
+
OpenSSL::Digest::SHA1.digest(buffer.to_s)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end; end
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
require 'common'
|
|
2
|
+
require 'net/ssh/transport/algorithms'
|
|
3
|
+
|
|
4
|
+
module Transport
|
|
5
|
+
|
|
6
|
+
class TestAlgorithms < Test::Unit::TestCase
|
|
7
|
+
include Net::SSH::Transport::Constants
|
|
8
|
+
|
|
9
|
+
def test_allowed_packets
|
|
10
|
+
(0..255).each do |type|
|
|
11
|
+
packet = stub("packet", :type => type)
|
|
12
|
+
case type
|
|
13
|
+
when 1..4, 6..19, 21..49 then assert(Net::SSH::Transport::Algorithms.allowed_packet?(packet), "#{type} should be allowed during key exchange")
|
|
14
|
+
else assert(!Net::SSH::Transport::Algorithms.allowed_packet?(packet), "#{type} should not be allowed during key exchange")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_constructor_should_build_default_list_of_preferred_algorithms
|
|
20
|
+
assert_equal %w(ssh-rsa ssh-dss), algorithms[:host_key]
|
|
21
|
+
assert_equal %w(diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1), algorithms[:kex]
|
|
22
|
+
assert_equal %w(aes128-cbc 3des-cbc blowfish-cbc cast128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se idea-cbc none), algorithms[:encryption]
|
|
23
|
+
assert_equal %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96 none), algorithms[:hmac]
|
|
24
|
+
assert_equal %w(none zlib@openssh.com zlib), algorithms[:compression]
|
|
25
|
+
assert_equal %w(), algorithms[:language]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_constructor_should_set_client_and_server_prefs_identically
|
|
29
|
+
%w(encryption hmac compression language).each do |key|
|
|
30
|
+
assert_equal algorithms[key.to_sym], algorithms[:"#{key}_client"], key
|
|
31
|
+
assert_equal algorithms[key.to_sym], algorithms[:"#{key}_server"], key
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_constructor_with_preferred_host_key_type_should_put_preferred_host_key_type_first
|
|
36
|
+
assert_equal %w(ssh-dss ssh-rsa), algorithms(:host_key => "ssh-dss")[:host_key]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_constructor_with_known_hosts_reporting_known_host_key_should_use_that_host_key_type
|
|
40
|
+
Net::SSH::KnownHosts.expects(:search_for).with("net.ssh.test,127.0.0.1", {}).returns([stub("key", :ssh_type => "ssh-dss")])
|
|
41
|
+
assert_equal %w(ssh-dss ssh-rsa), algorithms[:host_key]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_constructor_with_unrecognized_host_key_type_should_raise_exception
|
|
45
|
+
assert_raises(NotImplementedError) { algorithms(:host_key => "bogus") }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_constructor_with_preferred_kex_should_put_preferred_kex_first
|
|
49
|
+
assert_equal %w(diffie-hellman-group1-sha1 diffie-hellman-group-exchange-sha1), algorithms(:kex => "diffie-hellman-group1-sha1")[:kex]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_constructor_with_unrecognized_kex_should_raise_exception
|
|
53
|
+
assert_raises(NotImplementedError) { algorithms(:kex => "bogus") }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_constructor_with_preferred_encryption_should_put_preferred_encryption_first
|
|
57
|
+
assert_equal %w(aes256-cbc aes128-cbc 3des-cbc blowfish-cbc cast128-cbc aes192-cbc rijndael-cbc@lysator.liu.se idea-cbc none), algorithms(:encryption => "aes256-cbc")[:encryption]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_constructor_with_multiple_preferred_encryption_should_put_all_preferred_encryption_first
|
|
61
|
+
assert_equal %w(aes256-cbc 3des-cbc idea-cbc aes128-cbc blowfish-cbc cast128-cbc aes192-cbc rijndael-cbc@lysator.liu.se none), algorithms(:encryption => %w(aes256-cbc 3des-cbc idea-cbc))[:encryption]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_constructor_with_unrecognized_encryption_should_raise_exception
|
|
65
|
+
assert_raises(NotImplementedError) { algorithms(:encryption => "bogus") }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_constructor_with_preferred_hmac_should_put_preferred_hmac_first
|
|
69
|
+
assert_equal %w(hmac-md5-96 hmac-sha1 hmac-md5 hmac-sha1-96 none), algorithms(:hmac => "hmac-md5-96")[:hmac]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_constructor_with_multiple_preferred_hmac_should_put_all_preferred_hmac_first
|
|
73
|
+
assert_equal %w(hmac-md5-96 hmac-sha1-96 hmac-sha1 hmac-md5 none), algorithms(:hmac => %w(hmac-md5-96 hmac-sha1-96))[:hmac]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_constructor_with_unrecognized_hmac_should_raise_exception
|
|
77
|
+
assert_raises(NotImplementedError) { algorithms(:hmac => "bogus") }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_constructor_with_preferred_compression_should_put_preferred_compression_first
|
|
81
|
+
assert_equal %w(zlib none zlib@openssh.com), algorithms(:compression => "zlib")[:compression]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_constructor_with_multiple_preferred_compression_should_put_all_preferred_compression_first
|
|
85
|
+
assert_equal %w(zlib@openssh.com zlib none), algorithms(:compression => %w(zlib@openssh.com zlib))[:compression]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_constructor_with_general_preferred_compression_should_put_none_last
|
|
89
|
+
assert_equal %w(zlib@openssh.com zlib none), algorithms(:compression => true)[:compression]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_constructor_with_unrecognized_compression_should_raise_exception
|
|
93
|
+
assert_raises(NotImplementedError) { algorithms(:compression => "bogus") }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_initial_state_should_be_neither_pending_nor_initialized
|
|
97
|
+
assert !algorithms.pending?
|
|
98
|
+
assert !algorithms.initialized?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_key_exchange_when_initiated_by_server
|
|
102
|
+
transport.expect do |t, buffer|
|
|
103
|
+
assert_kexinit(buffer)
|
|
104
|
+
install_mock_key_exchange(buffer)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
install_mock_algorithm_lookups
|
|
108
|
+
algorithms.accept_kexinit(kexinit)
|
|
109
|
+
|
|
110
|
+
assert_exchange_results
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_key_exchange_when_initiated_by_client
|
|
114
|
+
state = nil
|
|
115
|
+
transport.expect do |t, buffer|
|
|
116
|
+
assert_kexinit(buffer)
|
|
117
|
+
state = :sent_kexinit
|
|
118
|
+
install_mock_key_exchange(buffer)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
algorithms.rekey!
|
|
122
|
+
assert_equal state, :sent_kexinit
|
|
123
|
+
assert algorithms.pending?
|
|
124
|
+
|
|
125
|
+
install_mock_algorithm_lookups
|
|
126
|
+
algorithms.accept_kexinit(kexinit)
|
|
127
|
+
|
|
128
|
+
assert_exchange_results
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_key_exchange_when_server_does_not_support_preferred_kex_should_fallback_to_secondary
|
|
132
|
+
kexinit :kex => "diffie-hellman-group1-sha1"
|
|
133
|
+
transport.expect do |t,buffer|
|
|
134
|
+
assert_kexinit(buffer)
|
|
135
|
+
install_mock_key_exchange(buffer, :kex => Net::SSH::Transport::Kex::DiffieHellmanGroup1SHA1)
|
|
136
|
+
end
|
|
137
|
+
algorithms.accept_kexinit(kexinit)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_key_exchange_when_server_does_not_support_any_preferred_kex_should_raise_error
|
|
141
|
+
kexinit :kex => "something-obscure"
|
|
142
|
+
transport.expect { |t,buffer| assert_kexinit(buffer) }
|
|
143
|
+
assert_raises(Net::SSH::Exception) { algorithms.accept_kexinit(kexinit) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_allow_when_not_pending_should_be_true_for_all_packets
|
|
147
|
+
(0..255).each do |type|
|
|
148
|
+
packet = stub("packet", :type => type)
|
|
149
|
+
assert algorithms.allow?(packet), type
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def test_allow_when_pending_should_be_true_only_for_packets_valid_during_key_exchange
|
|
154
|
+
transport.expect!
|
|
155
|
+
algorithms.rekey!
|
|
156
|
+
assert algorithms.pending?
|
|
157
|
+
|
|
158
|
+
(0..255).each do |type|
|
|
159
|
+
packet = stub("packet", :type => type)
|
|
160
|
+
case type
|
|
161
|
+
when 1..4, 6..19, 21..49 then assert(algorithms.allow?(packet), "#{type} should be allowed during key exchange")
|
|
162
|
+
else assert(!algorithms.allow?(packet), "#{type} should not be allowed during key exchange")
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def test_exchange_with_zlib_compression_enabled_sets_compression_to_standard
|
|
168
|
+
algorithms :compression => "zlib"
|
|
169
|
+
|
|
170
|
+
transport.expect do |t, buffer|
|
|
171
|
+
assert_kexinit(buffer, :compression_client => "zlib,none,zlib@openssh.com", :compression_server => "zlib,none,zlib@openssh.com")
|
|
172
|
+
install_mock_key_exchange(buffer)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
install_mock_algorithm_lookups
|
|
176
|
+
algorithms.accept_kexinit(kexinit)
|
|
177
|
+
|
|
178
|
+
assert_equal :standard, transport.client_options[:compression]
|
|
179
|
+
assert_equal :standard, transport.server_options[:compression]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_exchange_with_zlib_at_openssh_dot_com_compression_enabled_sets_compression_to_delayed
|
|
183
|
+
algorithms :compression => "zlib@openssh.com"
|
|
184
|
+
|
|
185
|
+
transport.expect do |t, buffer|
|
|
186
|
+
assert_kexinit(buffer, :compression_client => "zlib@openssh.com,none,zlib", :compression_server => "zlib@openssh.com,none,zlib")
|
|
187
|
+
install_mock_key_exchange(buffer)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
install_mock_algorithm_lookups
|
|
191
|
+
algorithms.accept_kexinit(kexinit)
|
|
192
|
+
|
|
193
|
+
assert_equal :delayed, transport.client_options[:compression]
|
|
194
|
+
assert_equal :delayed, transport.server_options[:compression]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def install_mock_key_exchange(buffer, options={})
|
|
200
|
+
kex = options[:kex] || Net::SSH::Transport::Kex::DiffieHellmanGroupExchangeSHA1
|
|
201
|
+
|
|
202
|
+
Net::SSH::Transport::Kex::MAP.each do |name, klass|
|
|
203
|
+
next if klass == kex
|
|
204
|
+
klass.expects(:new).never
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
kex.expects(:new).
|
|
208
|
+
with(algorithms, transport,
|
|
209
|
+
:client_version_string => Net::SSH::Transport::ServerVersion::PROTO_VERSION,
|
|
210
|
+
:server_version_string => transport.server_version.version,
|
|
211
|
+
:server_algorithm_packet => kexinit.to_s,
|
|
212
|
+
:client_algorithm_packet => buffer.to_s,
|
|
213
|
+
:need_bytes => 20,
|
|
214
|
+
:logger => nil).
|
|
215
|
+
returns(stub("kex", :exchange_keys => { :shared_secret => shared_secret, :session_id => session_id, :hashing_algorithm => hashing_algorithm }))
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def install_mock_algorithm_lookups(options={})
|
|
219
|
+
Net::SSH::Transport::CipherFactory.expects(:get).
|
|
220
|
+
with(options[:client_cipher] || "aes128-cbc", :iv => key("A"), :key => key("C"), :shared => shared_secret.to_ssh, :hash => session_id, :digester => hashing_algorithm, :encrypt => true).
|
|
221
|
+
returns(:client_cipher)
|
|
222
|
+
Net::SSH::Transport::CipherFactory.expects(:get).
|
|
223
|
+
with(options[:server_cipher] || "aes128-cbc", :iv => key("B"), :key => key("D"), :shared => shared_secret.to_ssh, :hash => session_id, :digester => hashing_algorithm, :decrypt => true).
|
|
224
|
+
returns(:server_cipher)
|
|
225
|
+
|
|
226
|
+
Net::SSH::Transport::HMAC.expects(:get).with(options[:client_hmac] || "hmac-sha1", key("E")).returns(:client_hmac)
|
|
227
|
+
Net::SSH::Transport::HMAC.expects(:get).with(options[:server_hmac] || "hmac-sha1", key("F")).returns(:server_hmac)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def shared_secret
|
|
231
|
+
@shared_secret ||= OpenSSL::BN.new("1234567890", 10)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def session_id
|
|
235
|
+
@session_id ||= "this is the session id"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def hashing_algorithm
|
|
239
|
+
OpenSSL::Digest::SHA1
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def key(salt)
|
|
243
|
+
hashing_algorithm.digest(shared_secret.to_ssh + session_id + salt + session_id)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def cipher(type, options={})
|
|
247
|
+
Net::SSH::Transport::CipherFactory.get(type, options)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def kexinit(options={})
|
|
251
|
+
@kexinit ||= P(:byte, KEXINIT,
|
|
252
|
+
:long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF),
|
|
253
|
+
:string, options[:kex] || "diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1",
|
|
254
|
+
:string, options[:host_key] || "ssh-rsa,ssh-dss",
|
|
255
|
+
:string, options[:encryption_client] || "aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc",
|
|
256
|
+
:string, options[:encryption_server] || "aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc",
|
|
257
|
+
:string, options[:hmac_client] || "hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96",
|
|
258
|
+
:string, options[:hmac_server] || "hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96",
|
|
259
|
+
:string, options[:compmression_client] || "none,zlib@openssh.com,zlib",
|
|
260
|
+
:string, options[:compmression_server] || "none,zlib@openssh.com,zlib",
|
|
261
|
+
:string, options[:language_client] || "",
|
|
262
|
+
:string, options[:langauge_server] || "",
|
|
263
|
+
:bool, options[:first_kex_follows])
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def assert_kexinit(buffer, options={})
|
|
267
|
+
assert_equal KEXINIT, buffer.type
|
|
268
|
+
assert_equal 16, buffer.read(16).length
|
|
269
|
+
assert_equal options[:kex] || "diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1", buffer.read_string
|
|
270
|
+
assert_equal options[:host_key] || "ssh-rsa,ssh-dss", buffer.read_string
|
|
271
|
+
assert_equal options[:encryption_client] || "aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc,none", buffer.read_string
|
|
272
|
+
assert_equal options[:encryption_server] || "aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc,none", buffer.read_string
|
|
273
|
+
assert_equal options[:hmac_client] || "hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96,none", buffer.read_string
|
|
274
|
+
assert_equal options[:hmac_server] || "hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96,none", buffer.read_string
|
|
275
|
+
assert_equal options[:compression_client] || "none,zlib@openssh.com,zlib", buffer.read_string
|
|
276
|
+
assert_equal options[:compression_server] || "none,zlib@openssh.com,zlib", buffer.read_string
|
|
277
|
+
assert_equal options[:language_client] || "", buffer.read_string
|
|
278
|
+
assert_equal options[:language_server] || "", buffer.read_string
|
|
279
|
+
assert_equal options[:first_kex_follows] || false, buffer.read_bool
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def assert_exchange_results
|
|
283
|
+
assert algorithms.initialized?
|
|
284
|
+
assert !algorithms.pending?
|
|
285
|
+
assert !transport.client_options[:compression]
|
|
286
|
+
assert !transport.server_options[:compression]
|
|
287
|
+
assert_equal :client_cipher, transport.client_options[:cipher]
|
|
288
|
+
assert_equal :server_cipher, transport.server_options[:cipher]
|
|
289
|
+
assert_equal :client_hmac, transport.client_options[:hmac]
|
|
290
|
+
assert_equal :server_hmac, transport.server_options[:hmac]
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def algorithms(options={})
|
|
294
|
+
@algorithms ||= Net::SSH::Transport::Algorithms.new(transport, options)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def transport
|
|
298
|
+
@transport ||= MockTransport.new
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
end
|