diff --git a/src/Npgsql.csproj b/src/Npgsql.csproj index 0eef897b71..4a92fcb7ca 100644 --- a/src/Npgsql.csproj +++ b/src/Npgsql.csproj @@ -146,6 +146,7 @@ + diff --git a/src/Npgsql/NpgsqlConnection.cs b/src/Npgsql/NpgsqlConnection.cs index f8ef238fc4..5e76856dc8 100644 --- a/src/Npgsql/NpgsqlConnection.cs +++ b/src/Npgsql/NpgsqlConnection.cs @@ -1035,6 +1035,13 @@ private void LoadConnectionStringBuilder(string connectionString) cache[connectionString] = settings; } + // Clone the settings, because if Integrated Security is enabled, user ID can be different + settings = settings.Clone(); + + // Set the UserName explicitly to freeze any Integrated Security-determined names + if(settings.IntegratedSecurity) + settings.UserName = settings.UserName; + RefreshConnectionString(); LogConnectionString(); } diff --git a/src/Npgsql/NpgsqlConnectionStringBuilder.cs b/src/Npgsql/NpgsqlConnectionStringBuilder.cs index e85642b8cd..4e5b4801ba 100644 --- a/src/Npgsql/NpgsqlConnectionStringBuilder.cs +++ b/src/Npgsql/NpgsqlConnectionStringBuilder.cs @@ -31,6 +31,7 @@ using System; using System.Collections.Generic; using System.Data.Common; +using System.DirectoryServices.AccountManagement; using System.Reflection; using System.Resources; using System.Text; @@ -69,6 +70,7 @@ static NpgsqlConnectionStringBuilder() defaults.Add(Keywords.PreloadReader, false); defaults.Add(Keywords.UseExtendedTypes, false); defaults.Add(Keywords.IntegratedSecurity, false); + defaults.Add(Keywords.IncludeRealm, false); defaults.Add(Keywords.Compatible, THIS_VERSION); defaults.Add(Keywords.ApplicationName, string.Empty); } @@ -276,9 +278,17 @@ public string UserName { if ((_integrated_security) && (String.IsNullOrEmpty(_username))) { - System.Security.Principal.WindowsIdentity identity = - System.Security.Principal.WindowsIdentity.GetCurrent(); - _username = identity.Name.Split('\\')[1]; + string[] usernameParts = UserPrincipal.Current.UserPrincipalName.Split('@'); + + // With the realm split out, decide whether to add it back + if (_includeRealm) + { + _username = usernameParts[0] + "@" + usernameParts[1].ToUpperInvariant(); + } + else + { + _username = usernameParts[0]; + } } return _username; } @@ -402,6 +412,13 @@ public bool IntegratedSecurity set { SetValue(GetKeyName(Keywords.IntegratedSecurity), value); } } + private bool _includeRealm; + public bool IncludeRealm + { + get { return _includeRealm; } + set { SetValue(GetKeyName(Keywords.IncludeRealm), value); } + } + private Version _compatible; private static readonly Version THIS_VERSION = @@ -486,6 +503,8 @@ private static Keywords GetKey(string key) return Keywords.UseExtendedTypes; case "INTEGRATED SECURITY": return Keywords.IntegratedSecurity; + case "INCLUDEREALM": + return Keywords.IncludeRealm; case "COMPATIBLE": return Keywords.Compatible; case "APPLICATIONNAME": @@ -543,6 +562,8 @@ internal static string GetKeyName(Keywords keyword) return "USEEXTENDEDTYPES"; case Keywords.IntegratedSecurity: return "INTEGRATED SECURITY"; + case Keywords.IncludeRealm: + return "INCLUDEREALM"; case Keywords.Compatible: return "COMPATIBLE"; default: @@ -702,6 +723,9 @@ private void SetValue(Keywords keyword, object value) case Keywords.IntegratedSecurity: this._integrated_security = ToIntegratedSecurity(value); break; + case Keywords.IncludeRealm: + this._includeRealm = ToBoolean(value); + break; case Keywords.Compatible: Version ver = new Version(value.ToString()); if (ver > THIS_VERSION) @@ -731,6 +755,7 @@ private void SetValue(Keywords keyword, object value) case Keywords.SSL: case Keywords.Pooling: case Keywords.SyncNotification: + case Keywords.IncludeRealm: exception_template = resman.GetString("Exception_InvalidBooleanKeyVal"); break; case Keywords.Protocol: @@ -789,7 +814,8 @@ public enum Keywords UseExtendedTypes, IntegratedSecurity, Compatible, - ApplicationName + ApplicationName, + IncludeRealm } public enum SslMode diff --git a/src/Npgsql/NpgsqlPasswordPacket.cs b/src/Npgsql/NpgsqlPasswordPacket.cs index 270f768e4e..6ab647ec9f 100644 --- a/src/Npgsql/NpgsqlPasswordPacket.cs +++ b/src/Npgsql/NpgsqlPasswordPacket.cs @@ -61,19 +61,19 @@ public override void WriteToStream(Stream outputStream) { case ProtocolVersion.Version2: // Write the size of the packet. - // 4 + (passwordlength + 1) -> Int32 + NULL terminated string. - // output_stream.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(4 + (password.Length + 1))), 0, 4); + // 4 + (passwordlength) -> Int32 + Byte string. Null termination is done before we get here. + // output_stream.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(4 + (password.Length))), 0, 4); outputStream - .WriteInt32(4 + password.Length + 1) - .WriteBytesNullTerminated(password); + .WriteInt32(4 + password.Length) + .WriteBytes(password); break; case ProtocolVersion.Version3: outputStream .WriteBytes((Byte)ASCIIBytes.p) - .WriteInt32(4 + password.Length + 1) - .WriteBytesNullTerminated(password); + .WriteInt32(4 + password.Length) + .WriteBytes(password); break; } diff --git a/src/Npgsql/NpgsqlState.cs b/src/Npgsql/NpgsqlState.cs index 434401a70b..321f748f35 100644 --- a/src/Npgsql/NpgsqlState.cs +++ b/src/Npgsql/NpgsqlState.cs @@ -445,6 +445,13 @@ internal bool CheckForContextSocketAvailability (NpgsqlConnector context, Select return socketPoolResponse || context.Socket.Poll (1000000 * secondsToWait, selectMode); } + static byte[] NullTerminateArray(byte[] input) { + byte[] output = new byte[input.Length + 1]; + input.CopyTo(output, 0); + + return output; + } + protected IEnumerable ProcessBackendResponses_Ver_2(NpgsqlConnector context) { NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessBackendResponses"); @@ -498,7 +505,7 @@ protected IEnumerable ProcessBackendResponses_Ver_2(Npgsq NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationClearTextRequest", LogLevel.Debug); // Send the PasswordPacket. ChangeState(context, NpgsqlStartupState.Instance); - context.Authenticate(context.Password); + context.Authenticate(NullTerminateArray(context.Password)); break; case AuthenticationRequestType.AuthenticationMD5Password: NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationMD5Request", LogLevel.Debug); @@ -547,7 +554,7 @@ protected IEnumerable ProcessBackendResponses_Ver_2(Npgsq sb.Append(b.ToString("x2")); } - context.Authenticate(BackendEncoding.UTF8Encoding.GetBytes(sb.ToString())); + context.Authenticate(NullTerminateArray(BackendEncoding.UTF8Encoding.GetBytes(sb.ToString()))); break; default: @@ -737,7 +744,7 @@ protected IEnumerable ProcessBackendResponses_Ver_3(Npgsq // Send the PasswordPacket. ChangeState(context, NpgsqlStartupState.Instance); - context.Authenticate(context.Password); + context.Authenticate(NullTerminateArray(context.Password)); break; case AuthenticationRequestType.AuthenticationMD5Password: @@ -784,18 +791,35 @@ protected IEnumerable ProcessBackendResponses_Ver_3(Npgsq sb.Append(b.ToString("x2")); } - context.Authenticate(BackendEncoding.UTF8Encoding.GetBytes(sb.ToString())); + context.Authenticate(NullTerminateArray(BackendEncoding.UTF8Encoding.GetBytes(sb.ToString()))); break; #if WINDOWS && UNMANAGED + case AuthenticationRequestType.AuthenticationGSS: + { + if (context.IntegratedSecurity) + { + // For SSPI we have to get the IP-Address (hostname doesn't work) + context.SSPI = new SSPIHandler(context.Host, "POSTGRES", true); + ChangeState(context, NpgsqlStartupState.Instance); + context.Authenticate(context.SSPI.Continue(null)); + break; + } + else + { + // TODO: correct exception + throw new Exception(); + } + } + case AuthenticationRequestType.AuthenticationSSPI: { if (context.IntegratedSecurity) { // For SSPI we have to get the IP-Address (hostname doesn't work) string ipAddressString = ((IPEndPoint)context.Socket.RemoteEndPoint).Address.ToString(); - context.SSPI = new SSPIHandler(ipAddressString, "POSTGRES"); + context.SSPI = new SSPIHandler(ipAddressString, "POSTGRES", false); ChangeState(context, NpgsqlStartupState.Instance); context.Authenticate(context.SSPI.Continue(null)); break; diff --git a/src/Npgsql/SSPIHandler.cs b/src/Npgsql/SSPIHandler.cs index f61e3efe73..83de35ec27 100644 --- a/src/Npgsql/SSPIHandler.cs +++ b/src/Npgsql/SSPIHandler.cs @@ -118,7 +118,7 @@ ref SecHandle phContext private SecHandle sspictx; private bool sspictx_set; - public SSPIHandler(string pghost, string krbsrvname) + public SSPIHandler(string pghost, string krbsrvname, bool useGssapi) { if (pghost == null) throw new ArgumentNullException("pghost"); @@ -129,7 +129,7 @@ public SSPIHandler(string pghost, string krbsrvname) SecHandle expire; int status = AcquireCredentialsHandle( "", - "negotiate", + useGssapi ? "kerberos" : "negotiate", SECPKG_CRED_OUTBOUND, IntPtr.Zero, IntPtr.Zero,