public static ColumnDefinitionPayload Create(PayloadData payload) { var reader = new ByteArrayReader(payload.ArraySegment); var catalog = reader.ReadLengthEncodedByteString(); var schema = reader.ReadLengthEncodedByteString(); var table = reader.ReadLengthEncodedByteString(); var physicalTable = reader.ReadLengthEncodedByteString(); var name = Encoding.UTF8.GetString(reader.ReadLengthEncodedByteString()); var physicalName = reader.ReadLengthEncodedByteString(); reader.ReadByte(0x0C); // length of fixed-length fields, always 0x0C var characterSet = (CharacterSet) reader.ReadUInt16(); var columnLength = (int) reader.ReadUInt32(); var columnType = (ColumnType) reader.ReadByte(); var columnFlags = (ColumnFlags) reader.ReadUInt16(); var decimals = reader.ReadByte(); // 0x00 for integers and static strings, 0x1f for dynamic strings, double, float, 0x00 to 0x51 for decimals reader.ReadByte(0); if (reader.BytesRemaining > 0) { int defaultValuesCount = checked((int) reader.ReadLengthEncodedInteger()); for (int i = 0; i < defaultValuesCount; i++) reader.ReadLengthEncodedByteString(); } if (reader.BytesRemaining != 0) throw new FormatException("Extra bytes at end of payload."); return new ColumnDefinitionPayload(name, characterSet, columnLength, columnType, columnFlags); }
private async Task DoSendAsync(PayloadData payload, CancellationToken cancellationToken) { var bytesSent = 0; var data = payload.ArraySegment; const int maxBytesToSend = 16777215; int bytesToSend; do { // break payload into packets of at most (2^24)-1 bytes bytesToSend = Math.Min(data.Count - bytesSent, maxBytesToSend); // write four-byte packet header; https://dev.mysql.com/doc/internals/en/mysql-packet.html SerializationUtility.WriteUInt32((uint) bytesToSend, m_buffer, 0, 3); m_buffer[3] = (byte) m_sequenceId; m_sequenceId++; if (bytesToSend <= m_buffer.Length - 4) { Buffer.BlockCopy(data.Array, data.Offset, m_buffer, 4, bytesToSend); m_socketAwaitable.EventArgs.SetBuffer(0, bytesToSend + 4); await m_socket.SendAsync(m_socketAwaitable); } else { m_socketAwaitable.EventArgs.SetBuffer(null, 0, 0); m_socketAwaitable.EventArgs.BufferList = new[] { new ArraySegment<byte>(m_buffer, 0, 4), data }; await m_socket.SendAsync(m_socketAwaitable); m_socketAwaitable.EventArgs.BufferList = null; m_socketAwaitable.EventArgs.SetBuffer(m_buffer, 0, 0); } bytesSent += bytesToSend; } while (bytesToSend == maxBytesToSend); }
public static AuthenticationMethodSwitchRequestPayload Create(PayloadData payload) { var reader = new ByteArrayReader(payload.ArraySegment); reader.ReadByte(Signature); var name = Encoding.UTF8.GetString(reader.ReadNullTerminatedByteString()); var data = reader.ReadByteString(reader.BytesRemaining); return new AuthenticationMethodSwitchRequestPayload(name, data); }
public static ErrorPayload Create(PayloadData payload) { var reader = new ByteArrayReader(payload.ArraySegment); reader.ReadByte(Signature); var errorCode = reader.ReadUInt16(); reader.ReadByte(0x23); var state = Encoding.ASCII.GetString(reader.ReadByteString(5)); var message = Encoding.UTF8.GetString(reader.ReadByteString(payload.ArraySegment.Count - 9)); return new ErrorPayload(errorCode, state, message); }
public static EofPayload Create(PayloadData payload) { var reader = new ByteArrayReader(payload.ArraySegment); reader.ReadByte(Signature); if (payload.ArraySegment.Count > 5) throw new FormatException("Not an EOF packet"); int warningCount = reader.ReadUInt16(); ServerStatus serverStatus = (ServerStatus) reader.ReadUInt16(); if (reader.BytesRemaining != 0) throw new FormatException("Extra bytes at end of payload."); return new EofPayload(warningCount, serverStatus); }
public static PrepareStatementOkPayload Create(PayloadData payload) { var reader = new ByteArrayReader(payload.ArraySegment); reader.ReadByte(0); var statementId = reader.ReadUInt32(); int columnCount = reader.ReadUInt16(); int parameterCount = reader.ReadUInt16(); reader.ReadByte(0); int warningCount = reader.ReadUInt16(); if (reader.BytesRemaining != 0) throw new FormatException("Extra bytes at end of payload."); return new PrepareStatementOkPayload(statementId, columnCount, parameterCount, warningCount); }
private async Task <PayloadData> SendEncryptedPasswordAsync( AuthenticationMethodSwitchRequestPayload switchRequest, string rsaPublicKey, ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken) { // load the RSA public key RSA rsa; try { rsa = Utility.DecodeX509PublicKey(rsaPublicKey); } catch (Exception ex) { throw new MySqlException("Couldn't load server's RSA public key; try using a secure connection instead.", ex); } // add NUL terminator to password var passwordBytes = Encoding.UTF8.GetBytes(cs.Password); Array.Resize(ref passwordBytes, passwordBytes.Length + 1); using (rsa) { // XOR the password bytes with the challenge AuthPluginData = Utility.TrimZeroByte(switchRequest.Data); for (var i = 0; i < passwordBytes.Length; i++) { passwordBytes[i] ^= AuthPluginData[i % AuthPluginData.Length]; } // encrypt with RSA public key var padding = switchRequest.Name == "caching_sha2_password" ? RSAEncryptionPadding.Pkcs1 : RSAEncryptionPadding.OaepSHA1; var encryptedPassword = rsa.Encrypt(passwordBytes, padding); var payload = new PayloadData(new ArraySegment <byte>(encryptedPassword)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); return(await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false)); } }
private ValueTask <PayloadData> TryAsync(Func <ProtocolErrorBehavior, IOBehavior, ValueTask <ArraySegment <byte> > > func, IOBehavior ioBehavior, CancellationToken cancellationToken) { VerifyConnected(); var task = func(ProtocolErrorBehavior.Throw, ioBehavior); if (task.IsCompletedSuccessfully) { var payload = new PayloadData(task.Result); if (payload.HeaderByte != ErrorPayload.Signature) { return(new ValueTask <PayloadData>(payload)); } var exception = ErrorPayload.Create(payload).ToException(); return(ValueTaskExtensions.FromException <PayloadData>(exception)); } return(new ValueTask <PayloadData>(task.AsTask() .ContinueWith(TryAsyncContinuation, cancellationToken, TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default))); }
// Continues a conversation with the server by sending a reply to a packet received with 'Receive' or 'ReceiveReply'. public ValueTask <int> SendReplyAsync(PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) { ValueTask <int> task; try { VerifyConnected(); task = m_payloadHandler.WritePayloadAsync(payload.ArraySegment, ioBehavior); } catch (Exception ex) { task = ValueTaskExtensions.FromException <int>(ex); } if (task.IsCompletedSuccessfully) { return(task); } return(new ValueTask <int>(task.AsTask().ContinueWith(TryAsyncContinuation, cancellationToken, TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default))); }
private PayloadData TryAsyncContinuation(Task <ArraySegment <byte> > task) { if (task.IsFaulted) { SetFailed(); } ArraySegment <byte> bytes; try { bytes = task.GetAwaiter().GetResult(); } catch (MySqlException ex) when(ex.Number == (int)MySqlErrorCode.CommandTimeoutExpired) { HandleTimeout(); throw; } var payload = new PayloadData(bytes); payload.ThrowIfError(); return(payload); }
public static ErrorPayload Create(PayloadData payload) { var reader = new ByteArrayReader(payload.ArraySegment); reader.ReadByte(Signature); var errorCode = reader.ReadUInt16(); var stateMarker = Encoding.ASCII.GetString(reader.ReadByteString(1)); string state, message; if (stateMarker == "#") { state = Encoding.ASCII.GetString(reader.ReadByteString(5)); message = Encoding.UTF8.GetString(reader.ReadByteString(payload.ArraySegment.Count - 9)); } else { state = "HY000"; message = stateMarker + Encoding.UTF8.GetString(reader.ReadByteString(payload.ArraySegment.Count - 4)); } return(new ErrorPayload(errorCode, state, message)); }
public static AuthenticationMethodSwitchRequestPayload Create(PayloadData payload) { var reader = new ByteArrayReader(payload.ArraySegment); reader.ReadByte(Signature); string name; byte[] data; if (payload.ArraySegment.Count == 1) { // if the packet is just the header byte (0xFE), it's an "Old Authentication Method Switch Request Packet" // (possibly sent by a server that doesn't support CLIENT_PLUGIN_AUTH) name = "mysql_old_password"; data = new byte[0]; } else { name = Encoding.UTF8.GetString(reader.ReadNullTerminatedByteString()); data = reader.ReadByteString(reader.BytesRemaining); } return(new AuthenticationMethodSwitchRequestPayload(name, data)); }
private bool ReadAsyncRemainder(PayloadData payload) { if (payload.HeaderByte == EofPayload.Signature) { var eof = EofPayload.Create(payload); m_state = eof.ServerStatus.HasFlag(ServerStatus.MoreResultsExist) ? State.HasMoreData : State.NoMoreData; return false; } var reader = new ByteArrayReader(payload.ArraySegment); for (var column = 0; column < m_dataOffsets.Length; column++) { var length = checked((int) ReadFieldLength(reader)); m_dataLengths[column] = length == -1 ? 0 : length; m_dataOffsets[column] = length == -1 ? -1 : reader.Offset; reader.Offset += m_dataLengths[column]; } m_currentRow = payload.ArraySegment.Array; m_state = State.ReadingRows; return true; }
public async Task ResetConnectionAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken) { VerifyState(State.Connected); if (ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0) { await SendAsync(ResetConnectionPayload.Create(), ioBehavior, cancellationToken).ConfigureAwait(false); var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); // the "reset connection" packet also resets the connection charset, so we need to change that back to our default payload = new PayloadData(new ArraySegment <byte>(PayloadUtilities.CreateEofStringPayload(CommandKind.Query, "SET NAMES utf8mb4 COLLATE utf8mb4_bin;"))); await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); } else { // optimistically hash the password with the challenge from the initial handshake (supported by MariaDB; doesn't appear to be supported by MySQL) var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password); var payload = ChangeUserPayload.Create(cs.UserID, hashedPassword, cs.Database, m_supportsConnectionAttributes ? s_connectionAttributes : null); await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature) { await SwitchAuthenticationAsync(cs, payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } OkPayload.Create(payload); } }
public void FinishQuerying() { bool clearConnection = false; lock (m_lock) { if (m_state == State.CancelingQuery) { m_state = State.ClearingPendingCancellation; clearConnection = true; } } if (clearConnection) { // KILL QUERY will kill a subsequent query if the command it was intended to cancel has already completed. // In order to handle this case, we issue a dummy query that will consume the pending cancellation. // See https://bugs.mysql.com/bug.php?id=45679 var payload = new PayloadData(new ArraySegment <byte>(PayloadUtilities.CreateEofStringPayload(CommandKind.Query, "DO SLEEP(0);"))); SendAsync(payload, IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult(); payload = ReceiveReplyAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult(); OkPayload.Create(payload); } lock (m_lock) { if (m_state == State.Querying || m_state == State.ClearingPendingCancellation) { m_state = State.Connected; } else { VerifyState(State.Failed); } m_activeCommandId = 0; } }
private async Task DoSendAsync(PayloadData payload, CancellationToken cancellationToken) { var bytesSent = 0; var data = payload.ArraySegment; const int maxBytesToSend = 16777215; int bytesToSend; do { // break payload into packets of at most (2^24)-1 bytes bytesToSend = Math.Min(data.Count - bytesSent, maxBytesToSend); // write four-byte packet header; https://dev.mysql.com/doc/internals/en/mysql-packet.html SerializationUtility.WriteUInt32((uint)bytesToSend, m_buffer, 0, 3); m_buffer[3] = (byte)m_sequenceId; m_sequenceId++; if (bytesToSend <= m_buffer.Length - 4) { Buffer.BlockCopy(data.Array, data.Offset, m_buffer, 4, bytesToSend); m_socketAwaitable.EventArgs.SetBuffer(0, bytesToSend + 4); await m_socket.SendAsync(m_socketAwaitable); } else { m_socketAwaitable.EventArgs.SetBuffer(null, 0, 0); m_socketAwaitable.EventArgs.BufferList = new[] { new ArraySegment <byte>(m_buffer, 0, 4), data }; await m_socket.SendAsync(m_socketAwaitable); m_socketAwaitable.EventArgs.BufferList = null; m_socketAwaitable.EventArgs.SetBuffer(m_buffer, 0, 0); } bytesSent += bytesToSend; } while (bytesToSend == maxBytesToSend); }
// Starts a new conversation with the server by sending the first packet. public ValueTask <int> SendAsync(PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) { m_payloadHandler.StartNewConversation(); return(SendReplyAsync(payload, ioBehavior, cancellationToken)); }
private async Task <PayloadData> SwitchAuthenticationAsync(ConnectionSettings cs, PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) { // if the server didn't support the hashed password; rehash with the new challenge var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload); switch (switchRequest.Name) { case "mysql_native_password": AuthPluginData = switchRequest.Data; var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password); payload = new PayloadData(new ArraySegment <byte>(hashedPassword)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); return(await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false)); case "mysql_clear_password": if (!m_isSecureConnection) { throw new MySqlException("Authentication method '{0}' requires a secure connection.".FormatInvariant(switchRequest.Name)); } payload = new PayloadData(new ArraySegment <byte>(Encoding.UTF8.GetBytes(cs.Password))); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); return(await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false)); case "caching_sha2_password": var scrambleBytes = AuthenticationUtility.CreateScrambleResponse(Utility.TrimZeroByte(switchRequest.Data), cs.Password); payload = new PayloadData(new ArraySegment <byte>(scrambleBytes)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var cachingSha2ServerResponsePayload = CachingSha2ServerResponsePayload.Create(payload); if (cachingSha2ServerResponsePayload.Succeeded) { return(await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false)); } goto case "sha256_password"; case "sha256_password": if (!m_isSecureConnection && cs.Password.Length > 1) { #if NET45 throw new MySqlException("Authentication method '{0}' requires a secure connection (prior to .NET 4.6).".FormatInvariant(switchRequest.Name)); #else var publicKey = await GetRsaPublicKeyAsync(switchRequest.Name, cs, ioBehavior, cancellationToken).ConfigureAwait(false); return(await SendEncryptedPasswordAsync(switchRequest, publicKey, cs, ioBehavior, cancellationToken).ConfigureAwait(false)); #endif } else { return(await SendClearPasswordAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false)); } case "mysql_old_password": throw new NotSupportedException("'MySQL Server is requesting the insecure pre-4.1 auth mechanism (mysql_old_password). The user password must be upgraded; see https://dev.mysql.com/doc/refman/5.7/en/account-upgrades.html."); default: throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(switchRequest.Name)); } }
// Continues a conversation with the server by sending a reply to a packet received with 'Receive' or 'ReceiveReply'. public Task SendReplyAsync(PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) => DoSendAsync(payload, ioBehavior, cancellationToken);
/* See * http://web.archive.org/web/20160604101747/http://dev.mysql.com/doc/internals/en/packet-OK_Packet.html * https://mariadb.com/kb/en/the-mariadb-library/resultset/ * https://github.com/MariaDB/mariadb-connector-j/blob/5fa814ac6e1b4c9cb6d141bd221cbd5fc45c8a78/src/main/java/org/mariadb/jdbc/internal/com/read/resultset/SelectResultSet.java#L443-L444 */ public static bool IsOk(PayloadData payload, bool deprecateEof) => payload.ArraySegment.Array != null && payload.ArraySegment.Count > 0 && ((payload.ArraySegment.Count > 6 && payload.ArraySegment.Array[payload.ArraySegment.Offset] == Signature) || (deprecateEof && payload.ArraySegment.Count < 0xFF_FFFF && payload.ArraySegment.Array[payload.ArraySegment.Offset] == EofPayload.Signature));
public async Task ConnectAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken) { lock (m_lock) { VerifyState(State.Created); m_state = State.Connecting; } var connected = false; if (cs.ConnectionType == ConnectionType.Tcp) { connected = await OpenTcpSocketAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false); } else if (cs.ConnectionType == ConnectionType.Unix) { connected = await OpenUnixSocketAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false); } if (!connected) { lock (m_lock) m_state = State.Failed; throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } var byteHandler = new SocketByteHandler(m_socket); m_payloadHandler = new StandardPayloadHandler(byteHandler); var payload = await ReceiveAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count); var initialHandshake = new InitialHandshakePacket(reader); // if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use string authPluginName; if ((initialHandshake.ProtocolCapabilities & ProtocolCapabilities.PluginAuth) != 0) { authPluginName = initialHandshake.AuthPluginName; } else { authPluginName = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.SecureConnection) == 0 ? "mysql_old_password" : "mysql_native_password"; } if (authPluginName != "mysql_native_password" && authPluginName != "sha256_password" && authPluginName != "caching_sha2_password") { throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(initialHandshake.AuthPluginName)); } ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion)); ConnectionId = initialHandshake.ConnectionId; AuthPluginData = initialHandshake.AuthPluginData; m_useCompression = cs.UseCompression && (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Compress) != 0; var serverSupportsSsl = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Ssl) != 0; if (cs.SslMode != MySqlSslMode.None && (cs.SslMode != MySqlSslMode.Preferred || serverSupportsSsl)) { if (!serverSupportsSsl) { throw new MySqlException("Server does not support SSL"); } await InitSslAsync(initialHandshake.ProtocolCapabilities, cs, ioBehavior, cancellationToken).ConfigureAwait(false); } m_supportsConnectionAttributes = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.ConnectionAttributes) != 0; if (m_supportsConnectionAttributes && s_connectionAttributes == null) { s_connectionAttributes = CreateConnectionAttributes(); } m_supportsDeprecateEof = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.DeprecateEof) != 0; var response = HandshakeResponse41Packet.Create(initialHandshake, cs, m_useCompression, m_supportsConnectionAttributes ? s_connectionAttributes : null); payload = new PayloadData(new ArraySegment <byte>(response)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); // if server doesn't support the authentication fast path, it will send a new challenge if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature) { payload = await SwitchAuthenticationAsync(cs, payload, ioBehavior, cancellationToken).ConfigureAwait(false); } OkPayload.Create(payload); if (m_useCompression) { m_payloadHandler = new CompressedPayloadHandler(m_payloadHandler.ByteHandler); } }
private async Task SwitchAuthenticationAsync(ConnectionSettings cs, PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) { // if the server didn't support the hashed password; rehash with the new challenge var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload); switch (switchRequest.Name) { case "mysql_native_password": AuthPluginData = switchRequest.Data; var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password); payload = new PayloadData(new ArraySegment <byte>(hashedPassword)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); break; case "mysql_clear_password": if (!m_isSecureConnection) { throw new MySqlException("Authentication method '{0}' requires a secure connection.".FormatInvariant(switchRequest.Name)); } payload = new PayloadData(new ArraySegment <byte>(Encoding.UTF8.GetBytes(cs.Password))); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); break; case "sha256_password": // add NUL terminator to password var passwordBytes = Encoding.UTF8.GetBytes(cs.Password); Array.Resize(ref passwordBytes, passwordBytes.Length + 1); if (!m_isSecureConnection && passwordBytes.Length > 1) { #if NET45 throw new MySqlException("Authentication method '{0}' requires a secure connection (prior to .NET 4.6).".FormatInvariant(switchRequest.Name)); #else string publicKey; if (!string.IsNullOrEmpty(cs.ServerRsaPublicKeyFile)) { try { publicKey = File.ReadAllText(cs.ServerRsaPublicKeyFile); } catch (IOException ex) { throw new MySqlException("Couldn't load server's RSA public key from '{0}'".FormatInvariant(cs.ServerRsaPublicKeyFile), ex); } } else if (cs.AllowPublicKeyRetrieval) { // request the RSA public key await SendReplyAsync(new PayloadData(new ArraySegment <byte>(new byte[] { 0x01 }, 0, 1)), ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var publicKeyPayload = AuthenticationMoreDataPayload.Create(payload); publicKey = Encoding.ASCII.GetString(publicKeyPayload.Data); } else { throw new MySqlException("Authentication method '{0}' failed. Either use a secure connection, specify the server's RSA public key with ServerRSAPublicKeyFile, or set AllowPublicKeyRetrieval=True.".FormatInvariant(switchRequest.Name)); } // load the RSA public key RSA rsa; try { rsa = Utility.DecodeX509PublicKey(publicKey); } catch (Exception ex) { throw new MySqlException("Couldn't load server's RSA public key; try using a secure connection instead.", ex); } using (rsa) { // XOR the password bytes with the challenge AuthPluginData = Utility.TrimZeroByte(switchRequest.Data); for (int i = 0; i < passwordBytes.Length; i++) { passwordBytes[i] ^= AuthPluginData[i % AuthPluginData.Length]; } // encrypt with RSA public key var encryptedPassword = rsa.Encrypt(passwordBytes, RSAEncryptionPadding.OaepSHA1); payload = new PayloadData(new ArraySegment <byte>(encryptedPassword)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); } #endif } else { // send plaintext password payload = new PayloadData(new ArraySegment <byte>(passwordBytes)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); } break; default: throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(switchRequest.Name)); } }
/// <summary> /// Returns <c>true</c> if <paramref name="payload"/> contains an <a href="https://dev.mysql.com/doc/internals/en/packet-EOF_Packet.html">EOF packet</a>. /// Note that EOF packets can appear in places where a length-encoded integer (which starts with the same signature byte) may appear, so the length /// has to be checked to verify that it is an EOF packet. /// </summary> /// <param name="payload">The payload to examine.</param> /// <returns><c>true</c> if this is an EOF packet; otherwise, <c>false</c>.</returns> public static bool IsEof(PayloadData payload) => payload.ArraySegment.Count > 0 && payload.ArraySegment.Count < 9 && payload.ArraySegment.Array[payload.ArraySegment.Offset] == Signature;
private LocalInfilePayload(PayloadData payload) { FileName = Utility.GetString(Encoding.UTF8, Utility.Slice(payload.ArraySegment, 1)); }
public override async Task OpenAsync(CancellationToken cancellationToken) { VerifyNotDisposed(); if (State != ConnectionState.Closed) throw new InvalidOperationException("Cannot Open when State is {0}.".FormatInvariant(State)); #if !NETSTANDARD1_3 if (System.Transactions.Transaction.Current != null) throw new NotSupportedException("Ambient transactions are not supported. Use BeginTransaction instead."); #endif if (m_connectionStringBuilder.UseCompression) throw new NotSupportedException("Compression not supported."); SetState(ConnectionState.Connecting); bool success = false; try { var pool = ConnectionPool.GetPool(m_connectionStringBuilder); m_session = pool?.TryGetSession(); if (m_session == null) { m_session = new MySqlSession(pool); var connected = await m_session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int) m_connectionStringBuilder.Port).ConfigureAwait(false); if (!connected) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } var payload = await m_session.ReceiveAsync(cancellationToken).ConfigureAwait(false); var reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count); var initialHandshake = new InitialHandshakePacket(reader); if (initialHandshake.AuthPluginName != "mysql_native_password") throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported."); m_session.ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion)); m_session.AuthPluginData = initialHandshake.AuthPluginData; var response = HandshakeResponse41Packet.Create(initialHandshake, m_connectionStringBuilder.UserID, m_connectionStringBuilder.Password, m_database); payload = new PayloadData(new ArraySegment<byte>(response)); await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); // TODO: Check success } else if (m_connectionStringBuilder.ConnectionReset) { if (m_session.ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0) { await m_session.SendAsync(ResetConnectionPayload.Create(), cancellationToken).ConfigureAwait(false); var payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); } else { // optimistically hash the password with the challenge from the initial handshake (supported by MariaDB; doesn't appear to be supported by MySQL) var hashedPassword = AuthenticationUtility.HashPassword(m_session.AuthPluginData, 0, m_connectionStringBuilder.Password); var payload = ChangeUserPayload.Create(m_connectionStringBuilder.UserID, hashedPassword, m_database); await m_session.SendAsync(payload, cancellationToken).ConfigureAwait(false); payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature) { // if the server didn't support the hashed password; rehash with the new challenge var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload); if (switchRequest.Name != "mysql_native_password") throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported."); hashedPassword = AuthenticationUtility.HashPassword(switchRequest.Data, 0, m_connectionStringBuilder.Password); payload = new PayloadData(new ArraySegment<byte>(hashedPassword)); await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); } OkPayload.Create(payload); } } m_hasBeenOpened = true; SetState(ConnectionState.Open); success = true; } catch (MySqlException) { SetState(ConnectionState.Closed); throw; } catch (SocketException ex) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts.", ex); } finally { if (!success) Utility.Dispose(ref m_session); } }
// Starts a new conversation with the server by sending the first packet. public Task SendAsync(PayloadData payload, CancellationToken cancellationToken) => TryAsync(m_transmitter.SendAsync, payload, cancellationToken);
public static LocalInfilePayload Create(PayloadData payload) => new LocalInfilePayload(payload);
private async Task InitSslAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken) { X509Certificate2 certificate; try { certificate = new X509Certificate2(cs.CertificateFile, cs.CertificatePassword); } catch (CryptographicException ex) { if (!File.Exists(cs.CertificateFile)) { throw new MySqlException("Cannot find SSL Certificate File", ex); } throw new MySqlException("Either the SSL Certificate Password is incorrect or the SSL Certificate File is invalid", ex); } Func <object, string, X509CertificateCollection, X509Certificate, string[], X509Certificate> localCertificateCb = (lcbSender, lcbTargetHost, lcbLocalCertificates, lcbRemoteCertificate, lcbAcceptableIssuers) => lcbLocalCertificates[0]; Func <object, X509Certificate, X509Chain, SslPolicyErrors, bool> remoteCertificateCb = (rcbSender, rcbCertificate, rcbChain, rcbPolicyErrors) => { switch (rcbPolicyErrors) { case SslPolicyErrors.None: return(true); case SslPolicyErrors.RemoteCertificateNameMismatch: return(cs.SslMode != MySqlSslMode.VerifyFull); default: return(cs.SslMode == MySqlSslMode.Required); } }; var sslStream = new SslStream(m_networkStream, false, new RemoteCertificateValidationCallback(remoteCertificateCb), new LocalCertificateSelectionCallback(localCertificateCb)); var clientCertificates = new X509CertificateCollection { certificate }; // SslProtocols.Tls1.2 throws an exception in Windows, see https://github.com/mysql-net/MySqlConnector/pull/101 var sslProtocols = SslProtocols.Tls | SslProtocols.Tls11; if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { sslProtocols |= SslProtocols.Tls12; } var checkCertificateRevocation = cs.SslMode == MySqlSslMode.VerifyFull; var initSsl = new PayloadData(new ArraySegment <byte>(HandshakeResponse41Packet.InitSsl(cs))); await SendReplyAsync(initSsl, ioBehavior, cancellationToken).ConfigureAwait(false); try { if (ioBehavior == IOBehavior.Asynchronous) { await sslStream.AuthenticateAsClientAsync(m_hostname, clientCertificates, sslProtocols, checkCertificateRevocation).ConfigureAwait(false); } else { #if NETSTANDARD1_3 await sslStream.AuthenticateAsClientAsync(m_hostname, clientCertificates, sslProtocols, checkCertificateRevocation).ConfigureAwait(false); #else sslStream.AuthenticateAsClient(m_hostname, clientCertificates, sslProtocols, checkCertificateRevocation); #endif } var sslByteHandler = new StreamByteHandler(sslStream); m_payloadHandler.ByteHandler = sslByteHandler; } catch (AuthenticationException ex) { ShutdownSocket(); m_hostname = ""; m_state = State.Failed; throw new MySqlException("SSL Authentication Error", ex); } }
protected override async Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) { VerifyValid(); Connection.HasActiveReader = true; MySqlDataReader reader = null; try { LastInsertedId = -1; var connection = (MySqlConnection) DbConnection; var statementPreparerOptions = StatementPreparerOptions.None; if (connection.AllowUserVariables) statementPreparerOptions |= StatementPreparerOptions.AllowUserVariables; if (connection.OldGuids) statementPreparerOptions |= StatementPreparerOptions.OldGuids; var preparer = new MySqlStatementPreparer(CommandText, m_parameterCollection, statementPreparerOptions); preparer.BindParameters(); var payload = new PayloadData(new ArraySegment<byte>(Payload.CreateEofStringPayload(CommandKind.Query, preparer.PreparedSql))); await Session.SendAsync(payload, cancellationToken).ConfigureAwait(false); reader = await MySqlDataReader.CreateAsync(this, behavior, cancellationToken).ConfigureAwait(false); return reader; } finally { if (reader == null) { // received an error from MySQL and never created an active reader Connection.HasActiveReader = false; } } }
// Continues a conversation with the server by sending a reply to a packet received with 'Receive' or 'ReceiveReply'. public Task SendReplyAsync(PayloadData payload, CancellationToken cancellationToken) => DoSendAsync(payload, cancellationToken);
// Starts a new conversation with the server by sending the first packet. public Task SendAsync(PayloadData payload, CancellationToken cancellationToken) { m_sequenceId = 0; return(DoSendAsync(payload, cancellationToken)); }
private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken) { X509CertificateCollection clientCertificates = null; if (cs.CertificateFile != null) { try { var certificate = new X509Certificate2(cs.CertificateFile, cs.CertificatePassword); #if !NET45 m_clientCertificate = certificate; #endif clientCertificates = new X509CertificateCollection { certificate }; } catch (CryptographicException ex) { if (!File.Exists(cs.CertificateFile)) { throw new MySqlException("Cannot find Certificate File", ex); } throw new MySqlException("Either the Certificate Password is incorrect or the Certificate File is invalid", ex); } } X509Chain caCertificateChain = null; if (cs.CACertificateFile != null) { try { var caCertificate = new X509Certificate2(cs.CACertificateFile); #if !NET45 m_serverCertificate = caCertificate; #endif caCertificateChain = new X509Chain { ChainPolicy = { RevocationMode = X509RevocationMode.NoCheck, VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority } }; caCertificateChain.ChainPolicy.ExtraStore.Add(caCertificate); } catch (CryptographicException ex) { if (!File.Exists(cs.CACertificateFile)) { throw new MySqlException("Cannot find CA Certificate File", ex); } throw new MySqlException("The CA Certificate File is invalid", ex); } } X509Certificate ValidateLocalCertificate(object lcbSender, string lcbTargetHost, X509CertificateCollection lcbLocalCertificates, X509Certificate lcbRemoteCertificate, string[] lcbAcceptableIssuers) => lcbLocalCertificates[0]; bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate, X509Chain rcbChain, SslPolicyErrors rcbPolicyErrors) { if (cs.SslMode == MySqlSslMode.Preferred || cs.SslMode == MySqlSslMode.Required) { return(true); } if ((rcbPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0 && caCertificateChain != null) { if (caCertificateChain.Build((X509Certificate2)rcbCertificate)) { var chainStatus = caCertificateChain.ChainStatus[0].Status & ~X509ChainStatusFlags.UntrustedRoot; if (chainStatus == X509ChainStatusFlags.NoError) { rcbPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors; } } } if (cs.SslMode == MySqlSslMode.VerifyCA) { rcbPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNameMismatch; } return(rcbPolicyErrors == SslPolicyErrors.None); } SslStream sslStream; if (clientCertificates == null) { sslStream = new SslStream(m_networkStream, false, ValidateRemoteCertificate); } else { sslStream = new SslStream(m_networkStream, false, ValidateRemoteCertificate, ValidateLocalCertificate); } // SslProtocols.Tls1.2 throws an exception in Windows, see https://github.com/mysql-net/MySqlConnector/pull/101 var sslProtocols = SslProtocols.Tls | SslProtocols.Tls11; if (!Utility.IsWindows()) { sslProtocols |= SslProtocols.Tls12; } var checkCertificateRevocation = cs.SslMode == MySqlSslMode.VerifyFull; var initSsl = new PayloadData(new ArraySegment <byte>(HandshakeResponse41Packet.InitSsl(serverCapabilities, cs, m_useCompression))); await SendReplyAsync(initSsl, ioBehavior, cancellationToken).ConfigureAwait(false); try { if (ioBehavior == IOBehavior.Asynchronous) { await sslStream.AuthenticateAsClientAsync(m_hostname, clientCertificates, sslProtocols, checkCertificateRevocation).ConfigureAwait(false); } else { #if NETSTANDARD1_3 await sslStream.AuthenticateAsClientAsync(m_hostname, clientCertificates, sslProtocols, checkCertificateRevocation).ConfigureAwait(false); #else sslStream.AuthenticateAsClient(m_hostname, clientCertificates, sslProtocols, checkCertificateRevocation); #endif } var sslByteHandler = new StreamByteHandler(sslStream); m_payloadHandler.ByteHandler = sslByteHandler; m_isSecureConnection = true; } catch (Exception ex) { sslStream.Dispose(); ShutdownSocket(); m_hostname = ""; lock (m_lock) m_state = State.Failed; if (ex is AuthenticationException) { throw new MySqlException("SSL Authentication Error", ex); } if (ex is IOException && clientCertificates != null) { throw new MySqlException("MySQL Server rejected client certificate", ex); } throw; } }
public async Task ConnectAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken) { var connected = false; if (cs.ConnectionType == ConnectionType.Tcp) { connected = await OpenTcpSocketAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false); } else if (cs.ConnectionType == ConnectionType.Unix) { connected = await OpenUnixSocketAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false); } if (!connected) { throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } var socketByteHandler = new SocketByteHandler(m_socket); m_payloadHandler = new StandardPayloadHandler(socketByteHandler); var payload = await ReceiveAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count); var initialHandshake = new InitialHandshakePacket(reader); // if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use string authPluginName; if ((initialHandshake.ProtocolCapabilities & ProtocolCapabilities.PluginAuth) != 0) { authPluginName = initialHandshake.AuthPluginName; } else { authPluginName = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.SecureConnection) == 0 ? "mysql_old_password" : "mysql_native_password"; } if (authPluginName != "mysql_native_password") { throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(initialHandshake.AuthPluginName)); } ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion)); ConnectionId = initialHandshake.ConnectionId; AuthPluginData = initialHandshake.AuthPluginData; if (cs.UseCompression && (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Compress) == 0) { cs = cs.WithUseCompression(false); } if (cs.SslMode != MySqlSslMode.None) { await InitSslAsync(initialHandshake.ProtocolCapabilities, cs, ioBehavior, cancellationToken).ConfigureAwait(false); } var response = HandshakeResponse41Packet.Create(initialHandshake, cs); payload = new PayloadData(new ArraySegment <byte>(response)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); if (cs.UseCompression) { m_payloadHandler = new CompressedPayloadHandler(m_payloadHandler.ByteHandler); } }
// Starts a new conversation with the server by sending the first packet. public Task SendAsync(PayloadData payload, CancellationToken cancellationToken) { m_sequenceId = 0; return DoSendAsync(payload, cancellationToken); }
// Starts a new conversation with the server by sending the first packet. public ValueTask <int> SendAsync(PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) { m_payloadHandler.StartNewConversation(); return(TryAsync(m_payloadHandler.WritePayloadAsync, payload.ArraySegment, ioBehavior, cancellationToken)); }
// Continues a conversation with the server by sending a reply to a packet received with 'Receive' or 'ReceiveReply'. public ValueTask <int> SendReplyAsync(PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) => TryAsync(m_payloadHandler.WritePayloadAsync, payload.ArraySegment, ioBehavior, cancellationToken);
// Continues a conversation with the server by sending a reply to a packet received with 'Receive' or 'ReceiveReply'. public Task SendReplyAsync(PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) => TryAsync(m_transmitter.SendReplyAsync, payload, ioBehavior, cancellationToken);