288 lines
8.8 KiB
JavaScript
288 lines
8.8 KiB
JavaScript
'use strict';
|
|
|
|
const Command = require('../command');
|
|
const InitialHandshake = require('./initial-handshake');
|
|
const ClientHandshakeResponse = require('./client-handshake-response');
|
|
const SslRequest = require('./ssl-request');
|
|
const ClientCapabilities = require('./client-capabilities');
|
|
const Errors = require('../../misc/errors');
|
|
const Capabilities = require('../../const/capabilities');
|
|
const process = require('process');
|
|
|
|
/**
|
|
* Handle handshake.
|
|
* see https://mariadb.com/kb/en/library/1-connecting-connecting/
|
|
*/
|
|
class Handshake extends Command {
|
|
constructor(resolve, reject, _createSecureContext, _addCommand, getSocket) {
|
|
super(resolve, reject);
|
|
this._createSecureContext = _createSecureContext;
|
|
this._addCommand = _addCommand;
|
|
this.getSocket = getSocket;
|
|
this.onPacketReceive = this.parseHandshakeInit;
|
|
this.plugin = this;
|
|
}
|
|
|
|
ensureOptionCompatibility(opts, info) {
|
|
if (
|
|
opts.multipleStatements &&
|
|
(info.serverCapabilities & Capabilities.MULTI_STATEMENTS) === 0
|
|
) {
|
|
return this.throwNewError(
|
|
"Option `multipleStatements` enable, but server doesn'permits multi-statment",
|
|
true,
|
|
info,
|
|
'08S01',
|
|
Errors.ER_CLIENT_OPTION_INCOMPATIBILITY
|
|
);
|
|
}
|
|
|
|
if (opts.permitLocalInfile && (info.serverCapabilities & Capabilities.LOCAL_FILES) === 0) {
|
|
return this.throwNewError(
|
|
"Option `permitLocalInfile` enable, but server doesn'permits using local file",
|
|
true,
|
|
info,
|
|
'08S01',
|
|
Errors.ER_CLIENT_OPTION_INCOMPATIBILITY
|
|
);
|
|
}
|
|
}
|
|
|
|
parseHandshakeInit(packet, out, opts, info) {
|
|
if (packet.peek() === 0xff) {
|
|
//in case that some host is not permit to connect server
|
|
const authErr = packet.readError(info);
|
|
authErr.fatal = true;
|
|
return this.throwError(authErr, info);
|
|
}
|
|
|
|
let handshake = new InitialHandshake(packet, info);
|
|
this.ensureOptionCompatibility(opts, info);
|
|
ClientCapabilities.init(opts, info);
|
|
|
|
if (opts.ssl) {
|
|
if (info.serverCapabilities & Capabilities.SSL) {
|
|
info.clientCapabilities |= Capabilities.SSL;
|
|
SslRequest.send(this, out, info, opts);
|
|
this._createSecureContext(
|
|
function () {
|
|
ClientHandshakeResponse.send(this, out, opts, handshake.pluginName, info);
|
|
}.bind(this)
|
|
);
|
|
} else {
|
|
return this.throwNewError(
|
|
'Trying to connect with ssl, but ssl not enabled in the server',
|
|
true,
|
|
info,
|
|
'08S01',
|
|
Errors.ER_SERVER_SSL_DISABLED
|
|
);
|
|
}
|
|
} else {
|
|
ClientHandshakeResponse.send(this, out, opts, handshake.pluginName, info);
|
|
}
|
|
this.onPacketReceive = this.handshakeResult;
|
|
}
|
|
|
|
/**
|
|
* Fast-path handshake results :
|
|
* - if plugin was the one expected by server, server will send OK_Packet / ERR_Packet.
|
|
* - if not, server send an AuthSwitchRequest packet, indicating the specific PLUGIN to use with this user.
|
|
* dispatching to plugin handler then.
|
|
*
|
|
* @param packet current packet
|
|
* @param out output buffer
|
|
* @param opts options
|
|
* @param info connection info
|
|
* @returns {*} return null if authentication succeed, depending on plugin conversation if not finished
|
|
*/
|
|
handshakeResult(packet, out, opts, info) {
|
|
const marker = packet.peek();
|
|
switch (marker) {
|
|
//*********************************************************************************************************
|
|
//* AuthSwitchRequest packet
|
|
//*********************************************************************************************************
|
|
case 0xfe:
|
|
this.plugin.onPacketReceive = null;
|
|
this.plugin.emit('send_end');
|
|
this.plugin.emit('end');
|
|
this.dispatchAuthSwitchRequest(packet, out, opts, info);
|
|
return;
|
|
|
|
//*********************************************************************************************************
|
|
//* OK_Packet - authentication succeeded
|
|
//*********************************************************************************************************
|
|
case 0x00:
|
|
packet.skip(1); //skip header
|
|
packet.skipLengthCodedNumber(); //skip affected rows
|
|
packet.skipLengthCodedNumber(); //skip last insert id
|
|
info.status = packet.readUInt16();
|
|
this.plugin.emit('send_end');
|
|
return this.plugin.successEnd();
|
|
|
|
//*********************************************************************************************************
|
|
//* ERR_Packet
|
|
//*********************************************************************************************************
|
|
case 0xff:
|
|
const authErr = packet.readError(info, this.displaySql());
|
|
authErr.fatal = true;
|
|
return this.plugin.throwError(authErr, info);
|
|
|
|
//*********************************************************************************************************
|
|
//* unexpected
|
|
//*********************************************************************************************************
|
|
default:
|
|
this.throwNewError(
|
|
'Unexpected type of packet during handshake phase : ' + marker,
|
|
true,
|
|
info,
|
|
'42000',
|
|
Errors.ER_AUTHENTICATION_BAD_PACKET
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle authentication switch request : dispatch to plugin handler.
|
|
*
|
|
* @param packet packet
|
|
* @param out output writer
|
|
* @param opts options
|
|
* @param info connection information
|
|
*/
|
|
dispatchAuthSwitchRequest(packet, out, opts, info) {
|
|
let pluginName, pluginData;
|
|
if (info.clientCapabilities & Capabilities.PLUGIN_AUTH) {
|
|
packet.skip(1); //header
|
|
if (packet.remaining()) {
|
|
//AuthSwitchRequest packet.
|
|
pluginName = packet.readStringNullEnded();
|
|
pluginData = packet.readBufferRemaining();
|
|
} else {
|
|
//OldAuthSwitchRequest
|
|
pluginName = 'mysql_old_password';
|
|
pluginData = info.seed.slice(0, 8);
|
|
}
|
|
} else {
|
|
pluginName = packet.readStringNullEnded('cesu8');
|
|
pluginData = packet.readBufferRemaining();
|
|
}
|
|
|
|
try {
|
|
this.plugin = Handshake.pluginHandler(
|
|
pluginName,
|
|
this.plugin.sequenceNo,
|
|
this.plugin.compressSequenceNo,
|
|
pluginData,
|
|
info,
|
|
opts,
|
|
out,
|
|
this.resolve,
|
|
this.reject,
|
|
this.handshakeResult.bind(this)
|
|
);
|
|
} catch (err) {
|
|
this.reject(err);
|
|
return;
|
|
}
|
|
|
|
if (!this.plugin) {
|
|
this.reject(
|
|
Errors.createError(
|
|
"Client does not support authentication protocol '" +
|
|
pluginName +
|
|
"' requested by server. ",
|
|
true,
|
|
info,
|
|
'08004',
|
|
Errors.ER_AUTHENTICATION_PLUGIN_NOT_SUPPORTED
|
|
)
|
|
);
|
|
} else {
|
|
this._addCommand(this.plugin, false);
|
|
}
|
|
}
|
|
|
|
static pluginHandler(
|
|
pluginName,
|
|
packSeq,
|
|
compressPackSeq,
|
|
pluginData,
|
|
info,
|
|
opts,
|
|
out,
|
|
authResolve,
|
|
authReject,
|
|
multiAuthResolver
|
|
) {
|
|
let pluginAuth;
|
|
switch (pluginName) {
|
|
case 'mysql_native_password':
|
|
pluginAuth = require('./auth/native-password-auth.js');
|
|
break;
|
|
|
|
case 'mysql_clear_password':
|
|
pluginAuth = require('./auth/clear-password-auth.js');
|
|
break;
|
|
|
|
case 'client_ed25519':
|
|
pluginAuth = require('./auth/ed25519-password-auth.js');
|
|
break;
|
|
|
|
case 'dialog':
|
|
pluginAuth = require('./auth/pam-password-auth.js');
|
|
break;
|
|
|
|
case 'sha256_password':
|
|
if (!Handshake.ensureNodeVersion(11, 6, 0)) {
|
|
throw Errors.createError(
|
|
'sha256_password authentication plugin require node 11.6+',
|
|
true,
|
|
info,
|
|
'08004',
|
|
Errors.ER_MINIMUM_NODE_VERSION_REQUIRED
|
|
);
|
|
}
|
|
pluginAuth = require('./auth/sha256-password-auth.js');
|
|
break;
|
|
|
|
case 'caching_sha2_password':
|
|
if (!Handshake.ensureNodeVersion(11, 6, 0)) {
|
|
throw Errors.createError(
|
|
'caching_sha2_password authentication plugin require node 11.6+',
|
|
true,
|
|
info,
|
|
'08004',
|
|
Errors.ER_MINIMUM_NODE_VERSION_REQUIRED
|
|
);
|
|
}
|
|
pluginAuth = require('./auth/caching-sha2-password-auth.js');
|
|
break;
|
|
|
|
//TODO "auth_gssapi_client"
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
return new pluginAuth(
|
|
packSeq,
|
|
compressPackSeq,
|
|
pluginData,
|
|
authResolve,
|
|
authReject,
|
|
multiAuthResolver
|
|
);
|
|
}
|
|
|
|
static ensureNodeVersion(major, minor, patch) {
|
|
const ver = process.versions.node.split('.');
|
|
return (
|
|
ver[0] > major ||
|
|
(ver[0] === major && ver[1] > minor) ||
|
|
(ver[0] === major && ver[1] === minor && ver[2] >= patch)
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = Handshake;
|