server_handshake.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. _isStatement(query, name) {
  67. const firstWord = query.split(' ')[0].toUpperCase();
  68. return firstWord === name;
  69. }
  70. dispatchCommands(packet, connection) {
  71. // command from client to server
  72. let knownCommand = true;
  73. const encoding = connection.clientHelloReply.encoding;
  74. const commandCode = packet.readInt8();
  75. switch (commandCode) {
  76. case CommandCode.STMT_PREPARE:
  77. if (connection.listeners('stmt_prepare').length) {
  78. const query = packet.readString(undefined, encoding);
  79. connection.emit('stmt_prepare', query);
  80. } else {
  81. connection.writeError({
  82. code: Errors.HA_ERR_INTERNAL_ERROR,
  83. message:
  84. 'No query handler for prepared statements.'
  85. });
  86. }
  87. break;
  88. case CommandCode.STMT_EXECUTE:
  89. if (connection.listeners('stmt_execute').length) {
  90. const { stmtId, flags, iterationCount, values } = Packets.Execute.fromPacket(packet, encoding);
  91. connection.emit('stmt_execute', stmtId, flags, iterationCount, values);
  92. } else {
  93. connection.writeError({
  94. code: Errors.HA_ERR_INTERNAL_ERROR,
  95. message:
  96. 'No query handler for execute statements.'
  97. });
  98. }
  99. break;
  100. case CommandCode.QUIT:
  101. if (connection.listeners('quit').length) {
  102. connection.emit('quit');
  103. } else {
  104. connection.stream.end();
  105. }
  106. break;
  107. case CommandCode.INIT_DB:
  108. if (connection.listeners('init_db').length) {
  109. const schemaName = packet.readString(undefined, encoding);
  110. connection.emit('init_db', schemaName);
  111. } else {
  112. connection.writeOk();
  113. }
  114. break;
  115. case CommandCode.QUERY:
  116. if (connection.listeners('query').length) {
  117. const query = packet.readString(undefined, encoding);
  118. if (this._isStatement(query, 'PREPARE') || this._isStatement(query, 'SET')) {
  119. connection.emit('stmt_prepare', query);
  120. }
  121. else if (this._isStatement(query, 'EXECUTE')) {
  122. connection.emit('stmt_execute', null, null, null, null, query);
  123. }
  124. else connection.emit('query', query);
  125. } else {
  126. connection.writeError({
  127. code: Errors.HA_ERR_INTERNAL_ERROR,
  128. message: 'No query handler'
  129. });
  130. }
  131. break;
  132. case CommandCode.FIELD_LIST:
  133. if (connection.listeners('field_list').length) {
  134. const table = packet.readNullTerminatedString(encoding);
  135. const fields = packet.readString(undefined, encoding);
  136. connection.emit('field_list', table, fields);
  137. } else {
  138. connection.writeError({
  139. code: Errors.ER_WARN_DEPRECATED_SYNTAX,
  140. message:
  141. 'As of MySQL 5.7.11, COM_FIELD_LIST is deprecated and will be removed in a future version of MySQL.'
  142. });
  143. }
  144. break;
  145. case CommandCode.PING:
  146. if (connection.listeners('ping').length) {
  147. connection.emit('ping');
  148. } else {
  149. connection.writeOk();
  150. }
  151. break;
  152. default:
  153. knownCommand = false;
  154. }
  155. if (connection.listeners('packet').length) {
  156. connection.emit('packet', packet.clone(), knownCommand, commandCode);
  157. } else if (!knownCommand) {
  158. // eslint-disable-next-line no-console
  159. console.log('Unknown command:', commandCode);
  160. }
  161. return ServerHandshake.prototype.dispatchCommands;
  162. }
  163. }
  164. module.exports = ServerHandshake;
  165. // TODO: implement server-side 4.1 authentication
  166. /*
  167. 4.1 authentication: (http://bazaar.launchpad.net/~mysql/mysql-server/5.5/view/head:/sql/password.c)
  168. SERVER: public_seed=create_random_string()
  169. send(public_seed)
  170. CLIENT: recv(public_seed)
  171. hash_stage1=sha1("password")
  172. hash_stage2=sha1(hash_stage1)
  173. reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
  174. // this three steps are done in scramble()
  175. send(reply)
  176. SERVER: recv(reply)
  177. hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
  178. candidate_hash2=sha1(hash_stage1)
  179. check(candidate_hash2==hash_stage2)
  180. server stores sha1(sha1(password)) ( hash_stag2)
  181. */