add tests for old_password. Fix py3 old password authentication. empty plugin name occurs with PLUGIN_AUTH meaning old passwords
This commit is contained in:
@@ -195,7 +195,8 @@ def _hash_password_323(password):
|
|||||||
add = 7
|
add = 7
|
||||||
nr2 = 0x12345671
|
nr2 = 0x12345671
|
||||||
|
|
||||||
for c in [byte2int(x) for x in password if x not in (' ', '\t')]:
|
# x in py3 is numbers, p27 is chars
|
||||||
|
for c in [byte2int(x) for x in password if x not in (' ', '\t', 32, 9)]:
|
||||||
nr ^= (((nr & 63) + add) * c) + (nr << 8) & 0xFFFFFFFF
|
nr ^= (((nr & 63) + add) * c) + (nr << 8) & 0xFFFFFFFF
|
||||||
nr2 = (nr2 + ((nr2 << 8) ^ nr)) & 0xFFFFFFFF
|
nr2 = (nr2 + ((nr2 << 8) ^ nr)) & 0xFFFFFFFF
|
||||||
add = (add + c) & 0xFFFFFFFF
|
add = (add + c) & 0xFFFFFFFF
|
||||||
@@ -1088,9 +1089,9 @@ class Connection(object):
|
|||||||
|
|
||||||
if auth_packet.is_auth_switch_request():
|
if auth_packet.is_auth_switch_request():
|
||||||
# https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest
|
# https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest
|
||||||
if self.server_capabilities & CLIENT.PLUGIN_AUTH:
|
|
||||||
auth_packet.read_uint8() # 0xfe packet identifier
|
auth_packet.read_uint8() # 0xfe packet identifier
|
||||||
plugin_name = auth_packet.read_string()
|
plugin_name = auth_packet.read_string()
|
||||||
|
if self.server_capabilities & CLIENT.PLUGIN_AUTH and plugin_name is not None:
|
||||||
auth_packet = self._process_auth(plugin_name, auth_packet)
|
auth_packet = self._process_auth(plugin_name, auth_packet)
|
||||||
else:
|
else:
|
||||||
# send legacy handshake
|
# send legacy handshake
|
||||||
@@ -1105,21 +1106,21 @@ class Connection(object):
|
|||||||
try:
|
try:
|
||||||
return handler.authenticate(auth_pkt)
|
return handler.authenticate(auth_pkt)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if plugin_name != 'dialog':
|
if plugin_name != b'dialog':
|
||||||
raise err.OperationalError(2059, "Authentication plugin '%s'" +
|
raise err.OperationalError(2059, "Authentication plugin '%s'" +
|
||||||
" not loaded: - missing authenticate method" % plugin)
|
" not loaded: - missing authenticate method" % plugin)
|
||||||
else:
|
else:
|
||||||
handler = None
|
handler = None
|
||||||
if plugin_name == "mysql_native_password":
|
if plugin_name == b"mysql_native_password":
|
||||||
# https://dev.mysql.com/doc/internals/en/secure-password-authentication.html#packet-Authentication::Native41
|
# https://dev.mysql.com/doc/internals/en/secure-password-authentication.html#packet-Authentication::Native41
|
||||||
data = _scramble(self.password.encode('latin1'), auth_packet.read_all()) + b'\0'
|
data = _scramble(self.password.encode('latin1'), auth_packet.read_all()) + b'\0'
|
||||||
elif plugin_name == "mysql_old_password":
|
elif plugin_name == b"mysql_old_password":
|
||||||
# https://dev.mysql.com/doc/internals/en/old-password-authentication.html
|
# https://dev.mysql.com/doc/internals/en/old-password-authentication.html
|
||||||
data = _scramble_323(self.password.encode('latin1'), auth_packet.read_all()) + b'\0'
|
data = _scramble_323(self.password.encode('latin1'), auth_packet.read_all()) + b'\0'
|
||||||
elif plugin_name == "mysql_clear_password":
|
elif plugin_name == b"mysql_clear_password":
|
||||||
# https://dev.mysql.com/doc/internals/en/clear-text-authentication.html
|
# https://dev.mysql.com/doc/internals/en/clear-text-authentication.html
|
||||||
data = self.password.encode('latin1') + b'\0'
|
data = self.password.encode('latin1') + b'\0'
|
||||||
elif plugin_name == "dialog":
|
elif plugin_name == b"dialog":
|
||||||
pkt = auth_packet
|
pkt = auth_packet
|
||||||
while True:
|
while True:
|
||||||
flag = pkt.read_uint8()
|
flag = pkt.read_uint8()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
|
import sys
|
||||||
import pymysql
|
import pymysql
|
||||||
import time
|
import time
|
||||||
import unittest2
|
import unittest2
|
||||||
@@ -7,12 +8,15 @@ from pymysql.tests import base
|
|||||||
|
|
||||||
|
|
||||||
class TempUser:
|
class TempUser:
|
||||||
def __init__(self, c, user, db, auth, authdata=None):
|
def __init__(self, c, user, db, auth=None, authdata=None, password=None):
|
||||||
self._c = c
|
self._c = c
|
||||||
self._user = user
|
self._user = user
|
||||||
self._db = db
|
self._db = db
|
||||||
create = "CREATE USER %s" \
|
create = "CREATE USER " + user
|
||||||
" IDENTIFIED WITH %s" % (user, auth)
|
if password is not None:
|
||||||
|
create += " IDENTIFIED BY '%s'" % password
|
||||||
|
elif auth is not None:
|
||||||
|
create += " IDENTIFIED WITH %s" % auth
|
||||||
if authdata is not None:
|
if authdata is not None:
|
||||||
create += " AS '%s'" % authdata
|
create += " AS '%s'" % authdata
|
||||||
try:
|
try:
|
||||||
@@ -43,6 +47,7 @@ class TestAuthentication(base.PyMySQLTestCase):
|
|||||||
two_questions_found = False
|
two_questions_found = False
|
||||||
three_attempts_found = False
|
three_attempts_found = False
|
||||||
pam_found = False
|
pam_found = False
|
||||||
|
mysql_old_password_found = False
|
||||||
|
|
||||||
import os
|
import os
|
||||||
osuser = os.environ.get('USER')
|
osuser = os.environ.get('USER')
|
||||||
@@ -61,12 +66,12 @@ class TestAuthentication(base.PyMySQLTestCase):
|
|||||||
if (r[1], r[2], r[3]) == (u'ACTIVE', u'AUTHENTICATION', u'auth_socket.so'):
|
if (r[1], r[2], r[3]) == (u'ACTIVE', u'AUTHENTICATION', u'auth_socket.so'):
|
||||||
socket_plugin_name = r[0]
|
socket_plugin_name = r[0]
|
||||||
socket_found = True
|
socket_found = True
|
||||||
if (r[1], r[2], r[3]) == (u'ACTIVE', u'AUTHENTICATION', u'dialog_examples.so'):
|
elif (r[1], r[2], r[3]) == (u'ACTIVE', u'AUTHENTICATION', u'dialog_examples.so'):
|
||||||
if r[0] == 'two_questions':
|
if r[0] == 'two_questions':
|
||||||
two_questions_found = True
|
two_questions_found = True
|
||||||
elif r[0] == 'three_attempts':
|
elif r[0] == 'three_attempts':
|
||||||
three_attempts_found = True
|
three_attempts_found = True
|
||||||
if (r[0], r[1], r[2]) == (u'pam', u'ACTIVE', u'AUTHENTICATION'):
|
elif (r[0], r[1], r[2]) == (u'pam', u'ACTIVE', u'AUTHENTICATION'):
|
||||||
pam_found = True
|
pam_found = True
|
||||||
pam_plugin_name = r[3].split('.')[0]
|
pam_plugin_name = r[3].split('.')[0]
|
||||||
if pam_plugin_name == 'auth_pam':
|
if pam_plugin_name == 'auth_pam':
|
||||||
@@ -78,6 +83,8 @@ class TestAuthentication(base.PyMySQLTestCase):
|
|||||||
# https://mariadb.com/kb/en/mariadb/pam-authentication-plugin/
|
# https://mariadb.com/kb/en/mariadb/pam-authentication-plugin/
|
||||||
|
|
||||||
# Names differ but functionality is close
|
# Names differ but functionality is close
|
||||||
|
elif (r[0], r[1], r[2]) == (u'mysql_old_password', u'ACTIVE', u'AUTHENTICATION'):
|
||||||
|
mysql_old_password_found = True
|
||||||
|
|
||||||
def test_plugin(self):
|
def test_plugin(self):
|
||||||
# Bit of an assumption that the current user is a native password
|
# Bit of an assumption that the current user is a native password
|
||||||
@@ -163,6 +170,49 @@ class TestAuthentication(base.PyMySQLTestCase):
|
|||||||
return
|
return
|
||||||
# else we had 'bad guess at password' work with pam. Well cool
|
# else we had 'bad guess at password' work with pam. Well cool
|
||||||
|
|
||||||
|
# select old_password("crummy p\tassword");
|
||||||
|
#| old_password("crummy p\tassword") |
|
||||||
|
#| 2a01785203b08770 |
|
||||||
|
@unittest2.skipUnless(socket_auth, "connection to unix_socket required")
|
||||||
|
@unittest2.skipUnless(mysql_old_password_found, "no mysql_old_password plugin")
|
||||||
|
def testMySQLOldPasswordAuth(self):
|
||||||
|
if self.mysql_server_is(self.connections[0], (5, 7, 0)):
|
||||||
|
raise unittest2.SkipTest('Old passwords aren\'t supported in 5.7')
|
||||||
|
# pymysql.err.OperationalError: (1045, "Access denied for user 'old_pass_user'@'localhost' (using password: YES)")
|
||||||
|
# from login in MySQL-5.6
|
||||||
|
if self.mysql_server_is(self.connections[0], (5, 6, 0)):
|
||||||
|
raise unittest2.SkipTest('Old passwords don\'t authenticate in 5.6')
|
||||||
|
db = self.db.copy()
|
||||||
|
db['password'] = "crummy p\tassword"
|
||||||
|
with self.connections[0] as c:
|
||||||
|
# deprecated in 5.6
|
||||||
|
if sys.version_info[0:2] >= (3,2) and self.mysql_server_is(self.connections[0], (5, 6, 0)):
|
||||||
|
with self.assertWarns(pymysql.err.Warning) as cm:
|
||||||
|
c.execute("SELECT OLD_PASSWORD('%s')" % db['password'])
|
||||||
|
else:
|
||||||
|
c.execute("SELECT OLD_PASSWORD('%s')" % db['password'])
|
||||||
|
v = c.fetchone()[0]
|
||||||
|
self.assertEqual(v, '2a01785203b08770')
|
||||||
|
# only works in MariaDB and MySQL-5.6 - can't separate out by version
|
||||||
|
#if self.mysql_server_is(self.connections[0], (5, 5, 0)):
|
||||||
|
# with TempUser(c, 'old_pass_user@localhost',
|
||||||
|
# self.databases[0]['db'], 'mysql_old_password', '2a01785203b08770') as u:
|
||||||
|
# cur = pymysql.connect(user='old_pass_user', **db).cursor()
|
||||||
|
# cur.execute("SELECT VERSION()")
|
||||||
|
c.execute("SELECT @@secure_auth")
|
||||||
|
secure_auth_setting = c.fetchone()[0]
|
||||||
|
c.execute('set old_passwords=1')
|
||||||
|
# pymysql.err.Warning: 'pre-4.1 password hash' is deprecated and will be removed in a future release. Please use post-4.1 password hash instead
|
||||||
|
if sys.version_info[0:2] >= (3,2) and self.mysql_server_is(self.connections[0], (5, 6, 0)):
|
||||||
|
with self.assertWarns(pymysql.err.Warning) as cm:
|
||||||
|
c.execute('set global secure_auth=0')
|
||||||
|
else:
|
||||||
|
c.execute('set global secure_auth=0')
|
||||||
|
with TempUser(c, 'old_pass_user@localhost',
|
||||||
|
self.databases[0]['db'], password=db['password']) as u:
|
||||||
|
cur = pymysql.connect(user='old_pass_user', **db).cursor()
|
||||||
|
cur.execute("SELECT VERSION()")
|
||||||
|
c.execute('set global secure_auth=%r' % secure_auth_setting)
|
||||||
|
|
||||||
class TestConnection(base.PyMySQLTestCase):
|
class TestConnection(base.PyMySQLTestCase):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user