blob: d0a406e5914b2175a59f40eb2db0a0779d6dec47 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.message.client;
import static org.mariadb.jdbc.util.constants.Capabilities.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.StringTokenizer;
import org.mariadb.jdbc.Configuration;
import org.mariadb.jdbc.client.Context;
import org.mariadb.jdbc.client.socket.Writer;
import org.mariadb.jdbc.client.socket.impl.PacketWriter;
import org.mariadb.jdbc.message.ClientMessage;
import org.mariadb.jdbc.plugin.Credential;
import org.mariadb.jdbc.plugin.authentication.standard.NativePasswordPlugin;
import org.mariadb.jdbc.util.VersionFactory;
/**
* Server handshake response builder. see
* https://mariadb.com/kb/en/connection/#client-handshake-response
*/
public final class HandshakeResponse implements ClientMessage {
private static final String _CLIENT_NAME = "_client_name";
private static final String _CLIENT_VERSION = "_client_version";
private static final String _SERVER_HOST = "_server_host";
private static final String _OS = "_os";
private static final String _THREAD = "_thread";
private static final String _JAVA_VENDOR = "_java_vendor";
private static final String _JAVA_VERSION = "_java_version";
private final String username;
private final CharSequence password;
private final String database;
private final String connectionAttributes;
private final String host;
private final long clientCapabilities;
private final byte exchangeCharset;
private final byte[] seed;
private String authenticationPluginType;
/**
* Object with parsed results
*
* @param credential credential
* @param authenticationPluginType authentication plugin to use
* @param seed server seed
* @param conf configuration
* @param host current host
* @param clientCapabilities client capabilities
* @param exchangeCharset connection charset
*/
public HandshakeResponse(
Credential credential,
String authenticationPluginType,
byte[] seed,
Configuration conf,
String host,
long clientCapabilities,
byte exchangeCharset) {
this.authenticationPluginType = authenticationPluginType;
this.seed = seed;
this.username = credential.getUser();
this.password = credential.getPassword();
this.database = conf.database();
this.connectionAttributes = conf.connectionAttributes();
this.host = host;
this.clientCapabilities = clientCapabilities;
this.exchangeCharset = exchangeCharset;
}
private static void writeStringLengthAscii(Writer encoder, String value) throws IOException {
byte[] valBytes = value.getBytes(StandardCharsets.US_ASCII);
encoder.writeLength(valBytes.length);
encoder.writeBytes(valBytes);
}
private static void writeStringLength(Writer encoder, String value) throws IOException {
byte[] valBytes = value.getBytes(StandardCharsets.UTF_8);
encoder.writeLength(valBytes.length);
encoder.writeBytes(valBytes);
}
private static void writeConnectAttributes(
Writer writer, String connectionAttributes, String host) throws IOException {
PacketWriter tmpWriter = new PacketWriter(null, 0, 0, null, null);
tmpWriter.pos(0);
writeStringLengthAscii(tmpWriter, _CLIENT_NAME);
writeStringLength(tmpWriter, "MariaDB Connector/J");
writeStringLengthAscii(tmpWriter, _CLIENT_VERSION);
writeStringLength(tmpWriter, VersionFactory.getInstance().getVersion());
writeStringLengthAscii(tmpWriter, _SERVER_HOST);
writeStringLength(tmpWriter, (host != null) ? host : "");
writeStringLengthAscii(tmpWriter, _OS);
writeStringLength(tmpWriter, System.getProperty("os.name"));
writeStringLengthAscii(tmpWriter, _THREAD);
writeStringLength(tmpWriter, Long.toString(Thread.currentThread().getId()));
writeStringLengthAscii(tmpWriter, _JAVA_VENDOR);
writeStringLength(tmpWriter, System.getProperty("java.vendor"));
writeStringLengthAscii(tmpWriter, _JAVA_VERSION);
writeStringLength(tmpWriter, System.getProperty("java.version"));
if (connectionAttributes != null) {
StringTokenizer tokenizer = new StringTokenizer(connectionAttributes, ",");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
int separator = token.indexOf(":");
if (separator != -1) {
writeStringLength(tmpWriter, token.substring(0, separator));
writeStringLength(tmpWriter, token.substring(separator + 1));
} else {
writeStringLength(tmpWriter, token);
writeStringLength(tmpWriter, "");
}
}
}
writer.writeLength(tmpWriter.pos());
writer.writeBytes(tmpWriter.buf(), 0, tmpWriter.pos());
}
@Override
public int encode(Writer writer, Context context) throws IOException {
final byte[] authData;
if ("mysql_clear_password".equals(authenticationPluginType)) {
if (!context.hasClientCapability(SSL)) {
throw new IllegalStateException("Cannot send password in clear if SSL is not enabled.");
}
authData =
(password == null) ? new byte[0] : password.toString().getBytes(StandardCharsets.UTF_8);
} else {
authenticationPluginType = "mysql_native_password";
authData = NativePasswordPlugin.encryptPassword(password, seed);
}
writer.writeInt((int) clientCapabilities);
writer.writeInt(1024 * 1024 * 1024);
writer.writeByte(exchangeCharset); // 1
writer.writeBytes(new byte[19]); // 19
writer.writeInt((int) (clientCapabilities >> 32)); // Maria extended flag
writer.writeString(username != null ? username : System.getProperty("user.name"));
writer.writeByte(0x00);
if (context.hasServerCapability(PLUGIN_AUTH_LENENC_CLIENT_DATA)) {
writer.writeLength(authData.length);
writer.writeBytes(authData);
} else if (context.hasServerCapability(SECURE_CONNECTION)) {
writer.writeByte((byte) authData.length);
writer.writeBytes(authData);
} else {
writer.writeBytes(authData);
writer.writeByte(0x00);
}
if (context.hasClientCapability(CONNECT_WITH_DB)) {
writer.writeString(database);
writer.writeByte(0x00);
}
if (context.hasServerCapability(PLUGIN_AUTH)) {
writer.writeString(authenticationPluginType);
writer.writeByte(0x00);
}
if (context.hasServerCapability(CONNECT_ATTRS)) {
writeConnectAttributes(writer, connectionAttributes, host);
}
writer.flush();
return 1;
}
}