|
@@ -12,13 +12,18 @@ import org.slf4j.LoggerFactory;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.AuthSwitchResponsePacket;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.ClientAuthenticationPacket;
|
|
|
+import com.alibaba.otter.canal.parse.driver.mysql.packets.client.ClientAuthenticationSHA2Packet;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QuitCommandPacket;
|
|
|
+import com.alibaba.otter.canal.parse.driver.mysql.packets.client.SslRequestCommandPacket;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.*;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannelPool;
|
|
|
+import com.alibaba.otter.canal.parse.driver.mysql.ssl.SslInfo;
|
|
|
+import com.alibaba.otter.canal.parse.driver.mysql.ssl.SslMode;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.MSC;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter;
|
|
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
|
|
+import static com.alibaba.otter.canal.parse.driver.mysql.packets.Capability.CLIENT_SSL;
|
|
|
|
|
|
/**
|
|
|
* 基于mysql socket协议的链接实现
|
|
@@ -32,8 +37,8 @@ public class MysqlConnector {
|
|
|
private InetSocketAddress address;
|
|
|
private String username;
|
|
|
private String password;
|
|
|
+ private SslInfo sslInfo;
|
|
|
|
|
|
- private byte charsetNumber = 33;
|
|
|
private String defaultSchema;
|
|
|
private int soTimeout = 30 * 1000;
|
|
|
private int connTimeout = 5 * 1000;
|
|
@@ -62,20 +67,26 @@ public class MysqlConnector {
|
|
|
this.password = password;
|
|
|
}
|
|
|
|
|
|
- public MysqlConnector(InetSocketAddress address, String username, String password, byte charsetNumber,
|
|
|
+ public MysqlConnector(InetSocketAddress address, String username, String password,
|
|
|
String defaultSchema){
|
|
|
this(address, username, password);
|
|
|
|
|
|
- this.charsetNumber = charsetNumber;
|
|
|
this.defaultSchema = defaultSchema;
|
|
|
}
|
|
|
|
|
|
+ public MysqlConnector(InetSocketAddress address, String username, String password,
|
|
|
+ String defaultSchema, SslInfo sslInfo) {
|
|
|
+ this(address, username, password, defaultSchema);
|
|
|
+ this.sslInfo = sslInfo;
|
|
|
+ }
|
|
|
+
|
|
|
public void connect() throws IOException {
|
|
|
if (connected.compareAndSet(false, true)) {
|
|
|
try {
|
|
|
channel = SocketChannelPool.open(address);
|
|
|
logger.info("connect MysqlConnection to {}...", address);
|
|
|
negotiate(channel);
|
|
|
+ printSslStatus();
|
|
|
} catch (Exception e) {
|
|
|
disconnect();
|
|
|
throw new IOException("connect " + this.address + " failure", e);
|
|
@@ -85,6 +96,27 @@ public class MysqlConnector {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void printSslStatus() {
|
|
|
+ try {
|
|
|
+ MysqlQueryExecutor executor = new MysqlQueryExecutor(this);
|
|
|
+ ResultSetPacket result = executor.query("SHOW STATUS LIKE 'Ssl_version'");
|
|
|
+ String sslVersion = "";
|
|
|
+ if (result.getFieldValues() != null && result.getFieldValues().size() >= 2) {
|
|
|
+ sslVersion = result.getFieldValues().get(1);
|
|
|
+ }
|
|
|
+ result = executor.query("SHOW STATUS LIKE 'Ssl_cipher'");
|
|
|
+ String sslCipher = "";
|
|
|
+ if (result.getFieldValues() != null && result.getFieldValues().size() >= 2) {
|
|
|
+ sslCipher = result.getFieldValues().get(1);
|
|
|
+ }
|
|
|
+ logger.info("connect MysqlConnection in sslMode {}, Ssl_version:{}, Ssl_cipher:{}",
|
|
|
+ (sslInfo != null ? sslInfo.getSslMode() : SslMode.DISABLED), sslVersion, sslCipher);
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.info("Can't show SSL status, server may not standard MySQL server: {}", e.toString());
|
|
|
+ logger.debug("show SSL status exception", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public void reconnect() throws IOException {
|
|
|
disconnect();
|
|
|
connect();
|
|
@@ -131,7 +163,6 @@ public class MysqlConnector {
|
|
|
|
|
|
public MysqlConnector fork() {
|
|
|
MysqlConnector connector = new MysqlConnector();
|
|
|
- connector.setCharsetNumber(getCharsetNumber());
|
|
|
connector.setDefaultSchema(getDefaultSchema());
|
|
|
connector.setAddress(getAddress());
|
|
|
connector.setPassword(password);
|
|
@@ -140,6 +171,7 @@ public class MysqlConnector {
|
|
|
connector.setSendBufferSize(getSendBufferSize());
|
|
|
connector.setSoTimeout(getSoTimeout());
|
|
|
connector.setConnTimeout(connTimeout);
|
|
|
+ connector.setSslInfo(getSslInfo());
|
|
|
return connector;
|
|
|
}
|
|
|
|
|
@@ -170,29 +202,53 @@ public class MysqlConnector {
|
|
|
}
|
|
|
HandshakeInitializationPacket handshakePacket = new HandshakeInitializationPacket();
|
|
|
handshakePacket.fromBytes(body);
|
|
|
+ SslMode sslMode = sslInfo != null ? sslInfo.getSslMode() : SslMode.DISABLED;
|
|
|
+ if (sslMode != SslMode.DISABLED) {
|
|
|
+ boolean serverSupportSsl = (handshakePacket.serverCapabilities & CLIENT_SSL) > 0;
|
|
|
+ if (!serverSupportSsl) {
|
|
|
+ throw new IOException("MySQL Server does not support SSL: " + address
|
|
|
+ + " serverCapabilities: " + handshakePacket.serverCapabilities);
|
|
|
+ }
|
|
|
+ byte[] sslPacket = new SslRequestCommandPacket(handshakePacket.serverCharsetNumber).toBytes();
|
|
|
+ HeaderPacket sslHeader = new HeaderPacket();
|
|
|
+ sslHeader.setPacketBodyLength(sslPacket.length);
|
|
|
+ sslHeader.setPacketSequenceNumber((byte)(header.getPacketSequenceNumber() + 1));
|
|
|
+ header.setPacketSequenceNumber((byte)(header.getPacketSequenceNumber() + 1));
|
|
|
+ PacketManager.writePkg(channel, sslHeader.toBytes(), sslPacket);
|
|
|
+ channel = SocketChannelPool.connectSsl(channel, sslInfo);
|
|
|
+ this.channel = channel;
|
|
|
+ }
|
|
|
if (handshakePacket.protocolVersion != MSC.DEFAULT_PROTOCOL_VERSION) {
|
|
|
// HandshakeV9
|
|
|
- auth323(channel, (byte) (header.getPacketSequenceNumber() + 1), handshakePacket.seed);
|
|
|
+ auth323(channel, (byte)(header.getPacketSequenceNumber() + 1), handshakePacket.seed);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
connectionId = handshakePacket.threadId; // 记录一下connection
|
|
|
serverVersion = handshakePacket.serverVersion; // 记录serverVersion
|
|
|
logger.info("handshake initialization packet received, prepare the client authentication packet to send");
|
|
|
- ClientAuthenticationPacket clientAuth = new ClientAuthenticationPacket();
|
|
|
- clientAuth.setCharsetNumber(charsetNumber);
|
|
|
+ logger.info("auth plugin: {}", new String(handshakePacket.authPluginName));
|
|
|
+ boolean isSha2Password = false;
|
|
|
+ ClientAuthenticationPacket clientAuth;
|
|
|
+ if ("caching_sha2_password".equals(new String(handshakePacket.authPluginName))) {
|
|
|
+ clientAuth = new ClientAuthenticationSHA2Packet();
|
|
|
+ isSha2Password = true;
|
|
|
+ } else {
|
|
|
+ clientAuth = new ClientAuthenticationPacket();
|
|
|
+ }
|
|
|
+ clientAuth.setCharsetNumber(handshakePacket.serverCharsetNumber);
|
|
|
|
|
|
clientAuth.setUsername(username);
|
|
|
clientAuth.setPassword(password);
|
|
|
clientAuth.setServerCapabilities(handshakePacket.serverCapabilities);
|
|
|
clientAuth.setDatabaseName(defaultSchema);
|
|
|
clientAuth.setScrumbleBuff(joinAndCreateScrumbleBuff(handshakePacket));
|
|
|
- clientAuth.setAuthPluginName("mysql_native_password".getBytes());
|
|
|
+ clientAuth.setAuthPluginName(handshakePacket.authPluginName);
|
|
|
|
|
|
byte[] clientAuthPkgBody = clientAuth.toBytes();
|
|
|
HeaderPacket h = new HeaderPacket();
|
|
|
h.setPacketBodyLength(clientAuthPkgBody.length);
|
|
|
- h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
|
|
|
+ h.setPacketSequenceNumber((byte)(header.getPacketSequenceNumber() + 1));
|
|
|
|
|
|
PacketManager.writePkg(channel, h.toBytes(), clientAuthPkgBody);
|
|
|
logger.info("client authentication packet is sent out.");
|
|
@@ -205,57 +261,64 @@ public class MysqlConnector {
|
|
|
assert body != null;
|
|
|
byte marker = body[0];
|
|
|
if (marker == -2 || marker == 1) {
|
|
|
- byte[] authData = null;
|
|
|
- String pluginName = null;
|
|
|
- if (marker == 1) {
|
|
|
- AuthSwitchRequestMoreData packet = new AuthSwitchRequestMoreData();
|
|
|
- packet.fromBytes(body);
|
|
|
- authData = packet.authData;
|
|
|
- } else {
|
|
|
- AuthSwitchRequestPacket packet = new AuthSwitchRequestPacket();
|
|
|
- packet.fromBytes(body);
|
|
|
- authData = packet.authData;
|
|
|
- pluginName = packet.authName;
|
|
|
- logger.info("auth switch pluginName is {}.", pluginName);
|
|
|
- }
|
|
|
-
|
|
|
- byte[] encryptedPassword = null;
|
|
|
- if ("mysql_clear_password".equals(pluginName)) {
|
|
|
- encryptedPassword = getPassword().getBytes();
|
|
|
- header = authSwitchAfterAuth(encryptedPassword, header);
|
|
|
- body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
- } else if ("mysql_native_password".equals(pluginName)) {
|
|
|
- try {
|
|
|
- encryptedPassword = MySQLPasswordEncrypter.scramble411(getPassword().getBytes(), authData);
|
|
|
- } catch (NoSuchAlgorithmException e) {
|
|
|
- throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
|
|
|
- }
|
|
|
- header = authSwitchAfterAuth(encryptedPassword, header);
|
|
|
+ if (isSha2Password && body[1] == 3) {
|
|
|
+ // sha2 auth ok
|
|
|
+ logger.info("caching_sha2_password auth success.");
|
|
|
+ header = PacketManager.readHeader(channel, 4);
|
|
|
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
- } else if ("caching_sha2_password".equals(pluginName)) {
|
|
|
- byte[] scramble = authData;
|
|
|
- try {
|
|
|
- encryptedPassword = MySQLPasswordEncrypter.scrambleCachingSha2(getPassword().getBytes(), scramble);
|
|
|
- } catch (DigestException e) {
|
|
|
- throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
|
|
|
+ } else {
|
|
|
+ byte[] authData = null;
|
|
|
+ String pluginName = null;
|
|
|
+ if (marker == 1) {
|
|
|
+ AuthSwitchRequestMoreData packet = new AuthSwitchRequestMoreData();
|
|
|
+ packet.fromBytes(body);
|
|
|
+ authData = packet.authData;
|
|
|
+ } else {
|
|
|
+ AuthSwitchRequestPacket packet = new AuthSwitchRequestPacket();
|
|
|
+ packet.fromBytes(body);
|
|
|
+ authData = packet.authData;
|
|
|
+ pluginName = packet.authName;
|
|
|
+ logger.info("auth switch pluginName is {}.", pluginName);
|
|
|
}
|
|
|
- header = authSwitchAfterAuth(encryptedPassword, header);
|
|
|
- body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
- assert body != null;
|
|
|
- if (body[0] == 0x01 && body[1] == 0x04) {
|
|
|
- // fixed issue https://github.com/alibaba/canal/pull/4767, support mysql 8.0.30+
|
|
|
- header = cachingSha2PasswordFullAuth(channel, header, getPassword().getBytes(), scramble);
|
|
|
+
|
|
|
+ byte[] encryptedPassword = null;
|
|
|
+ if ("mysql_clear_password".equals(pluginName)) {
|
|
|
+ encryptedPassword = getPassword().getBytes();
|
|
|
+ header = authSwitchAfterAuth(encryptedPassword, header);
|
|
|
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
+ } else if ("mysql_native_password".equals(pluginName)) {
|
|
|
+ try {
|
|
|
+ encryptedPassword = MySQLPasswordEncrypter.scramble411(getPassword().getBytes(), authData);
|
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
|
+ throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
|
|
|
+ }
|
|
|
+ header = authSwitchAfterAuth(encryptedPassword, header);
|
|
|
+ body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
+ } else if ("caching_sha2_password".equals(pluginName)) {
|
|
|
+ byte[] scramble = authData;
|
|
|
+ try {
|
|
|
+ encryptedPassword = MySQLPasswordEncrypter.scrambleCachingSha2(getPassword().getBytes(),
|
|
|
+ scramble);
|
|
|
+ } catch (DigestException e) {
|
|
|
+ throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
|
|
|
+ }
|
|
|
+ header = authSwitchAfterAuth(encryptedPassword, header);
|
|
|
+ body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
+ assert body != null;
|
|
|
+ if (body[0] == 0x01 && body[1] == 0x04) {
|
|
|
+ // fixed issue https://github.com/alibaba/canal/pull/4767, support mysql 8.0.30+
|
|
|
+ header = cachingSha2PasswordFullAuth(channel, header, getPassword().getBytes(), scramble);
|
|
|
+ body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
+ } else {
|
|
|
+ header = PacketManager.readHeader(channel, 4);
|
|
|
+ body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
+ }
|
|
|
} else {
|
|
|
- header = PacketManager.readHeader(channel, 4);
|
|
|
+ header = authSwitchAfterAuth(encryptedPassword, header);
|
|
|
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
}
|
|
|
- } else {
|
|
|
- header = authSwitchAfterAuth(encryptedPassword, header);
|
|
|
- body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
|
|
}
|
|
|
}
|
|
|
- assert body != null;
|
|
|
if (body[0] < 0) {
|
|
|
if (body[0] == -1) {
|
|
|
ErrorPacket err = new ErrorPacket();
|
|
@@ -380,14 +443,6 @@ public class MysqlConnector {
|
|
|
this.username = username;
|
|
|
}
|
|
|
|
|
|
- public byte getCharsetNumber() {
|
|
|
- return charsetNumber;
|
|
|
- }
|
|
|
-
|
|
|
- public void setCharsetNumber(byte charsetNumber) {
|
|
|
- this.charsetNumber = charsetNumber;
|
|
|
- }
|
|
|
-
|
|
|
public String getDefaultSchema() {
|
|
|
return defaultSchema;
|
|
|
}
|
|
@@ -464,4 +519,11 @@ public class MysqlConnector {
|
|
|
return serverVersion;
|
|
|
}
|
|
|
|
|
|
+ public SslInfo getSslInfo() {
|
|
|
+ return sslInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setSslInfo(SslInfo sslInfo) {
|
|
|
+ this.sslInfo = sslInfo;
|
|
|
+ }
|
|
|
}
|