You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

284 lines
10 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright ?2012, 2018, Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using System;
  24. using System.Diagnostics;
  25. using System.Text;
  26. using Externals.MySql.Data.MySqlClient;
  27. namespace Externals.MySql.Data.MySqlClient.Authentication
  28. {
  29. /// <summary>
  30. /// Defines the default behavior for an authentication plugin.
  31. /// </summary>
  32. internal abstract class MySqlAuthenticationPlugin
  33. {
  34. private NativeDriver _driver;
  35. /// <summary>
  36. /// Gets or sets the authentication data returned by the server.
  37. /// </summary>
  38. protected byte[] AuthenticationData;
  39. /// <summary>
  40. /// This is a factory method that is used only internally. It creates an auth plugin based on the method type
  41. /// </summary>
  42. /// <param name="method"></param>
  43. /// <param name="driver"></param>
  44. /// <param name="authData"></param>
  45. /// <returns></returns>
  46. internal static MySqlAuthenticationPlugin GetPlugin(string method, NativeDriver driver, byte[] authData)
  47. {
  48. if (method == "mysql_old_password")
  49. {
  50. driver.Close(true);
  51. throw new MySqlException(Resources.OldPasswordsNotSupported);
  52. }
  53. MySqlAuthenticationPlugin plugin = AuthenticationPluginManager.GetPlugin(method);
  54. if (plugin == null)
  55. throw new MySqlException(String.Format(Resources.UnknownAuthenticationMethod, method));
  56. plugin._driver = driver;
  57. plugin.SetAuthData(authData);
  58. return plugin;
  59. }
  60. /// <summary>
  61. /// Gets the connection option settings.
  62. /// </summary>
  63. protected MySqlConnectionStringBuilder Settings => _driver.Settings;
  64. /// <summary>
  65. /// Gets the server version associated with this authentication plugin.
  66. /// </summary>
  67. protected Version ServerVersion => new Version(_driver.Version.Major, _driver.Version.Minor, _driver.Version.Build);
  68. internal ClientFlags Flags => _driver.Flags;
  69. /// <summary>
  70. /// Gets the encoding assigned to the native driver.
  71. /// </summary>
  72. protected Encoding Encoding => _driver.Encoding;
  73. /// <summary>
  74. /// Sets the authentication data required to encode, encrypt, or convert the password of the user.
  75. /// </summary>
  76. /// <param name="data">A byte array containing the authentication data provided by the server.</param>
  77. /// <remarks>This method may be overriden based on the requirements by the implementing authentication plugin.</remarks>
  78. protected virtual void SetAuthData(byte[] data)
  79. {
  80. AuthenticationData = data;
  81. }
  82. /// <summary>
  83. /// Defines the behavior when checking for constraints.
  84. /// </summary>
  85. /// <remarks>This method is intended to be overriden.</remarks>
  86. protected virtual void CheckConstraints()
  87. {
  88. }
  89. /// <summary>
  90. /// Throws a <see cref="MySqlException"/> that encapsulates the original exception.
  91. /// </summary>
  92. /// <param name="ex">The exception to encapsulate.</param>
  93. protected virtual void AuthenticationFailed(Exception ex)
  94. {
  95. string msg = String.Format(Resources.AuthenticationFailed, Settings.Server, GetUsername(), PluginName, ex.Message);
  96. throw new MySqlException(msg, ex);
  97. }
  98. /// <summary>
  99. /// Defines the behavior when authentication is successful.
  100. /// </summary>
  101. /// <remarks>This method is intended to be overriden.</remarks>
  102. protected virtual void AuthenticationSuccessful()
  103. {
  104. }
  105. /// <summary>
  106. /// Defines the behavior when more data is required from the server.
  107. /// </summary>
  108. /// <param name="data">The data returned by the server.</param>
  109. /// <returns>The data to return to the server.</returns>
  110. /// <remarks>This method is intended to be overriden.</remarks>
  111. protected virtual byte[] MoreData(byte[] data)
  112. {
  113. return null;
  114. }
  115. internal void Authenticate(bool reset)
  116. {
  117. CheckConstraints();
  118. MySqlPacket packet = _driver.Packet;
  119. // send auth response
  120. packet.WriteString(GetUsername());
  121. // now write the password
  122. WritePassword(packet);
  123. if ((Flags & ClientFlags.CONNECT_WITH_DB) != 0 || reset)
  124. {
  125. if (!String.IsNullOrEmpty(Settings.Database))
  126. packet.WriteString(Settings.Database);
  127. }
  128. if (reset)
  129. packet.WriteInteger(8, 2);
  130. if ((Flags & ClientFlags.PLUGIN_AUTH) != 0)
  131. packet.WriteString(PluginName);
  132. _driver.SetConnectAttrs();
  133. _driver.SendPacket(packet);
  134. // Read server response.
  135. packet = ReadPacket();
  136. byte[] b = packet.Buffer;
  137. if (PluginName == "caching_sha2_password" && b[0] == 0x01)
  138. {
  139. // React to the authentication type set by server: FAST, FULL.
  140. ContinueAuthentication(new byte[] { b[1] });
  141. }
  142. // Auth switch request Protocol::AuthSwitchRequest.
  143. if (b[0] == 0xfe)
  144. {
  145. if (packet.IsLastPacket)
  146. {
  147. _driver.Close(true);
  148. throw new MySqlException(Resources.OldPasswordsNotSupported);
  149. }
  150. else
  151. {
  152. HandleAuthChange(packet);
  153. }
  154. }
  155. _driver.ReadOk(false);
  156. AuthenticationSuccessful();
  157. }
  158. private void WritePassword(MySqlPacket packet)
  159. {
  160. bool secure = (Flags & ClientFlags.SECURE_CONNECTION) != 0;
  161. object password = GetPassword();
  162. if (password is string)
  163. {
  164. if (secure)
  165. packet.WriteLenString((string)password);
  166. else
  167. packet.WriteString((string)password);
  168. }
  169. else if (password == null)
  170. packet.WriteByte(0);
  171. else if (password is byte[])
  172. packet.Write(password as byte[]);
  173. else throw new MySqlException("Unexpected password format: " + password.GetType());
  174. }
  175. private MySqlPacket ReadPacket()
  176. {
  177. try
  178. {
  179. MySqlPacket p = _driver.ReadPacket();
  180. return p;
  181. }
  182. catch (MySqlException ex)
  183. {
  184. // Make sure this is an auth failed ex.
  185. AuthenticationFailed(ex);
  186. return null;
  187. }
  188. }
  189. private void HandleAuthChange(MySqlPacket packet)
  190. {
  191. byte b = packet.ReadByte();
  192. Debug.Assert(b == 0xfe);
  193. string method = packet.ReadString();
  194. byte[] authData = new byte[packet.Length - packet.Position];
  195. Array.Copy(packet.Buffer, packet.Position, authData, 0, authData.Length);
  196. MySqlAuthenticationPlugin plugin = GetPlugin(method, _driver, authData);
  197. plugin.ContinueAuthentication();
  198. }
  199. private void ContinueAuthentication(byte[] data = null)
  200. {
  201. MySqlPacket packet = _driver.Packet;
  202. packet.Clear();
  203. byte[] moreData = MoreData(data);
  204. while (moreData != null)
  205. {
  206. packet.Clear();
  207. packet.Write(moreData);
  208. _driver.SendPacket(packet);
  209. packet = ReadPacket();
  210. byte prefixByte = packet.Buffer[0];
  211. if (prefixByte != 1) return;
  212. // A prefix of 0x01 means need more auth data.
  213. byte[] responseData = new byte[packet.Length - 1];
  214. Array.Copy(packet.Buffer, 1, responseData, 0, responseData.Length);
  215. moreData = MoreData(responseData);
  216. }
  217. // We get here if MoreData returned null but the last packet read was a more data packet.
  218. ReadPacket();
  219. }
  220. /// <summary>
  221. /// Gets the plugin name based on the authentication plugin type defined during the creation of this object.
  222. /// </summary>
  223. public abstract string PluginName { get; }
  224. /// <summary>
  225. /// Gets the user name associated to the connection settings.
  226. /// </summary>
  227. /// <returns>The user name associated to the connection settings.</returns>
  228. public virtual string GetUsername()
  229. {
  230. return Settings.UserID;
  231. }
  232. /// <summary>
  233. /// Gets the encoded, encrypted, or converted password based on the authentication plugin type defined during the creation of this object.
  234. /// This method is intended to be overriden.
  235. /// </summary>
  236. /// <returns>An object containing the encoded, encrypted, or converted password.</returns>
  237. public virtual object GetPassword()
  238. {
  239. return null;
  240. }
  241. }
  242. }
  243. #endif