handshake.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. 'use strict';
  2. const Command = require('../command');
  3. const InitialHandshake = require('./initial-handshake');
  4. const ClientHandshakeResponse = require('./client-handshake-response');
  5. const SslRequest = require('./ssl-request');
  6. const ClientCapabilities = require('./client-capabilities');
  7. const Errors = require('../../misc/errors');
  8. const Capabilities = require('../../const/capabilities');
  9. const process = require('process');
  10. /**
  11. * Handle handshake.
  12. * see https://mariadb.com/kb/en/library/1-connecting-connecting/
  13. */
  14. class Handshake extends Command {
  15. constructor(resolve, reject, _createSecureContext, _addCommand, getSocket) {
  16. super(resolve, reject);
  17. this._createSecureContext = _createSecureContext;
  18. this._addCommand = _addCommand;
  19. this.getSocket = getSocket;
  20. this.onPacketReceive = this.parseHandshakeInit;
  21. this.plugin = this;
  22. }
  23. ensureOptionCompatibility(opts, info) {
  24. if (
  25. opts.multipleStatements &&
  26. (info.serverCapabilities & Capabilities.MULTI_STATEMENTS) === 0
  27. ) {
  28. return this.throwNewError(
  29. "Option `multipleStatements` enable, but server doesn'permits multi-statment",
  30. true,
  31. info,
  32. '08S01',
  33. Errors.ER_CLIENT_OPTION_INCOMPATIBILITY
  34. );
  35. }
  36. if (opts.permitLocalInfile && (info.serverCapabilities & Capabilities.LOCAL_FILES) === 0) {
  37. return this.throwNewError(
  38. "Option `permitLocalInfile` enable, but server doesn'permits using local file",
  39. true,
  40. info,
  41. '08S01',
  42. Errors.ER_CLIENT_OPTION_INCOMPATIBILITY
  43. );
  44. }
  45. }
  46. parseHandshakeInit(packet, out, opts, info) {
  47. if (packet.peek() === 0xff) {
  48. //in case that some host is not permit to connect server
  49. const authErr = packet.readError(info);
  50. authErr.fatal = true;
  51. return this.throwError(authErr, info);
  52. }
  53. let handshake = new InitialHandshake(packet, info);
  54. this.ensureOptionCompatibility(opts, info);
  55. ClientCapabilities.init(opts, info);
  56. if (opts.ssl) {
  57. if (info.serverCapabilities & Capabilities.SSL) {
  58. info.clientCapabilities |= Capabilities.SSL;
  59. SslRequest.send(this, out, info, opts);
  60. this._createSecureContext(
  61. function () {
  62. ClientHandshakeResponse.send(this, out, opts, handshake.pluginName, info);
  63. }.bind(this)
  64. );
  65. } else {
  66. return this.throwNewError(
  67. 'Trying to connect with ssl, but ssl not enabled in the server',
  68. true,
  69. info,
  70. '08S01',
  71. Errors.ER_SERVER_SSL_DISABLED
  72. );
  73. }
  74. } else {
  75. ClientHandshakeResponse.send(this, out, opts, handshake.pluginName, info);
  76. }
  77. this.onPacketReceive = this.handshakeResult;
  78. }
  79. /**
  80. * Fast-path handshake results :
  81. * - if plugin was the one expected by server, server will send OK_Packet / ERR_Packet.
  82. * - if not, server send an AuthSwitchRequest packet, indicating the specific PLUGIN to use with this user.
  83. * dispatching to plugin handler then.
  84. *
  85. * @param packet current packet
  86. * @param out output buffer
  87. * @param opts options
  88. * @param info connection info
  89. * @returns {*} return null if authentication succeed, depending on plugin conversation if not finished
  90. */
  91. handshakeResult(packet, out, opts, info) {
  92. const marker = packet.peek();
  93. switch (marker) {
  94. //*********************************************************************************************************
  95. //* AuthSwitchRequest packet
  96. //*********************************************************************************************************
  97. case 0xfe:
  98. this.plugin.onPacketReceive = null;
  99. this.plugin.emit('send_end');
  100. this.plugin.emit('end');
  101. this.dispatchAuthSwitchRequest(packet, out, opts, info);
  102. return;
  103. //*********************************************************************************************************
  104. //* OK_Packet - authentication succeeded
  105. //*********************************************************************************************************
  106. case 0x00:
  107. packet.skip(1); //skip header
  108. packet.skipLengthCodedNumber(); //skip affected rows
  109. packet.skipLengthCodedNumber(); //skip last insert id
  110. info.status = packet.readUInt16();
  111. this.plugin.emit('send_end');
  112. return this.plugin.successEnd();
  113. //*********************************************************************************************************
  114. //* ERR_Packet
  115. //*********************************************************************************************************
  116. case 0xff:
  117. const authErr = packet.readError(info, this.displaySql());
  118. authErr.fatal = true;
  119. return this.plugin.throwError(authErr, info);
  120. //*********************************************************************************************************
  121. //* unexpected
  122. //*********************************************************************************************************
  123. default:
  124. this.throwNewError(
  125. 'Unexpected type of packet during handshake phase : ' + marker,
  126. true,
  127. info,
  128. '42000',
  129. Errors.ER_AUTHENTICATION_BAD_PACKET
  130. );
  131. }
  132. }
  133. /**
  134. * Handle authentication switch request : dispatch to plugin handler.
  135. *
  136. * @param packet packet
  137. * @param out output writer
  138. * @param opts options
  139. * @param info connection information
  140. */
  141. dispatchAuthSwitchRequest(packet, out, opts, info) {
  142. let pluginName, pluginData;
  143. if (info.clientCapabilities & Capabilities.PLUGIN_AUTH) {
  144. packet.skip(1); //header
  145. if (packet.remaining()) {
  146. //AuthSwitchRequest packet.
  147. pluginName = packet.readStringNullEnded();
  148. pluginData = packet.readBufferRemaining();
  149. } else {
  150. //OldAuthSwitchRequest
  151. pluginName = 'mysql_old_password';
  152. pluginData = info.seed.slice(0, 8);
  153. }
  154. } else {
  155. pluginName = packet.readStringNullEnded('cesu8');
  156. pluginData = packet.readBufferRemaining();
  157. }
  158. try {
  159. this.plugin = Handshake.pluginHandler(
  160. pluginName,
  161. this.plugin.sequenceNo,
  162. this.plugin.compressSequenceNo,
  163. pluginData,
  164. info,
  165. opts,
  166. out,
  167. this.resolve,
  168. this.reject,
  169. this.handshakeResult.bind(this)
  170. );
  171. } catch (err) {
  172. this.reject(err);
  173. return;
  174. }
  175. if (!this.plugin) {
  176. this.reject(
  177. Errors.createError(
  178. "Client does not support authentication protocol '" +
  179. pluginName +
  180. "' requested by server. ",
  181. true,
  182. info,
  183. '08004',
  184. Errors.ER_AUTHENTICATION_PLUGIN_NOT_SUPPORTED
  185. )
  186. );
  187. } else {
  188. this._addCommand(this.plugin, false);
  189. }
  190. }
  191. static pluginHandler(
  192. pluginName,
  193. packSeq,
  194. compressPackSeq,
  195. pluginData,
  196. info,
  197. opts,
  198. out,
  199. authResolve,
  200. authReject,
  201. multiAuthResolver
  202. ) {
  203. let pluginAuth;
  204. switch (pluginName) {
  205. case 'mysql_native_password':
  206. pluginAuth = require('./auth/native-password-auth.js');
  207. break;
  208. case 'mysql_clear_password':
  209. pluginAuth = require('./auth/clear-password-auth.js');
  210. break;
  211. case 'client_ed25519':
  212. pluginAuth = require('./auth/ed25519-password-auth.js');
  213. break;
  214. case 'dialog':
  215. pluginAuth = require('./auth/pam-password-auth.js');
  216. break;
  217. case 'sha256_password':
  218. if (!Handshake.ensureNodeVersion(11, 6, 0)) {
  219. throw Errors.createError(
  220. 'sha256_password authentication plugin require node 11.6+',
  221. true,
  222. info,
  223. '08004',
  224. Errors.ER_MINIMUM_NODE_VERSION_REQUIRED
  225. );
  226. }
  227. pluginAuth = require('./auth/sha256-password-auth.js');
  228. break;
  229. case 'caching_sha2_password':
  230. if (!Handshake.ensureNodeVersion(11, 6, 0)) {
  231. throw Errors.createError(
  232. 'caching_sha2_password authentication plugin require node 11.6+',
  233. true,
  234. info,
  235. '08004',
  236. Errors.ER_MINIMUM_NODE_VERSION_REQUIRED
  237. );
  238. }
  239. pluginAuth = require('./auth/caching-sha2-password-auth.js');
  240. break;
  241. //TODO "auth_gssapi_client"
  242. default:
  243. return null;
  244. }
  245. return new pluginAuth(
  246. packSeq,
  247. compressPackSeq,
  248. pluginData,
  249. authResolve,
  250. authReject,
  251. multiAuthResolver
  252. );
  253. }
  254. static ensureNodeVersion(major, minor, patch) {
  255. const ver = process.versions.node.split('.');
  256. return (
  257. ver[0] > major ||
  258. (ver[0] === major && ver[1] > minor) ||
  259. (ver[0] === major && ver[1] === minor && ver[2] >= patch)
  260. );
  261. }
  262. }
  263. module.exports = Handshake;