Skip to content

Commit d9107c8

Browse files
authored
feat: auto authorisation packet switching (#105)
1 parent 9202753 commit d9107c8

File tree

5 files changed

+78
-27
lines changed

5 files changed

+78
-27
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release Notes
22

3+
## v8.0.1 (2024-01-31)
4+
5+
- Added: auto authorisation packet switching
6+
37
## v8.0.0 (2024-01-29)
48

59
- Change: drop support for < 8.2

src/MySQLReplication/BinLog/BinLogAuthPluginMode.php

+15
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,23 @@
44

55
namespace MySQLReplication\BinLog;
66

7+
use MySQLReplication\Exception\MySQLReplicationException;
8+
79
enum BinLogAuthPluginMode: string
810
{
911
case MysqlNativePassword = 'mysql_native_password';
1012
case CachingSha2Password = 'caching_sha2_password';
13+
14+
public static function make(string $authPluginName): self
15+
{
16+
$authPlugin = self::tryFrom($authPluginName);
17+
if ($authPlugin === null) {
18+
throw new MySQLReplicationException(
19+
MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED,
20+
MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED_CODE
21+
);
22+
}
23+
24+
return $authPlugin;
25+
}
1126
}

src/MySQLReplication/BinLog/BinLogServerInfo.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function __construct(
1717
public string $serverVersion,
1818
public int $connectionId,
1919
public string $salt,
20-
public ?BinLogAuthPluginMode $authPlugin,
20+
public BinLogAuthPluginMode $authPlugin,
2121
public string $versionName,
2222
public float $versionRevision
2323
) {
@@ -94,7 +94,7 @@ public static function make(string $data, string $version): self
9494
$serverVersion,
9595
$connectionId,
9696
$salt,
97-
BinLogAuthPluginMode::tryFrom($authPlugin),
97+
BinLogAuthPluginMode::make($authPlugin),
9898
self::parseVersion($serverVersion),
9999
self::parseRevision($version)
100100
);

src/MySQLReplication/BinLog/BinLogSocketConnect.php

+43-25
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
use MySQLReplication\BinaryDataReader\BinaryDataReader;
88
use MySQLReplication\Config\Config;
9-
use MySQLReplication\Exception\MySQLReplicationException;
109
use MySQLReplication\Gtid\GtidCollection;
1110
use MySQLReplication\Repository\RepositoryInterface;
1211
use MySQLReplication\Socket\SocketInterface;
@@ -17,6 +16,7 @@ class BinLogSocketConnect
1716
private const COM_BINLOG_DUMP = 0x12;
1817
private const COM_REGISTER_SLAVE = 0x15;
1918
private const COM_BINLOG_DUMP_GTID = 0x1e;
19+
private const AUTH_SWITCH_PACKET = 254;
2020
/**
2121
* https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase.html 00 FE
2222
*/
@@ -47,7 +47,8 @@ public function __construct(
4747
'Server version name: ' . $this->binLogServerInfo->versionName . ', revision: ' . $this->binLogServerInfo->versionRevision
4848
);
4949

50-
$this->authenticate();
50+
51+
$this->authenticate($this->binLogServerInfo->authPlugin);
5152
$this->getBinlogStream();
5253
}
5354

@@ -110,56 +111,60 @@ private function isWriteSuccessful(string $data): void
110111
}
111112
}
112113

113-
private function authenticate(): void
114+
private function authenticate(BinLogAuthPluginMode $authPlugin): void
114115
{
115-
if ($this->binLogServerInfo->authPlugin === null) {
116-
throw new MySQLReplicationException(
117-
MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED,
118-
MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED_CODE
119-
);
120-
}
121-
122116
$this->logger->info(
123-
'Trying to authenticate user: ' . $this->config->user . ' using ' . $this->binLogServerInfo->authPlugin->value . ' plugin'
117+
'Trying to authenticate user: ' . $this->config->user . ' using ' . $authPlugin->value . ' default plugin'
124118
);
125119

126120
$data = pack('L', self::getCapabilities());
127121
$data .= pack('L', $this->binaryDataMaxLength);
128122
$data .= chr(33);
129123
$data .= str_repeat(chr(0), 23);
130124
$data .= $this->config->user . chr(0);
131-
132-
$auth = '';
133-
if ($this->binLogServerInfo->authPlugin === BinLogAuthPluginMode::MysqlNativePassword) {
134-
$auth = $this->authenticateMysqlNativePasswordPlugin();
135-
} elseif ($this->binLogServerInfo->authPlugin === BinLogAuthPluginMode::CachingSha2Password) {
136-
$auth = $this->authenticateCachingSha2PasswordPlugin();
137-
}
138-
125+
$auth = $this->getAuthData($authPlugin, $this->binLogServerInfo->salt);
139126
$data .= chr(strlen($auth)) . $auth;
140-
$data .= $this->binLogServerInfo->authPlugin->value . chr(0);
127+
$data .= $authPlugin->value . chr(0);
141128
$str = pack('L', strlen($data));
142129
$s = $str[0] . $str[1] . $str[2];
143130
$data = $s . chr(1) . $data;
144131

145132
$this->socket->writeToSocket($data);
146-
$this->getResponse();
133+
$response = $this->getResponse();
134+
135+
// Check for AUTH_SWITCH_PACKET
136+
if (isset($response[0]) && ord($response[0]) === self::AUTH_SWITCH_PACKET) {
137+
$this->switchAuth($response);
138+
}
147139

148140
$this->logger->info('User authenticated');
149141
}
150142

151-
private function authenticateCachingSha2PasswordPlugin(): string
143+
private function getAuthData(?BinLogAuthPluginMode $authPlugin, string $salt): string
144+
{
145+
if ($authPlugin === BinLogAuthPluginMode::MysqlNativePassword) {
146+
return $this->authenticateMysqlNativePasswordPlugin($salt);
147+
}
148+
149+
if ($authPlugin === BinLogAuthPluginMode::CachingSha2Password) {
150+
return $this->authenticateCachingSha2PasswordPlugin($salt);
151+
}
152+
153+
return '';
154+
}
155+
156+
private function authenticateCachingSha2PasswordPlugin(string $salt): string
152157
{
153158
$hash1 = hash('sha256', $this->config->password, true);
154159
$hash2 = hash('sha256', $hash1, true);
155-
$hash3 = hash('sha256', $hash2 . $this->binLogServerInfo->salt, true);
160+
$hash3 = hash('sha256', $hash2 . $salt, true);
156161
return $hash1 ^ $hash3;
157162
}
158163

159-
private function authenticateMysqlNativePasswordPlugin(): string
164+
private function authenticateMysqlNativePasswordPlugin(string $salt): string
160165
{
161166
$hash1 = sha1($this->config->password, true);
162-
$hash2 = sha1($this->binLogServerInfo->salt . sha1(sha1($this->config->password, true), true), true);
167+
$hash2 = sha1($salt . sha1(sha1($this->config->password, true), true), true);
163168
return $hash1 ^ $hash2;
164169
}
165170

@@ -316,4 +321,17 @@ private function setBinLogDump(): void
316321

317322
$this->logger->info('Set binlog to start from: ' . $binFileName . ':' . $binFilePos);
318323
}
324+
325+
private function switchAuth(string $response): void
326+
{
327+
// skip AUTH_SWITCH_PACKET byte
328+
$offset = 1;
329+
$authPluginSwitched = BinLogAuthPluginMode::make(BinaryDataReader::decodeNullLength($response, $offset));
330+
$salt = BinaryDataReader::decodeNullLength($response, $offset);
331+
$auth = $this->getAuthData($authPluginSwitched, $salt);
332+
333+
$this->logger->info('Auth switch packet received, switching to ' . $authPluginSwitched->value);
334+
335+
$this->socket->writeToSocket(pack('L', (strlen($auth)) | (3 << 24)) . $auth);
336+
}
319337
}

src/MySQLReplication/BinaryDataReader/BinaryDataReader.php

+14
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,18 @@ public static function unpack(string $format, string $string): array
325325
}
326326
return [];
327327
}
328+
329+
public static function decodeNullLength(string $data, int &$offset = 0): string
330+
{
331+
$length = strpos($data, chr(0), $offset);
332+
if ($length === false) {
333+
return '';
334+
}
335+
336+
$length -= $offset;
337+
$result = substr($data, $offset, $length);
338+
$offset += $length + 1;
339+
340+
return $result;
341+
}
328342
}

0 commit comments

Comments
 (0)