ruby-mysql 2.10.0 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +4 -6
- data/lib/mysql/authenticator/caching_sha2_password.rb +62 -0
- data/lib/mysql/authenticator/mysql_native_password.rb +37 -0
- data/lib/mysql/authenticator/sha256_password.rb +40 -0
- data/lib/mysql/authenticator.rb +84 -0
- data/lib/mysql/charset.rb +379 -328
- data/lib/mysql/constants.rb +7 -0
- data/lib/mysql/error.rb +3 -6
- data/lib/mysql/protocol.rb +174 -88
- data/lib/mysql.rb +51 -31
- data/test/test_mysql.rb +28 -15
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40bdb4e1eba4eca2ed1e93524260bd9407f91ee5f65b8c32b8c38524ef05ebdd
|
4
|
+
data.tar.gz: 224ad0bdf2050dca6e5d7a603bd64cb44a5f414270aa06b9dd89690cd2665a32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a73e1ab248a44935cd4115c0d2de16f491d51b2eb7ab7a9b33185a0f478be8753cc226ea78d645a2bb1ae0b09cb15a7432f9e8d5a1dfa82af3ef016783689544
|
7
|
+
data.tar.gz: 37db0a8b13b5a543cf9d552801aa0419c4ccce9fbe287d8df0335002df728e5e5ab5a9d82c873c174dec547b1077730cd265a4a6ace1714c88cb984b3d5f4134
|
data/README.rdoc
CHANGED
@@ -20,8 +20,6 @@ MySQL connector for Ruby.
|
|
20
20
|
|
21
21
|
* MySQL/Ruby 2.8.x とほぼ互換があります。
|
22
22
|
|
23
|
-
* MySQL 8.0 には対応していません。
|
24
|
-
|
25
23
|
== Synopsis
|
26
24
|
|
27
25
|
使用例:
|
@@ -35,17 +33,17 @@ MySQL connector for Ruby.
|
|
35
33
|
|
36
34
|
== Incompatible with MySQL/Ruby 2.8.x
|
37
35
|
|
38
|
-
* Ruby 1.8.x ではシフトJISのような安全でないマルチバイト文字セットに対して Mysql#escape_string を使用すると例外が発生します。
|
39
|
-
|
40
36
|
* いくつかのメソッドがありません: Mysql#debug, Mysql#change_user,
|
41
37
|
Mysql#create_db, Mysql#drop_db, Mysql#dump_debug_info,
|
42
38
|
Mysql#ssl_set, Mysql#reconnect
|
43
39
|
|
44
40
|
* Mysql#options でサポートしているオプションは次のものだけです:
|
45
41
|
Mysql::INIT_COMMAND, Mysql::OPT_CONNECT_TIMEOUT,
|
42
|
+
Mysql::OPT_GET_SERVER_PUBLIC_KEY,
|
43
|
+
Mysql::OPT_LOAD_DATA_LOCAL_DIR,
|
46
44
|
Mysql::OPT_LOCAL_INFILE, Mysql::OPT_READ_TIMEOUT,
|
47
|
-
Mysql::
|
48
|
-
Mysql::
|
45
|
+
Mysql::OPT_SSL_MODE,
|
46
|
+
Mysql::OPT_WRITE_TIMEOUT, Mysql::SET_CHARSET_NAME.
|
49
47
|
これら以外を指定すると "option not implementted" という warning が標準エラー出力に出力されます。
|
50
48
|
|
51
49
|
* Mysql#use_result は Mysql#store_result と同じです。つまりサーバーから一気に結果セットを読み込みます。
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
|
3
|
+
class Mysql
|
4
|
+
class Authenticator
|
5
|
+
class CachingSha2Password
|
6
|
+
# @param protocol [Mysql::Protocol]
|
7
|
+
def initialize(protocol)
|
8
|
+
@protocol = protocol
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
def name
|
13
|
+
'caching_sha2_password'
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param passwd [String]
|
17
|
+
# @param scramble [String]
|
18
|
+
# @yield [String] hashed password
|
19
|
+
# @return [Mysql::Packet]
|
20
|
+
def authenticate(passwd, scramble)
|
21
|
+
yield hash_password(passwd, scramble)
|
22
|
+
pkt = @protocol.read
|
23
|
+
data = pkt.to_s
|
24
|
+
if data.size == 2 && data[0] == "\x01"
|
25
|
+
case data[1]
|
26
|
+
when "\x03" # fast_auth_success
|
27
|
+
# OK
|
28
|
+
when "\x04" # perform_full_authentication
|
29
|
+
if @protocol.client_flags & CLIENT_SSL != 0
|
30
|
+
@protocol.write passwd+"\0"
|
31
|
+
elsif !@protocol.get_server_public_key
|
32
|
+
raise 'Authentication requires secure connection'
|
33
|
+
else
|
34
|
+
@protocol.write "\2" # request public key
|
35
|
+
pkt = @protocol.read
|
36
|
+
pkt.utiny # skip
|
37
|
+
pubkey = pkt.to_s
|
38
|
+
hash = (passwd+"\0").unpack("C*").zip(scramble.unpack("C*")).map{|a, b| a ^ b}.pack("C*")
|
39
|
+
enc = OpenSSL::PKey::RSA.new(pubkey).public_encrypt(hash, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
40
|
+
@protocol.write enc
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise "invalid auth reply packet: #{data.inspect}"
|
44
|
+
end
|
45
|
+
pkt = @protocol.read
|
46
|
+
end
|
47
|
+
return pkt
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param passwd [String]
|
51
|
+
# @param scramble [String]
|
52
|
+
# @return [String] hashed password
|
53
|
+
def hash_password(passwd, scramble)
|
54
|
+
return '' if passwd.nil? or passwd.empty?
|
55
|
+
hash1 = Digest::SHA256.digest(passwd)
|
56
|
+
hash2 = Digest::SHA256.digest(hash1)
|
57
|
+
hash3 = Digest::SHA256.digest(hash2 + scramble)
|
58
|
+
hash1.unpack("C*").zip(hash3.unpack("C*")).map{|a, b| a ^ b}.pack("C*")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
class Mysql
|
4
|
+
class Authenticator
|
5
|
+
class MysqlNativePassword
|
6
|
+
# @param protocol [Mysql::Protocol]
|
7
|
+
def initialize(protocol)
|
8
|
+
@protocol = protocol
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
def name
|
13
|
+
'mysql_native_password'
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param passwd [String]
|
17
|
+
# @param scramble [String]
|
18
|
+
# @yield [String] hashed password
|
19
|
+
# @return [Mysql::Packet]
|
20
|
+
def authenticate(passwd, scramble)
|
21
|
+
yield hash_password(passwd, scramble)
|
22
|
+
@protocol.read
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param passwd [String]
|
26
|
+
# @param scramble [String]
|
27
|
+
# @return [String] hashed password
|
28
|
+
def hash_password(passwd, scramble)
|
29
|
+
return '' if passwd.nil? or passwd.empty?
|
30
|
+
hash1 = Digest::SHA1.digest(passwd)
|
31
|
+
hash2 = Digest::SHA1.digest(hash1)
|
32
|
+
hash3 = Digest::SHA1.digest(scramble + hash2)
|
33
|
+
hash1.unpack("C*").zip(hash3.unpack("C*")).map{|a, b| a ^ b}.pack("C*")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
class Mysql
|
4
|
+
class Authenticator
|
5
|
+
class Sha256Password
|
6
|
+
# @param protocol [Mysql::Protocol]
|
7
|
+
def initialize(protocol)
|
8
|
+
@protocol = protocol
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
def name
|
13
|
+
'sha256_password'
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param passwd [String]
|
17
|
+
# @param scramble [String]
|
18
|
+
# @yield [String] hashed password
|
19
|
+
# @return [Mysql::Packet]
|
20
|
+
def authenticate(passwd, scramble)
|
21
|
+
if @protocol.client_flags & CLIENT_SSL != 0
|
22
|
+
yield passwd+"\0"
|
23
|
+
return @protocol.read
|
24
|
+
end
|
25
|
+
yield "\x01" # request public key
|
26
|
+
pkt = @protocol.read
|
27
|
+
data = pkt.to_s
|
28
|
+
if data[0] == "\x01"
|
29
|
+
pkt.utiny # skip
|
30
|
+
pubkey = pkt.to_s
|
31
|
+
hash = (passwd+"\0").unpack("C*").zip(scramble.unpack("C*")).map{|a, b| a ^ b}.pack("C*")
|
32
|
+
enc = OpenSSL::PKey::RSA.new(pubkey).public_encrypt(hash, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
33
|
+
@protocol.write enc
|
34
|
+
pkt = @protocol.read
|
35
|
+
end
|
36
|
+
return pkt
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class Mysql
|
2
|
+
class Authenticator
|
3
|
+
@plugins = {}
|
4
|
+
|
5
|
+
# @param plugin [String]
|
6
|
+
def self.plugin_class(plugin)
|
7
|
+
return @plugins[plugin] if @plugins[plugin]
|
8
|
+
|
9
|
+
raise ClientError, "invalid plugin name: #{plugin}" unless plugin.match?(/\A\w+\z/)
|
10
|
+
begin
|
11
|
+
require_relative "authenticator/#{plugin}"
|
12
|
+
rescue LoadError
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
class_name = plugin.gsub(/(?:^|_)(.)/){$1.upcase}
|
16
|
+
raise ClientError, "#{class_name} is undefined" unless self.const_defined? class_name
|
17
|
+
klass = self.const_get(class_name)
|
18
|
+
@plugins[plugin] = klass
|
19
|
+
return klass
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(protocol)
|
23
|
+
@protocol = protocol
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param plugin [String]
|
27
|
+
def get(plugin)
|
28
|
+
self.class.plugin_class(plugin)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param plugin [String]
|
32
|
+
def get!(plugin)
|
33
|
+
get(plugin) or raise ClientError, "unknown plugin: #{plugin}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def authenticate(user, passwd, db, scramble, plugin_name)
|
37
|
+
plugin = (get(plugin_name) || DummyPlugin).new(@protocol)
|
38
|
+
pkt = plugin.authenticate(passwd, scramble) do |hashed|
|
39
|
+
@protocol.write Protocol::AuthenticationPacket.serialize(@protocol.client_flags, 1024**3, @protocol.charset.number, user, hashed, db, plugin.name)
|
40
|
+
end
|
41
|
+
while true
|
42
|
+
res = Protocol::AuthenticationResultPacket.parse(pkt)
|
43
|
+
case res.result
|
44
|
+
when 0 # OK
|
45
|
+
break
|
46
|
+
when 2 # multi factor auth
|
47
|
+
raise ClientError, 'multi factor authentication is not supported'
|
48
|
+
when 254 # change auth plugin
|
49
|
+
plugin = get!(res.auth_plugin).new(@protocol)
|
50
|
+
pkt = plugin.authenticate(passwd, res.scramble) do |hashed|
|
51
|
+
if passwd.nil? || passwd.empty?
|
52
|
+
@protocol.write "\0"
|
53
|
+
else
|
54
|
+
@protocol.write hashed
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise ClientError, "invalid packet: #{pkt.to_s}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class DummyPlugin
|
64
|
+
# @param protocol [Mysql::Protocol]
|
65
|
+
def initialize(protocol)
|
66
|
+
@protocol = protocol
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [String]
|
70
|
+
def name
|
71
|
+
''
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param passwd [String]
|
75
|
+
# @param scramble [String]
|
76
|
+
# @yield [String] hashed password
|
77
|
+
# @return [Mysql::Packet]
|
78
|
+
def authenticate(passwd, scramble)
|
79
|
+
yield ''
|
80
|
+
@protocol.read
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|