Spaces:
				
			
			
	
			
			
		Paused
		
	
	
	
			
			
	
	
	
	
		
		
		Paused
		
	| ; | |
| const CommandCode = require('../constants/commands.js'); | |
| const Errors = require('../constants/errors.js'); | |
| const Command = require('./command.js'); | |
| const Packets = require('../packets/index.js'); | |
| class ServerHandshake extends Command { | |
| constructor(args) { | |
| super(); | |
| this.args = args; | |
| /* | |
| this.protocolVersion = args.protocolVersion || 10; | |
| this.serverVersion = args.serverVersion; | |
| this.connectionId = args.connectionId, | |
| this.statusFlags = args.statusFlags, | |
| this.characterSet = args.characterSet, | |
| this.capabilityFlags = args.capabilityFlags || 512; | |
| */ | |
| } | |
| start(packet, connection) { | |
| const serverHelloPacket = new Packets.Handshake(this.args); | |
| this.serverHello = serverHelloPacket; | |
| serverHelloPacket.setScrambleData(err => { | |
| if (err) { | |
| connection.emit('error', new Error('Error generating random bytes')); | |
| return; | |
| } | |
| connection.writePacket(serverHelloPacket.toPacket(0)); | |
| }); | |
| return ServerHandshake.prototype.readClientReply; | |
| } | |
| readClientReply(packet, connection) { | |
| // check auth here | |
| const clientHelloReply = Packets.HandshakeResponse.fromPacket(packet); | |
| // TODO check we don't have something similar already | |
| connection.clientHelloReply = clientHelloReply; | |
| if (this.args.authCallback) { | |
| this.args.authCallback( | |
| { | |
| user: clientHelloReply.user, | |
| database: clientHelloReply.database, | |
| address: connection.stream.remoteAddress, | |
| authPluginData1: this.serverHello.authPluginData1, | |
| authPluginData2: this.serverHello.authPluginData2, | |
| authToken: clientHelloReply.authToken | |
| }, | |
| (err, mysqlError) => { | |
| // if (err) | |
| if (!mysqlError) { | |
| connection.writeOk(); | |
| } else { | |
| // TODO create constants / errorToCode | |
| // 1045 = ER_ACCESS_DENIED_ERROR | |
| connection.writeError({ | |
| message: mysqlError.message || '', | |
| code: mysqlError.code || 1045 | |
| }); | |
| connection.close(); | |
| } | |
| } | |
| ); | |
| } else { | |
| connection.writeOk(); | |
| } | |
| return ServerHandshake.prototype.dispatchCommands; | |
| } | |
| _isStatement(query, name) { | |
| const firstWord = query.split(' ')[0].toUpperCase(); | |
| return firstWord === name; | |
| } | |
| dispatchCommands(packet, connection) { | |
| // command from client to server | |
| let knownCommand = true; | |
| const encoding = connection.clientHelloReply.encoding; | |
| const commandCode = packet.readInt8(); | |
| switch (commandCode) { | |
| case CommandCode.STMT_PREPARE: | |
| if (connection.listeners('stmt_prepare').length) { | |
| const query = packet.readString(undefined, encoding); | |
| connection.emit('stmt_prepare', query); | |
| } else { | |
| connection.writeError({ | |
| code: Errors.HA_ERR_INTERNAL_ERROR, | |
| message: | |
| 'No query handler for prepared statements.' | |
| }); | |
| } | |
| break; | |
| case CommandCode.STMT_EXECUTE: | |
| if (connection.listeners('stmt_execute').length) { | |
| const { stmtId, flags, iterationCount, values } = Packets.Execute.fromPacket(packet, encoding); | |
| connection.emit('stmt_execute', stmtId, flags, iterationCount, values); | |
| } else { | |
| connection.writeError({ | |
| code: Errors.HA_ERR_INTERNAL_ERROR, | |
| message: | |
| 'No query handler for execute statements.' | |
| }); | |
| } | |
| break; | |
| case CommandCode.QUIT: | |
| if (connection.listeners('quit').length) { | |
| connection.emit('quit'); | |
| } else { | |
| connection.stream.end(); | |
| } | |
| break; | |
| case CommandCode.INIT_DB: | |
| if (connection.listeners('init_db').length) { | |
| const schemaName = packet.readString(undefined, encoding); | |
| connection.emit('init_db', schemaName); | |
| } else { | |
| connection.writeOk(); | |
| } | |
| break; | |
| case CommandCode.QUERY: | |
| if (connection.listeners('query').length) { | |
| const query = packet.readString(undefined, encoding); | |
| if (this._isStatement(query, 'PREPARE') || this._isStatement(query, 'SET')) { | |
| connection.emit('stmt_prepare', query); | |
| } | |
| else if (this._isStatement(query, 'EXECUTE')) { | |
| connection.emit('stmt_execute', null, null, null, null, query); | |
| } | |
| else connection.emit('query', query); | |
| } else { | |
| connection.writeError({ | |
| code: Errors.HA_ERR_INTERNAL_ERROR, | |
| message: 'No query handler' | |
| }); | |
| } | |
| break; | |
| case CommandCode.FIELD_LIST: | |
| if (connection.listeners('field_list').length) { | |
| const table = packet.readNullTerminatedString(encoding); | |
| const fields = packet.readString(undefined, encoding); | |
| connection.emit('field_list', table, fields); | |
| } else { | |
| connection.writeError({ | |
| code: Errors.ER_WARN_DEPRECATED_SYNTAX, | |
| message: | |
| 'As of MySQL 5.7.11, COM_FIELD_LIST is deprecated and will be removed in a future version of MySQL.' | |
| }); | |
| } | |
| break; | |
| case CommandCode.PING: | |
| if (connection.listeners('ping').length) { | |
| connection.emit('ping'); | |
| } else { | |
| connection.writeOk(); | |
| } | |
| break; | |
| default: | |
| knownCommand = false; | |
| } | |
| if (connection.listeners('packet').length) { | |
| connection.emit('packet', packet.clone(), knownCommand, commandCode); | |
| } else if (!knownCommand) { | |
| // eslint-disable-next-line no-console | |
| console.log('Unknown command:', commandCode); | |
| } | |
| return ServerHandshake.prototype.dispatchCommands; | |
| } | |
| } | |
| module.exports = ServerHandshake; | |
| // TODO: implement server-side 4.1 authentication | |
| /* | |
| 4.1 authentication: (http://bazaar.launchpad.net/~mysql/mysql-server/5.5/view/head:/sql/password.c) | |
| SERVER: public_seed=create_random_string() | |
| send(public_seed) | |
| CLIENT: recv(public_seed) | |
| hash_stage1=sha1("password") | |
| hash_stage2=sha1(hash_stage1) | |
| reply=xor(hash_stage1, sha1(public_seed,hash_stage2) | |
| // this three steps are done in scramble() | |
| send(reply) | |
| SERVER: recv(reply) | |
| hash_stage1=xor(reply, sha1(public_seed,hash_stage2)) | |
| candidate_hash2=sha1(hash_stage1) | |
| check(candidate_hash2==hash_stage2) | |
| server stores sha1(sha1(password)) ( hash_stag2) | |
| */ | |
