ruby-mysql 2.10.0 → 2.11.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.
- 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
|