201 lines
6.3 KiB
JavaScript
201 lines
6.3 KiB
JavaScript
|
// This file was modified by Oracle on June 17, 2021.
|
||
|
// Handshake errors are now maked as fatal and the corresponding events are
|
||
|
// emitted in the command instance itself.
|
||
|
// Modifications copyright (c) 2021, Oracle and/or its affiliates.
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
const Command = require('./command.js');
|
||
|
const Packets = require('../packets/index.js');
|
||
|
const ClientConstants = require('../constants/client.js');
|
||
|
const CharsetToEncoding = require('../constants/charset_encodings.js');
|
||
|
const auth41 = require('../auth_41.js');
|
||
|
|
||
|
function flagNames(flags) {
|
||
|
const res = [];
|
||
|
for (const c in ClientConstants) {
|
||
|
if (flags & ClientConstants[c]) {
|
||
|
res.push(c.replace(/_/g, ' ').toLowerCase());
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
class ClientHandshake extends Command {
|
||
|
constructor(clientFlags) {
|
||
|
super();
|
||
|
this.handshake = null;
|
||
|
this.clientFlags = clientFlags;
|
||
|
}
|
||
|
|
||
|
start() {
|
||
|
return ClientHandshake.prototype.handshakeInit;
|
||
|
}
|
||
|
|
||
|
sendSSLRequest(connection) {
|
||
|
const sslRequest = new Packets.SSLRequest(
|
||
|
this.clientFlags,
|
||
|
connection.config.charsetNumber
|
||
|
);
|
||
|
connection.writePacket(sslRequest.toPacket());
|
||
|
}
|
||
|
|
||
|
sendCredentials(connection) {
|
||
|
if (connection.config.debug) {
|
||
|
// eslint-disable-next-line
|
||
|
console.log(
|
||
|
'Sending handshake packet: flags:%d=(%s)',
|
||
|
this.clientFlags,
|
||
|
flagNames(this.clientFlags).join(', ')
|
||
|
);
|
||
|
}
|
||
|
this.user = connection.config.user;
|
||
|
this.password = connection.config.password;
|
||
|
this.passwordSha1 = connection.config.passwordSha1;
|
||
|
this.database = connection.config.database;
|
||
|
this.autPluginName = this.handshake.autPluginName;
|
||
|
const handshakeResponse = new Packets.HandshakeResponse({
|
||
|
flags: this.clientFlags,
|
||
|
user: this.user,
|
||
|
database: this.database,
|
||
|
password: this.password,
|
||
|
passwordSha1: this.passwordSha1,
|
||
|
charsetNumber: connection.config.charsetNumber,
|
||
|
authPluginData1: this.handshake.authPluginData1,
|
||
|
authPluginData2: this.handshake.authPluginData2,
|
||
|
compress: connection.config.compress,
|
||
|
connectAttributes: connection.config.connectAttributes
|
||
|
});
|
||
|
connection.writePacket(handshakeResponse.toPacket());
|
||
|
}
|
||
|
|
||
|
calculateNativePasswordAuthToken(authPluginData) {
|
||
|
// TODO: dont split into authPluginData1 and authPluginData2, instead join when 1 & 2 received
|
||
|
const authPluginData1 = authPluginData.slice(0, 8);
|
||
|
const authPluginData2 = authPluginData.slice(8, 20);
|
||
|
let authToken;
|
||
|
if (this.passwordSha1) {
|
||
|
authToken = auth41.calculateTokenFromPasswordSha(
|
||
|
this.passwordSha1,
|
||
|
authPluginData1,
|
||
|
authPluginData2
|
||
|
);
|
||
|
} else {
|
||
|
authToken = auth41.calculateToken(
|
||
|
this.password,
|
||
|
authPluginData1,
|
||
|
authPluginData2
|
||
|
);
|
||
|
}
|
||
|
return authToken;
|
||
|
}
|
||
|
|
||
|
handshakeInit(helloPacket, connection) {
|
||
|
this.on('error', e => {
|
||
|
connection._fatalError = e;
|
||
|
connection._protocolError = e;
|
||
|
});
|
||
|
this.handshake = Packets.Handshake.fromPacket(helloPacket);
|
||
|
if (connection.config.debug) {
|
||
|
// eslint-disable-next-line
|
||
|
console.log(
|
||
|
'Server hello packet: capability flags:%d=(%s)',
|
||
|
this.handshake.capabilityFlags,
|
||
|
flagNames(this.handshake.capabilityFlags).join(', ')
|
||
|
);
|
||
|
}
|
||
|
connection.serverCapabilityFlags = this.handshake.capabilityFlags;
|
||
|
connection.serverEncoding = CharsetToEncoding[this.handshake.characterSet];
|
||
|
connection.connectionId = this.handshake.connectionId;
|
||
|
const serverSSLSupport =
|
||
|
this.handshake.capabilityFlags & ClientConstants.SSL;
|
||
|
// use compression only if requested by client and supported by server
|
||
|
connection.config.compress =
|
||
|
connection.config.compress &&
|
||
|
this.handshake.capabilityFlags & ClientConstants.COMPRESS;
|
||
|
this.clientFlags = this.clientFlags | connection.config.compress;
|
||
|
if (connection.config.ssl) {
|
||
|
// client requires SSL but server does not support it
|
||
|
if (!serverSSLSupport) {
|
||
|
const err = new Error('Server does not support secure connnection');
|
||
|
err.code = 'HANDSHAKE_NO_SSL_SUPPORT';
|
||
|
err.fatal = true;
|
||
|
this.emit('error', err);
|
||
|
return false;
|
||
|
}
|
||
|
// send ssl upgrade request and immediately upgrade connection to secure
|
||
|
this.clientFlags |= ClientConstants.SSL;
|
||
|
this.sendSSLRequest(connection);
|
||
|
connection.startTLS(err => {
|
||
|
// after connection is secure
|
||
|
if (err) {
|
||
|
// SSL negotiation error are fatal
|
||
|
err.code = 'HANDSHAKE_SSL_ERROR';
|
||
|
err.fatal = true;
|
||
|
this.emit('error', err);
|
||
|
return;
|
||
|
}
|
||
|
// rest of communication is encrypted
|
||
|
this.sendCredentials(connection);
|
||
|
});
|
||
|
} else {
|
||
|
this.sendCredentials(connection);
|
||
|
}
|
||
|
return ClientHandshake.prototype.handshakeResult;
|
||
|
}
|
||
|
|
||
|
handshakeResult(packet, connection) {
|
||
|
const marker = packet.peekByte();
|
||
|
if (marker === 0xfe || marker === 1) {
|
||
|
const authSwitch = require('./auth_switch');
|
||
|
try {
|
||
|
if (marker === 1) {
|
||
|
authSwitch.authSwitchRequestMoreData(packet, connection, this);
|
||
|
} else {
|
||
|
authSwitch.authSwitchRequest(packet, connection, this);
|
||
|
}
|
||
|
return ClientHandshake.prototype.handshakeResult;
|
||
|
} catch (err) {
|
||
|
// Authentication errors are fatal
|
||
|
err.code = 'AUTH_SWITCH_PLUGIN_ERROR';
|
||
|
err.fatal = true;
|
||
|
|
||
|
if (this.onResult) {
|
||
|
this.onResult(err);
|
||
|
} else {
|
||
|
this.emit('error', err);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
if (marker !== 0) {
|
||
|
const err = new Error('Unexpected packet during handshake phase');
|
||
|
// Unknown handshake errors are fatal
|
||
|
err.code = 'HANDSHAKE_UNKNOWN_ERROR';
|
||
|
err.fatal = true;
|
||
|
|
||
|
if (this.onResult) {
|
||
|
this.onResult(err);
|
||
|
} else {
|
||
|
this.emit('error', err);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
// this should be called from ClientHandshake command only
|
||
|
// and skipped when called from ChangeUser command
|
||
|
if (!connection.authorized) {
|
||
|
connection.authorized = true;
|
||
|
if (connection.config.compress) {
|
||
|
const enableCompression = require('../compressed_protocol.js')
|
||
|
.enableCompression;
|
||
|
enableCompression(connection);
|
||
|
}
|
||
|
}
|
||
|
if (this.onResult) {
|
||
|
this.onResult(null);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
module.exports = ClientHandshake;
|