server_handshake.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. 'use strict';
  2. const CommandCode = require('../constants/commands.js');
  3. const Errors = require('../constants/errors.js');
  4. const Command = require('./command.js');
  5. const Packets = require('../packets/index.js');
  6. class ServerHandshake extends Command {
  7. constructor(args) {
  8. super();
  9. this.args = args;
  10. /*
  11. this.protocolVersion = args.protocolVersion || 10;
  12. this.serverVersion = args.serverVersion;
  13. this.connectionId = args.connectionId,
  14. this.statusFlags = args.statusFlags,
  15. this.characterSet = args.characterSet,
  16. this.capabilityFlags = args.capabilityFlags || 512;
  17. */
  18. }
  19. start(packet, connection) {
  20. const serverHelloPacket = new Packets.Handshake(this.args);
  21. this.serverHello = serverHelloPacket;
  22. serverHelloPacket.setScrambleData(err => {
  23. if (err) {
  24. connection.emit('error', new Error('Error generating random bytes'));
  25. return;
  26. }
  27. connection.writePacket(serverHelloPacket.toPacket(0));
  28. });
  29. return ServerHandshake.prototype.readClientReply;
  30. }
  31. readClientReply(packet, connection) {
  32. // check auth here
  33. const clientHelloReply = Packets.HandshakeResponse.fromPacket(packet);
  34. // TODO check we don't have something similar already
  35. connection.clientHelloReply = clientHelloReply;
  36. if (this.args.authCallback) {
  37. this.args.authCallback(
  38. {
  39. user: clientHelloReply.user,
  40. database: clientHelloReply.database,
  41. address: connection.stream.remoteAddress,
  42. authPluginData1: this.serverHello.authPluginData1,
  43. authPluginData2: this.serverHello.authPluginData2,
  44. authToken: clientHelloReply.authToken
  45. },
  46. (err, mysqlError) => {
  47. // if (err)
  48. if (!mysqlError) {
  49. connection.writeOk();
  50. } else {
  51. // TODO create constants / errorToCode
  52. // 1045 = ER_ACCESS_DENIED_ERROR
  53. connection.writeError({
  54. message: mysqlError.message || '',
  55. code: mysqlError.code || 1045
  56. });
  57. connection.close();
  58. }
  59. }
  60. );
  61. } else {
  62. connection.writeOk();
  63. }
  64. return ServerHandshake.prototype.dispatchCommands;
  65. }
  66. dispatchCommands(packet, connection) {
  67. // command from client to server
  68. let knownCommand = true;
  69. const encoding = connection.clientHelloReply.encoding;
  70. const commandCode = packet.readInt8();
  71. switch (commandCode) {
  72. case CommandCode.QUIT:
  73. if (connection.listeners('quit').length) {
  74. connection.emit('quit');
  75. } else {
  76. connection.stream.end();
  77. }
  78. break;
  79. case CommandCode.INIT_DB:
  80. if (connection.listeners('init_db').length) {
  81. const schemaName = packet.readString(undefined, encoding);
  82. connection.emit('init_db', schemaName);
  83. } else {
  84. connection.writeOk();
  85. }
  86. break;
  87. case CommandCode.QUERY:
  88. if (connection.listeners('query').length) {
  89. const query = packet.readString(undefined, encoding);
  90. connection.emit('query', query);
  91. } else {
  92. connection.writeError({
  93. code: Errors.HA_ERR_INTERNAL_ERROR,
  94. message: 'No query handler'
  95. });
  96. }
  97. break;
  98. case CommandCode.FIELD_LIST:
  99. if (connection.listeners('field_list').length) {
  100. const table = packet.readNullTerminatedString();
  101. const fields = packet.readString(undefined, encoding);
  102. connection.emit('field_list', table, fields);
  103. } else {
  104. connection.writeError({
  105. code: Errors.ER_WARN_DEPRECATED_SYNTAX,
  106. message:
  107. 'As of MySQL 5.7.11, COM_FIELD_LIST is deprecated and will be removed in a future version of MySQL.'
  108. });
  109. }
  110. break;
  111. case CommandCode.PING:
  112. if (connection.listeners('ping').length) {
  113. connection.emit('ping');
  114. } else {
  115. connection.writeOk();
  116. }
  117. break;
  118. default:
  119. knownCommand = false;
  120. }
  121. if (connection.listeners('packet').length) {
  122. connection.emit('packet', packet.clone(), knownCommand, commandCode);
  123. } else if (!knownCommand) {
  124. // eslint-disable-next-line no-console
  125. console.log('Unknown command:', commandCode);
  126. }
  127. return ServerHandshake.prototype.dispatchCommands;
  128. }
  129. }
  130. module.exports = ServerHandshake;
  131. // TODO: implement server-side 4.1 authentication
  132. /*
  133. 4.1 authentication: (http://bazaar.launchpad.net/~mysql/mysql-server/5.5/view/head:/sql/password.c)
  134. SERVER: public_seed=create_random_string()
  135. send(public_seed)
  136. CLIENT: recv(public_seed)
  137. hash_stage1=sha1("password")
  138. hash_stage2=sha1(hash_stage1)
  139. reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
  140. // this three steps are done in scramble()
  141. send(reply)
  142. SERVER: recv(reply)
  143. hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
  144. candidate_hash2=sha1(hash_stage1)
  145. check(candidate_hash2==hash_stage2)
  146. server stores sha1(sha1(password)) ( hash_stag2)
  147. */