X Tutup
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Text; using System.Threading; using System.Threading.Tasks; using Npgsql.BackendMessages; using Npgsql.FrontendMessages; using Npgsql.Logging; namespace Npgsql { partial class NpgsqlConnector { async Task Authenticate(string username, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) { Log.Trace("Authenticating...", Id); var msg = await ReadExpecting(async); timeout.Check(); switch (msg.AuthRequestType) { case AuthenticationRequestType.AuthenticationOk: return; case AuthenticationRequestType.AuthenticationCleartextPassword: await AuthenticateCleartext(async, cancellationToken); return; case AuthenticationRequestType.AuthenticationMD5Password: await AuthenticateMD5(username, ((AuthenticationMD5PasswordMessage)msg).Salt, async, cancellationToken); return; case AuthenticationRequestType.AuthenticationGSS: case AuthenticationRequestType.AuthenticationSSPI: await AuthenticateGSS(async, cancellationToken); return; case AuthenticationRequestType.AuthenticationGSSContinue: throw new NpgsqlException("Can't start auth cycle with AuthenticationGSSContinue"); default: throw new NotSupportedException($"Authentication method not supported (Received: {msg.AuthRequestType})"); } } async Task AuthenticateCleartext(bool async, CancellationToken cancellationToken) { var passwd = Settings.Password; // No password was provided. Attempt to pull the password from the pgpass file. if (passwd == null) { var matchingEntry = PgPassFile.Load(Settings.Passfile)?.GetFirstMatchingEntry(Settings.Host, Settings.Port, Settings.Database, Settings.Username); if (matchingEntry != null) { Log.Trace("Taking password from pgpass file"); passwd = matchingEntry.Password; } } if (passwd == null) throw new NpgsqlException("No password has been provided but the backend requires one (in cleartext)"); await PasswordMessage .CreateClearText(passwd) .Write(WriteBuffer, async); await WriteBuffer.Flush(async); await ReadExpecting(async); } async Task AuthenticateMD5(string username, byte[] salt, bool async, CancellationToken cancellationToken) { var passwd = Settings.Password; // No password was provided. Attempt to pull the password from the pgpass file. if (passwd == null) { var matchingEntry = PgPassFile.Load(Settings.Passfile)?.GetFirstMatchingEntry(Settings.Host, Settings.Port, Settings.Database, Settings.Username); if (matchingEntry != null) { Log.Trace("Taking password from pgpass file"); passwd = matchingEntry.Password; } } if (passwd == null) throw new NpgsqlException("No password has been provided but the backend requires one (in MD5)"); await PasswordMessage .CreateMD5(passwd, username, salt) .Write(WriteBuffer, async); await WriteBuffer.Flush(async); await ReadExpecting(async); } #pragma warning disable 1998 async Task AuthenticateGSS(bool async, CancellationToken cancellationToken) #pragma warning restore 1998 { if (!IntegratedSecurity) throw new NpgsqlException("SSPI authentication but IntegratedSecurity not enabled"); using (var negotiateStream = new NegotiateStream(new GSSPasswordMessageStream(this), true)) { try { var targetName = $"{KerberosServiceName}/{Host}"; // AuthenticateAsClientAsync doesn't exist in .NET 4.5/4.5.1 (only introduced in 4.6) // Conversely, no sync in .NET Standard 1.3 :/ #if NETSTANDARD1_3 await negotiateStream.AuthenticateAsClientAsync(CredentialCache.DefaultNetworkCredentials, targetName); #else negotiateStream.AuthenticateAsClient(CredentialCache.DefaultNetworkCredentials, targetName); #endif } catch (AuthenticationCompleteException) { return; } catch (IOException e) when (e.InnerException is AuthenticationCompleteException) { return; } catch (IOException e) when (e.InnerException is PostgresException) { throw e.InnerException; } } throw new NpgsqlException("NegotiateStream.AuthenticateAsClient completed unexpectedly without signaling success"); } /// /// This Stream is placed between NegotiateStream and the socket's NetworkStream (or SSLStream). It intercepts /// traffic and performs the following operations: /// * Outgoing messages are framed in PostgreSQL's PasswordMessage, and incoming are stripped of it. /// * NegotiateStream frames payloads with a 5-byte header, which PostgreSQL doesn't understand. This header is /// stripped from outgoing messages and added to incoming ones. /// /// /// See https://referencesource.microsoft.com/#System/net/System/Net/_StreamFramer.cs,16417e735f0e9530,references /// class GSSPasswordMessageStream : Stream { readonly NpgsqlConnector _connector; readonly PasswordMessage _msg; int _leftToWrite; int _leftToRead, _readPos; byte[] _readBuf; internal GSSPasswordMessageStream(NpgsqlConnector connector) { _connector = connector; _msg = new PasswordMessage(); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => Write(buffer, offset, count, true); public override void Write(byte[] buffer, int offset, int count) => Write(buffer, offset, count, false).GetAwaiter().GetResult(); async Task Write(byte[] buffer, int offset, int count, bool async) { if (_leftToWrite == 0) { // We're writing the frame header, which contains the payload size. _leftToWrite = (buffer[3] << 8) | buffer[4]; buffer[0] = 22; if (buffer[1] != 1) throw new NotSupportedException($"Received frame header major v {buffer[1]} (different from 1)"); if (buffer[2] != 0) throw new NotSupportedException($"Received frame header minor v {buffer[2]} (different from 0)"); // In case of payload data in the same buffer just after the frame header if (count == 5) return; count -= 5; offset += 5; } if (count > _leftToWrite) throw new NpgsqlException($"NegotiateStream trying to write {count} bytes but according to frame header we only have {_leftToWrite} left!"); await _msg.Populate(buffer, offset, count) .Write(_connector.WriteBuffer, false); await _connector.WriteBuffer.Flush(async); _leftToWrite -= count; } public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => Read(buffer, offset, count, true); public override int Read(byte[] buffer, int offset, int count) => Read(buffer, offset, count, false).GetAwaiter().GetResult(); async Task Read(byte[] buffer, int offset, int count, bool async) { if (_leftToRead == 0) { var response = await _connector.ReadExpecting(async); if (response.AuthRequestType == AuthenticationRequestType.AuthenticationOk) throw new AuthenticationCompleteException(); var gssMsg = response as AuthenticationGSSContinueMessage; if (gssMsg == null) throw new NpgsqlException($"Received unexpected authentication request message {response.AuthRequestType}"); _readBuf = gssMsg.AuthenticationData; _leftToRead = gssMsg.AuthenticationData.Length; _readPos = 0; buffer[0] = 22; buffer[1] = 1; buffer[2] = 0; buffer[3] = (byte)((_leftToRead >> 8) & 0xFF); buffer[4] = (byte)(_leftToRead & 0xFF); return 5; } if (count > _leftToRead) throw new NpgsqlException($"NegotiateStream trying to read {count} bytes but according to frame header we only have {_leftToRead} left!"); count = Math.Min(count, _leftToRead); Array.Copy(_readBuf, _readPos, buffer, offset, count); _leftToRead -= count; return count; } public override void Flush() { } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override bool CanRead => true; public override bool CanWrite => true; public override bool CanSeek => false; public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } } class AuthenticationCompleteException : Exception { } } }
X Tutup