Skip to content

Commit

Permalink
Fixes for Azure-MySQL connections
Browse files Browse the repository at this point in the history
Azure has a couple of differences from stock mysql.
First, it always sends a "switch auth" packet back from the first auth
request.  If you google "azure mysql handshake" you'll find a bunch of
other libraries that had to deal with this.

Second, it's rather persnickitty with the response to this; if we send
the response a byte or so at a time azure just closes our connection.
  • Loading branch information
osheroff committed Apr 20, 2019
1 parent 777448d commit 7a93eec
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.github.shyiko.mysql.binlog.network.protocol.PacketChannel;
import com.github.shyiko.mysql.binlog.network.protocol.ResultSetRowPacket;
import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateNativePasswordCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.Command;
import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogGtidCommand;
Expand Down Expand Up @@ -722,8 +723,40 @@ private void authenticate(GreetingPacket greetingPacket) throws IOException {
ErrorPacket errorPacket = new ErrorPacket(bytes);
throw new AuthenticationException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
errorPacket.getSqlState());
} else if (authenticationResult[0] == (byte) 0xFE) {
switchAuthentication(authenticationResult);
} else {
throw new AuthenticationException("Unexpected authentication result (" + authenticationResult[0] + ")");
}
}
}

private void switchAuthentication(byte[] authenticationResult) throws IOException {
/*
Azure-MySQL likes to tell us to switch authentication methods, even though
we haven't advertised that we support any. It uses this for some-odd
reason to send the real password scramble.
*/
ByteArrayInputStream buffer = new ByteArrayInputStream(authenticationResult);
buffer.read(1);

String authName = buffer.readZeroTerminatedString();
if ("mysql_native_password".equals(authName)) {
String scramble = buffer.readZeroTerminatedString();

Command switchCommand = new AuthenticateNativePasswordCommand(scramble, password);
channel.writeBuffered(switchCommand, 3);
byte[] authResult = channel.read();

if (authResult[0] != (byte) 0x00) {
byte[] bytes = Arrays.copyOfRange(authResult, 1, authResult.length);
ErrorPacket errorPacket = new ErrorPacket(bytes);
throw new AuthenticationException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
errorPacket.getSqlState());
}
throw new AuthenticationException("Unexpected authentication result (" + authenticationResult[0] + ")");
return;
} else {
throw new AuthenticationException("Unsupported authentication type: " + authName);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ public void write(Command command, int packetNumber) throws IOException {
outputStream.flush();
}

/*
Azure's MySQL has bizarre network properties that force us to write an
auth-response challenge in one shot, lest their hair catch on fire and
forcibly disconnect us.
*/
public void writeBuffered(Command command, int packetNumber) throws IOException {
byte[] body = command.toByteArray();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
buffer.writeInteger(body.length, 3); // packet length
buffer.writeInteger(packetNumber, 1);
buffer.write(body, 0, body.length);
buffer.flush();
socket.getOutputStream().write(buffer.toByteArray());
}

public void write(Command command) throws IOException {
write(command, 0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public byte[] toByteArray() throws IOException {
/**
* see mysql/sql/password.c scramble(...)
*/
private static byte[] passwordCompatibleWithMySQL411(String password, String salt) {
public static byte[] passwordCompatibleWithMySQL411(String password, String salt) {
MessageDigest sha;
try {
sha = MessageDigest.getInstance("SHA-1");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2013 Stanley Shyiko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.shyiko.mysql.binlog.network.protocol.command;

import java.io.IOException;

/**
* @author <a href="mailto:ben.osheroff@gmail.com">Ben Osheroff</a>
*/
public class AuthenticateNativePasswordCommand implements Command {
private final String scramble, password;

public AuthenticateNativePasswordCommand(String scramble, String password) {
this.scramble = scramble;
this.password = password;
}
@Override
public byte[] toByteArray() throws IOException {
return AuthenticateCommand.passwordCompatibleWithMySQL411(password, scramble);
}
}

0 comments on commit 7a93eec

Please sign in to comment.