#At file:///C:/Users/Reggie/work/connector-net/trunk-windows-auth/ based on revid:reggie.burnett@stripped22195247-c5d6si4vwuh6rl81
963 Reggie Burnett 2011-03-31 [merge]
Windows authenitcation plugin in Connector/NET
This patch implements IntegratedSecurity=yes (windows authentication)
for mysql servers that have the corresponding plugin.
added:
MySql.Data/Provider/Source/SSPI.cs
modified:
MySql.Data/Provider/MySql.Data.CF.csproj
MySql.Data/Provider/MySql.Data.csproj
MySql.Data/Provider/Source/Connection.cs
MySql.Data/Provider/Source/Driver.cs
MySql.Data/Provider/Source/MySqlConnectionStringBuilder.cs
MySql.Data/Provider/Source/MySqlPacket.cs
MySql.Data/Provider/Source/MySqlPoolManager.cs
MySql.Data/Provider/Source/MySqlStream.cs
MySql.Data/Provider/Source/MysqlDefs.cs
MySql.Data/Provider/Source/NativeDriver.cs
MySql.Data/Tests/Source/ConnectionTests.cs
=== modified file 'MySql.Data/Provider/MySql.Data.CF.csproj'
=== modified file 'MySql.Data/Provider/MySql.Data.CF.csproj'
--- a/MySql.Data/Provider/MySql.Data.CF.csproj 2010-02-13 04:34:16 +0000
+++ b/MySql.Data/Provider/MySql.Data.CF.csproj 2010-10-14 22:10:08 +0000
@@ -152,6 +152,7 @@
<Compile Include="Source\zlib\ZOutputStream.cs" />
<Compile Include="Source\zlib\ZStream.cs" />
<Compile Include="Source\zlib\ZStreamException.cs" />
+ <Compile Include="SSPI.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
=== modified file 'MySql.Data/Provider/MySql.Data.csproj'
--- a/MySql.Data/Provider/MySql.Data.csproj 2010-12-22 21:21:41 +0000
+++ b/MySql.Data/Provider/MySql.Data.csproj 2011-03-31 17:21:49 +0000
@@ -173,6 +173,7 @@
<Compile Include="Source\MySqlScript.cs" />
<Compile Include="Source\Types\MySqlGuid.cs" />
<Compile Include="Source\ResultSet.cs" />
+ <Compile Include="Source\SSPI.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\ReservedWords.txt" />
=== modified file 'MySql.Data/Provider/Source/Connection.cs'
--- a/MySql.Data/Provider/Source/Connection.cs 2011-02-18 15:26:03 +0000
+++ b/MySql.Data/Provider/Source/Connection.cs 2011-03-31 17:21:49 +0000
@@ -466,6 +466,7 @@
if (driver == null)
driver = pool.GetConnection();
procedureCache = pool.ProcedureCache;
+
}
else
{
=== modified file 'MySql.Data/Provider/Source/Driver.cs'
--- a/MySql.Data/Provider/Source/Driver.cs 2011-02-21 20:49:04 +0000
+++ b/MySql.Data/Provider/Source/Driver.cs 2011-03-31 17:21:49 +0000
@@ -39,7 +39,6 @@
{
protected Encoding encoding;
protected MySqlConnectionStringBuilder connectionString;
- protected ClientFlags serverCaps;
protected bool isOpen;
protected DateTime creationTime;
protected string serverCharSet;
=== modified file 'MySql.Data/Provider/Source/MySqlConnectionStringBuilder.cs'
--- a/MySql.Data/Provider/Source/MySqlConnectionStringBuilder.cs 2011-03-03 18:03:34 +0000
+++ b/MySql.Data/Provider/Source/MySqlConnectionStringBuilder.cs 2011-03-31 17:21:49 +0000
@@ -377,6 +377,27 @@
SetValue("Certificate Thumbprint", value);
}
}
+
+ [Category("Authentication")]
+ [DisplayName("Integrated Security")]
+ [Description("Use windows authentication when connecting to server")]
+ [DefaultValue(false)]
+ public bool IntegratedSecurity
+ {
+ get
+ {
+ object val = values["Integrated Security"];
+ return (bool)val;
+ }
+ set
+ {
+ if (!MySql.Data.Common.Platform.IsWindows())
+ throw new MySqlException("IntegratedSecurity is supported on Windows only");
+
+ SetValue("Integrated Security", value);
+ }
+ }
+
#endregion
#region Other Properties
@@ -887,6 +908,10 @@
string s = value.ToString().ToLower(CultureInfo.InvariantCulture);
if (s == "yes" || s == "true") return true;
if (s == "no" || s == "false") return false;
+
+ // Unclean, but we need IntegratedSecurity=SSPI to be
+ // the same as IntegratedSecurity=true, to match SqlClient
+ if (s == "sspi") return true;
throw new FormatException(String.Format(Resources.InvalidValueForBoolean, value));
}
else
=== modified file 'MySql.Data/Provider/Source/MySqlPacket.cs'
--- a/MySql.Data/Provider/Source/MySqlPacket.cs 2010-11-30 18:33:23 +0000
+++ b/MySql.Data/Provider/Source/MySqlPacket.cs 2011-03-31 17:21:49 +0000
@@ -338,7 +338,7 @@
return encoding.GetString(tempBuffer, 0, (int)length);
}
- public string ReadString()
+ public string ReadString(Encoding enc)
{
byte[] bits = buffer.GetBuffer();
int end = (int)buffer.Position;
@@ -347,12 +347,16 @@
bits[end] != 0 && (int)bits[end] != -1)
end++;
- string s = encoding.GetString(bits,
+ string s = enc.GetString(bits,
(int)buffer.Position, end - (int)buffer.Position);
buffer.Position = end + 1;
return s;
}
+ public string ReadString()
+ {
+ return ReadString(this.encoding);
+ }
#endregion
}
}
=== modified file 'MySql.Data/Provider/Source/MySqlPoolManager.cs'
--- a/MySql.Data/Provider/Source/MySqlPoolManager.cs 2010-08-18 19:48:34 +0000
+++ b/MySql.Data/Provider/Source/MySqlPoolManager.cs 2010-10-14 22:10:08 +0000
@@ -27,6 +27,8 @@
namespace MySql.Data.MySqlClient
{
+
+
/// <summary>
/// Summary description for MySqlPoolManager.
/// </summary>
@@ -43,9 +45,44 @@
private static Timer timer = new Timer(new TimerCallback(CleanIdleConnections),
null, maxConnectionIdleTime*1000, maxConnectionIdleTime*1000);
+ private static string GetKey(MySqlConnectionStringBuilder settings)
+ {
+ string key = settings.ConnectionString;
+#if !CF
+ if(settings.IntegratedSecurity && !settings.ConnectionReset)
+ {
+ try
+ {
+ // Append SID to the connection string to generate a key
+ // With Integrated security different Windows users with the same
+ // connection string may be mapped to different MySQL accounts.
+ System.Security.Principal.WindowsIdentity id =
+ System.Security.Principal.WindowsIdentity.GetCurrent();
+ if (id != null)
+ {
+ key += ";" + id.User;
+ }
+ }
+ catch (System.Security.SecurityException ex)
+ {
+ // Documentation for WindowsIdentity.GetCurrent() states
+ // SecurityException can be thrown. In this case the
+ // connection can only be pooled if reset is done.
+ throw new MySqlException(
+ "Cannot retrieve Windows identity for current user " +
+ "authentication using IntegratedSecurity" +
+ "Connections that use IntegratedSecurity cannot be " +
+ "pooled. Use either 'ConnectionReset=true' or " +
+ "'Pooling=false' in the connection string" +
+ "to fix", ex );
+ }
+ }
+#endif
+ return key;
+ }
public static MySqlPool GetPool(MySqlConnectionStringBuilder settings)
{
- string text = settings.ConnectionString;
+ string text = GetKey(settings);
lock (pools.SyncRoot)
{
@@ -86,8 +123,17 @@
public static void ClearPool(MySqlConnectionStringBuilder settings)
{
Debug.Assert(settings != null);
-
- string text = settings.ConnectionString;
+ string text;
+ try
+ {
+ text = GetKey(settings);
+ }
+ catch (MySqlException)
+ {
+ // Cannot retrieve windows identity for IntegratedSecurity=true
+ // This can be ignored.
+ return;
+ }
ClearPoolByText(text);
}
=== modified file 'MySql.Data/Provider/Source/MySqlStream.cs'
--- a/MySql.Data/Provider/Source/MySqlStream.cs 2010-08-18 19:48:34 +0000
+++ b/MySql.Data/Provider/Source/MySqlStream.cs 2010-10-14 22:10:08 +0000
@@ -44,7 +44,13 @@
Stream inStream;
Stream outStream;
-
+ internal Stream BaseStream
+ {
+ get
+ {
+ return timedStream;
+ }
+ }
public MySqlStream(Encoding encoding)
{
// we have no idea what the real value is so we start off with the max value
=== modified file 'MySql.Data/Provider/Source/MysqlDefs.cs'
--- a/MySql.Data/Provider/Source/MysqlDefs.cs 2010-08-18 19:48:34 +0000
+++ b/MySql.Data/Provider/Source/MysqlDefs.cs 2010-10-14 22:10:08 +0000
@@ -48,7 +48,10 @@
SECURE_CONNECTION = 32768, // new 4.1 authentication
MULTI_STATEMENTS = 65536, // Allow multi-stmt support
MULTI_RESULTS = 131072, // Allow multiple resultsets
- PS_MULTI_RESULTS = 1UL << 18 // allow multi results using PS protocol
+ PS_MULTI_RESULTS = 1UL << 18, // allow multi results using PS protocol
+ PLUGIN_AUTH = (1UL << 19), //Client supports plugin authentication
+ CLIENT_SSL_VERIFY_SERVER_CERT= (1UL << 30),
+ CLIENT_REMEMBER_OPTIONS= (1UL << 31)
}
[Flags]
=== modified file 'MySql.Data/Provider/Source/NativeDriver.cs'
--- a/MySql.Data/Provider/Source/NativeDriver.cs 2011-02-15 21:07:24 +0000
+++ b/MySql.Data/Provider/Source/NativeDriver.cs 2011-03-31 17:21:49 +0000
@@ -33,7 +33,6 @@
using System.Net.Security;
using System.Security.Authentication;
using System.Globalization;
-using System.Text;
#endif
namespace MySql.Data.MySqlClient
@@ -55,6 +54,13 @@
private Driver owner;
private int warnings;
+ // Windows authentication method string, used by the protocol.
+ // Also known as "client plugin name".
+ const string AuthenticationWindowsPlugin = "authentication_windows_client";
+
+ // Predefined username for IntegratedSecurity
+ const string AuthenticationWindowsUser = "auth_windows";
+
public NativeDriver(Driver owner)
{
this.owner = owner;
@@ -115,7 +121,10 @@
packet = stream.ReadPacket();
byte marker = (byte) packet.ReadByte();
if (marker != 0)
+ {
+ string s = packet.ReadString();
throw new MySqlException("Out of sync with server", true, null);
+ }
packet.ReadFieldLength(); /* affected rows */
packet.ReadFieldLength(); /* last insert id */
@@ -219,10 +228,22 @@
owner.ConnectionCharSetIndex = (int)packet.ReadByte();
serverStatus = (ServerStatusFlags) packet.ReadInteger(2);
- packet.Position += 13;
+ uint serverCapsHigh = (uint)packet.ReadInteger(2);
+ serverCaps |= (ClientFlags)(serverCapsHigh << 16);
+
+
+ int scrambleLength = (int)packet.ReadByte();
+
+ packet.Position += 10;
string seedPart2 = packet.ReadString();
encryptionSeed += seedPart2;
+ string authenticationMethod = "";
+ if ((serverCaps & ClientFlags.PLUGIN_AUTH)!=0)
+ {
+ authenticationMethod = packet.ReadString();
+ }
+
// based on our settings, set our connection flags
SetConnectionFlags(serverCaps);
@@ -254,7 +275,7 @@
packet.WriteByte(8);
packet.Write(new byte[23]);
- Authenticate();
+ Authenticate(false);
// if we are using compression, then we use our CompressedStream class
// to hide the ugliness of managing the compression
@@ -416,16 +437,73 @@
flags |= ClientFlags.SSL;
// if the server supports output parameters, then we do too
- //if ((serverCaps & ClientFlags.PS_MULTI_RESULTS) != 0)
+ if ((serverCaps & ClientFlags.PS_MULTI_RESULTS) != 0)
flags |= ClientFlags.PS_MULTI_RESULTS;
+ if(Settings.IntegratedSecurity)
+ {
+ if ((serverCaps & ClientFlags.PLUGIN_AUTH) != 0)
+ flags |= ClientFlags.PLUGIN_AUTH;
+ }
connectionFlags = flags;
}
+
+ private void AuthenticateSSPI()
+ {
+
+ string targetName = ""; // target name (required by Kerberos)
+
+ // First packet sent by server should include target name (for
+ // Kerberos) as UTF8 string. It might however be prepended by junk
+ // at the start of the string (0xfe"authentication_win_client"\0,
+ // see Bug#57442), this junk will be ignored. Target name can also
+ // be an empty string if server is not running in a domain environment,
+ // in this case authentication will fallback to NTLM.
+
+ // Note that 0xfe byte at the start could also indicate that windows
+ // authentication is not supported by sΘrver, we throw an exception
+ // if this happens.
+
+ packet = stream.ReadPacket();
+ byte b = packet.ReadByte();
+ if (b == 0xfe)
+ {
+ string authMethod = packet.ReadString();
+ if (authMethod.Equals(AuthenticationWindowsPlugin))
+ {
+ targetName = packet.ReadString(Encoding.UTF8);
+ }
+ else
+ {
+ // User has requested Windows authentication, bail out.
+ throw new MySqlException("unexpected authentication method " +
+ authMethod);
+ }
+ }
+ else
+ {
+ targetName = Encoding.UTF8.GetString(packet.Buffer, 0, packet.Buffer.Length);
+ }
+
+ // Do SSPI authentication handshake
+ SSPI sspi = new SSPI(targetName, stream.BaseStream);
+ sspi.AuthenticateClient();
+
+ // read ok packet.
+ packet = stream.ReadPacket();
+ ReadOk(false);
+ }
+
/// <summary>
/// Perform an authentication against a 4.1.1 server
+ /// <param name="reset">
+ /// True, if this function is called as part of CHANGE_USER request
+ /// (connection reset)
+ /// False, for first-time logon
+ /// </param>
/// </summary>
- private void AuthenticateNew()
+ private void AuthenticateNew(bool reset)
{
if ((connectionFlags & ClientFlags.SECURE_CONNECTION) == 0)
AuthenticateOld();
@@ -436,7 +514,24 @@
else
packet.WriteString(""); // Add a null termination to the string.
- stream.SendPacket(packet);
+
+ if (Settings.IntegratedSecurity)
+ {
+ // Append authentication method after the database name in the
+ // handshake authentication packet. Omit it, if we do connection
+ // reset (reset should use the same authentication method)
+ if (!reset)
+ {
+ packet.WriteString(AuthenticationWindowsPlugin);
+ }
+ stream.SendPacket(packet);
+ AuthenticateSSPI();
+ return;
+ }
+ else
+ {
+ stream.SendPacket(packet);
+ }
// this result means the server wants us to send the password using
// old encryption
@@ -449,8 +544,7 @@
stream.SendPacket(packet);
ReadOk(true);
}
- else
- ReadOk(false);
+ ReadOk(false);
}
private void AuthenticateOld()
@@ -464,11 +558,19 @@
ReadOk(true);
}
- public void Authenticate()
+
+ public void Authenticate(bool reset)
{
- // write the user id to the auth packet
- packet.WriteString(Settings.UserID);
- AuthenticateNew();
+ if (Settings.IntegratedSecurity)
+ {
+ packet.WriteString(AuthenticationWindowsUser);
+ }
+ else
+ {
+ // write the user id to the auth packet
+ packet.WriteString(Settings.UserID);
+ }
+ AuthenticateNew(reset);
}
#endregion
@@ -480,7 +582,7 @@
stream.SequenceByte = 0;
packet.Clear();
packet.WriteByte((byte)DBCmd.CHANGE_USER);
- Authenticate();
+ Authenticate(true);
}
/// <summary>
=== added file 'MySql.Data/Provider/Source/SSPI.cs'
--- a/MySql.Data/Provider/Source/SSPI.cs 1970-01-01 00:00:00 +0000
+++ b/MySql.Data/Provider/Source/SSPI.cs 2010-10-14 22:10:08 +0000
@@ -0,0 +1,425 @@
+// Copyright (c) 2010 Oracle and its affiliates.
+//
+// MySQL Connector/NET is licensed under the terms of the GPLv2
+// <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
+// MySQL Connectors. There are special exceptions to the terms and
+// conditions of the GPLv2 as it is applied to this software, see the
+// FLOSS License Exception
+// <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+using System.Collections;
+using System.Security.Principal;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Net.Sockets;
+
+using HANDLE = System.IntPtr;
+using System;
+using System.IO;
+
+
+namespace MySql.Data.MySqlClient
+{
+ internal class SSPI
+ {
+ const int SEC_E_OK = 0;
+ const int SEC_I_CONTINUE_NEEDED = 0x90312;
+ const int SEC_I_COMPLETE_NEEDED = 0x1013;
+ const int SEC_I_COMPLETE_AND_CONTINUE = 0x1014;
+
+ const int SECPKG_CRED_OUTBOUND = 2;
+ const int SECURITY_NETWORK_DREP = 0;
+ const int SECURITY_NATIVE_DREP = 0x10;
+ const int SECPKG_CRED_INBOUND = 1;
+ const int MAX_TOKEN_SIZE = 12288;
+ const int SECPKG_ATTR_SIZES = 0;
+ const int STANDARD_CONTEXT_ATTRIBUTES = 0;
+
+ SECURITY_HANDLE outboundCredentials = new SECURITY_HANDLE(0);
+ SECURITY_HANDLE clientContext = new SECURITY_HANDLE(0);
+ Stream stream;
+ String targetName;
+ byte[] packetHeader;
+ int seq = 3;
+
+
+ [DllImport("secur32", CharSet = CharSet.Auto)]
+ static extern int AcquireCredentialsHandle(
+ string pszPrincipal,
+ string pszPackage,
+ int fCredentialUse,
+ IntPtr PAuthenticationID,
+ IntPtr pAuthData,
+ int pGetKeyFn,
+ IntPtr pvGetKeyArgument,
+ ref SECURITY_HANDLE phCredential,
+ ref SECURITY_INTEGER ptsExpiry);
+
+ [DllImport("secur32", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern int InitializeSecurityContext(
+ ref SECURITY_HANDLE phCredential,
+ IntPtr phContext,
+ string pszTargetName,
+ int fContextReq,
+ int Reserved1,
+ int TargetDataRep,
+ IntPtr pInput,
+ int Reserved2,
+ out SECURITY_HANDLE phNewContext,
+ out SecBufferDesc pOutput,
+ out uint pfContextAttr,
+ out SECURITY_INTEGER ptsExpiry);
+
+ [DllImport("secur32", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern int InitializeSecurityContext(
+ ref SECURITY_HANDLE phCredential,
+ ref SECURITY_HANDLE phContext,
+ string pszTargetName,
+ int fContextReq,
+ int Reserved1,
+ int TargetDataRep,
+ ref SecBufferDesc SecBufferDesc,
+ int Reserved2,
+ out SECURITY_HANDLE phNewContext,
+ out SecBufferDesc pOutput,
+ out uint pfContextAttr,
+ out SECURITY_INTEGER ptsExpiry);
+
+ [DllImport("secur32", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern int CompleteAuthToken(
+ ref SECURITY_HANDLE phContext,
+ ref SecBufferDesc pToken );
+
+ [DllImport("secur32.Dll", CharSet = CharSet.Auto, SetLastError = false)]
+ public static extern int QueryContextAttributes(
+ ref SECURITY_HANDLE phContext,
+ uint ulAttribute,
+ out SecPkgContext_Sizes pContextAttributes);
+
+ [DllImport("secur32.Dll", CharSet = CharSet.Auto, SetLastError = false)]
+ public static extern int FreeCredentialsHandle(ref SECURITY_HANDLE pCred);
+
+ [DllImport("secur32.Dll", CharSet = CharSet.Auto, SetLastError = false)]
+ public static extern int DeleteSecurityContext(ref SECURITY_HANDLE pCred);
+
+ public SSPI(string targetName, Stream stream)
+ {
+ this.targetName = null;
+ this.stream = stream;
+ packetHeader = new byte[4];
+
+ }
+
+
+ // Read MySQL packet
+ // since SSPI blobs data cannot be larger than ~12K,
+ // handling just single packet is sufficient
+ private byte[] ReadData()
+ {
+ byte[] buffer;
+ MySqlStream.ReadFully(stream, packetHeader, 0, 4);
+ int length = (int)(packetHeader[0] + (packetHeader[1] << 8) +
+ (packetHeader[2] << 16));
+ seq = packetHeader[3]+1;
+ buffer = new byte[length];
+ MySqlStream.ReadFully(stream, buffer, 0, length);
+
+ return buffer;
+ }
+
+ // Write MySQL packet
+ private void WriteData(byte[] buffer)
+ {
+ int count = buffer.Length;
+
+ packetHeader[0] = (byte)(count & 0xff);
+ packetHeader[1] = (byte)((count >> 8) & 0xff);
+ packetHeader[2] = (byte)((count >> 16) & 0xff);
+ packetHeader[3] = (byte)(seq);
+ stream.Write(packetHeader, 0, 4);
+ stream.Write(buffer, 0, count);
+ stream.Flush();
+ }
+
+ public void AuthenticateClient()
+ {
+ bool continueProcessing = true;
+ byte[] clientBlob = null;
+ byte[] serverBlob = null;
+ SECURITY_INTEGER lifetime = new SECURITY_INTEGER(0);
+ int ss;
+
+ ss = AcquireCredentialsHandle(null, "Negotiate", SECPKG_CRED_OUTBOUND,
+ IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, ref outboundCredentials,
+ ref lifetime);
+ if(ss != SEC_E_OK)
+ {
+ throw new MySqlException(
+ "AcquireCredentialsHandle failed with errorcode" + ss);
+ }
+ try
+ {
+ while (continueProcessing)
+ {
+ InitializeClient(out clientBlob, serverBlob,
+ out continueProcessing);
+ if (clientBlob != null && clientBlob.Length > 0)
+ {
+ WriteData(clientBlob);
+ if (continueProcessing)
+ serverBlob = ReadData();
+ }
+ }
+ }
+ finally
+ {
+ FreeCredentialsHandle(ref outboundCredentials);
+ DeleteSecurityContext(ref clientContext);
+ }
+ }
+
+
+ void InitializeClient(out byte[] clientBlob, byte[] serverBlob,
+ out bool continueProcessing)
+ {
+ clientBlob = null;
+ continueProcessing = true;
+ SecBufferDesc clientBufferDesc = new SecBufferDesc(MAX_TOKEN_SIZE);
+ SECURITY_INTEGER lifetime = new SECURITY_INTEGER(0);
+ int ss = -1;
+ try
+ {
+ uint ContextAttributes = 0;
+
+ if (serverBlob == null)
+ {
+ ss = InitializeSecurityContext(
+ ref outboundCredentials,
+ IntPtr.Zero,
+ targetName,
+ STANDARD_CONTEXT_ATTRIBUTES,
+ 0,
+ SECURITY_NETWORK_DREP,
+ IntPtr.Zero, /* always zero first time around */
+ 0,
+ out clientContext,
+ out clientBufferDesc,
+ out ContextAttributes,
+ out lifetime);
+
+ }
+ else
+ {
+ String s = System.Text.Encoding.UTF8.GetString(serverBlob);
+ SecBufferDesc serverBufferDesc = new SecBufferDesc(serverBlob);
+
+ try
+ {
+ ss = InitializeSecurityContext(ref outboundCredentials,
+ ref clientContext,
+ targetName,
+ STANDARD_CONTEXT_ATTRIBUTES,
+ 0,
+ SECURITY_NETWORK_DREP,
+ ref serverBufferDesc,
+ 0,
+ out clientContext,
+ out clientBufferDesc,
+ out ContextAttributes,
+ out lifetime);
+ }
+ finally
+ {
+ serverBufferDesc.Dispose();
+ }
+ }
+
+
+ if ((SEC_I_COMPLETE_NEEDED == ss)
+ || (SEC_I_COMPLETE_AND_CONTINUE == ss))
+ {
+ CompleteAuthToken(ref clientContext, ref clientBufferDesc);
+ }
+
+ if (ss != SEC_E_OK &&
+ ss != SEC_I_CONTINUE_NEEDED &&
+ ss != SEC_I_COMPLETE_NEEDED &&
+ ss != SEC_I_COMPLETE_AND_CONTINUE)
+ {
+ throw new MySqlException(
+ "InitializeSecurityContext() failed with errorcode "+ss);
+ }
+
+ clientBlob = clientBufferDesc.GetSecBufferByteArray();
+ }
+ finally
+ {
+ clientBufferDesc.Dispose();
+ }
+ continueProcessing = (ss != SEC_E_OK && ss != SEC_I_COMPLETE_NEEDED);
+ }
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct SecBufferDesc : IDisposable
+ {
+
+ public int ulVersion;
+ public int cBuffers;
+ public IntPtr pBuffers; //Point to SecBuffer
+
+ public SecBufferDesc(int bufferSize)
+ {
+ ulVersion = (int)SecBufferType.SECBUFFER_VERSION;
+ cBuffers = 1;
+ SecBuffer secBuffer = new SecBuffer(bufferSize);
+ pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(secBuffer));
+ Marshal.StructureToPtr(secBuffer, pBuffers, false);
+ }
+
+ public SecBufferDesc(byte[] secBufferBytes)
+ {
+ ulVersion = (int)SecBufferType.SECBUFFER_VERSION;
+ cBuffers = 1;
+ SecBuffer ThisSecBuffer = new SecBuffer(secBufferBytes);
+ pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer));
+ Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false);
+ }
+
+ public void Dispose()
+ {
+ if (pBuffers != IntPtr.Zero)
+ {
+ Debug.Assert(cBuffers == 1);
+ SecBuffer ThisSecBuffer =
+ (SecBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecBuffer));
+ ThisSecBuffer.Dispose();
+ Marshal.FreeHGlobal(pBuffers);
+ pBuffers = IntPtr.Zero;
+ }
+ }
+
+ public byte[] GetSecBufferByteArray()
+ {
+ byte[] Buffer = null;
+
+ if (pBuffers == IntPtr.Zero)
+ {
+ throw new InvalidOperationException("Object has already been disposed!!!");
+ }
+ Debug.Assert(cBuffers == 1);
+ SecBuffer secBuffer = (SecBuffer)Marshal.PtrToStructure(pBuffers,
+ typeof(SecBuffer));
+ if (secBuffer.cbBuffer > 0)
+ {
+ Buffer = new byte[secBuffer.cbBuffer];
+ Marshal.Copy(secBuffer.pvBuffer, Buffer, 0, secBuffer.cbBuffer);
+ }
+ return (Buffer);
+ }
+
+ }
+
+ public enum SecBufferType
+ {
+ SECBUFFER_VERSION = 0,
+ SECBUFFER_EMPTY = 0,
+ SECBUFFER_DATA = 1,
+ SECBUFFER_TOKEN = 2
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SecHandle //=PCtxtHandle
+ {
+ IntPtr dwLower; // ULONG_PTR translates to IntPtr not to uint
+ IntPtr dwUpper; // this is crucial for 64-Bit Platforms
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SecBuffer : IDisposable
+ {
+ public int cbBuffer;
+ public int BufferType;
+ public IntPtr pvBuffer;
+
+
+ public SecBuffer(int bufferSize)
+ {
+ cbBuffer = bufferSize;
+ BufferType = (int)SecBufferType.SECBUFFER_TOKEN;
+ pvBuffer = Marshal.AllocHGlobal(bufferSize);
+ }
+
+ public SecBuffer(byte[] secBufferBytes)
+ {
+ cbBuffer = secBufferBytes.Length;
+ BufferType = (int)SecBufferType.SECBUFFER_TOKEN;
+ pvBuffer = Marshal.AllocHGlobal(cbBuffer);
+ Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer);
+ }
+
+ public SecBuffer(byte[] secBufferBytes, SecBufferType bufferType)
+ {
+ cbBuffer = secBufferBytes.Length;
+ BufferType = (int)bufferType;
+ pvBuffer = Marshal.AllocHGlobal(cbBuffer);
+ Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer);
+ }
+
+ public void Dispose()
+ {
+ if (pvBuffer != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(pvBuffer);
+ pvBuffer = IntPtr.Zero;
+ }
+ }
+ }
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SECURITY_INTEGER
+ {
+ public uint LowPart;
+ public int HighPart;
+ public SECURITY_INTEGER(int dummy)
+ {
+ LowPart = 0;
+ HighPart = 0;
+ }
+ };
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SECURITY_HANDLE
+ {
+ public uint LowPart;
+ public uint HighPart;
+ public SECURITY_HANDLE(int dummy)
+ {
+ LowPart = HighPart = 0;
+ }
+ };
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SecPkgContext_Sizes
+ {
+ public uint cbMaxToken;
+ public uint cbMaxSignature;
+ public uint cbBlockSize;
+ public uint cbSecurityTrailer;
+ };
+
+}
+
=== modified file 'MySql.Data/Tests/Source/ConnectionTests.cs'
--- a/MySql.Data/Tests/Source/ConnectionTests.cs 2010-08-18 19:52:04 +0000
+++ b/MySql.Data/Tests/Source/ConnectionTests.cs 2010-10-14 22:10:08 +0000
@@ -69,6 +69,120 @@
Assert.AreEqual(System.Data.ConnectionState.Closed, c.State, "State");
}
+#if !CF //No Security.Principal on CF
+
+ [Test]
+ public void TestIntegratedSecurity()
+ {
+
+
+ const string PluginName = "authentication_windows";
+
+
+ // Check if server has windows authentication plugin is installed
+ MySqlCommand cmd = new MySqlCommand("show plugins", rootConn);
+ bool haveWindowsAuthentication = false;
+ using (MySqlDataReader r = cmd.ExecuteReader())
+ {
+ while (r.Read())
+ {
+ string name = (string)r["Name"];
+ if (name == PluginName)
+ {
+ haveWindowsAuthentication = true;
+ break;
+ }
+ }
+ }
+ if(!haveWindowsAuthentication)
+ return;
+
+ bool haveAuthWindowsUser = false;
+ string pluginName = null;
+ string authenticationString = "";
+
+ // Check if predefined proxy user exists
+ cmd.CommandText =
+ "select plugin,authentication_string from mysql.user where user='auth_windows'";
+ using (MySqlDataReader r = cmd.ExecuteReader())
+ {
+ if (r.Read())
+ {
+ haveAuthWindowsUser = true;
+ pluginName = (string) r["plugin"];
+ authenticationString = (string) r["authentication_string"];
+ }
+ }
+
+ // Create mapping for current Windows user=>foo_user
+ String windowsUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
+ windowsUser = windowsUser.Replace("\\", "\\\\");
+ string userMapping = windowsUser+"=foo_user";
+
+ try
+ {
+ if (!haveAuthWindowsUser)
+ {
+ suExecSQL(
+ "CREATE USER auth_windows IDENTIFIED WITH " + PluginName +" as '" +
+ userMapping + "'");
+ }
+ else
+ {
+ // extend mapping string for current user
+ suExecSQL(
+ "UPDATE mysql.user SET authentication_string='" + userMapping +
+ "," + authenticationString + "'");
+ }
+ suExecSQL("create user foo_user identified by 'pass'");
+ suExecSQL("grant all privileges on *.* to 'foo_user'@'%'");
+ suExecSQL("grant proxy on foo_user to auth_windows");
+
+
+ // Finally, use IntegratedSecurity=true for the newly created user
+ string connStr = GetConnectionString(true) + ";Integrated Security=SSPI";
+ using (MySqlConnection c = new MySqlConnection(connStr))
+ {
+ c.Open();
+
+ MySqlCommand command = new MySqlCommand("SELECT 1", c);
+ long ret = (long)command.ExecuteScalar();
+ Assert.AreEqual(ret, 1);
+
+
+ command.CommandText = "select user()";
+ string user = (string)command.ExecuteScalar();
+ // Check if proxy user is correct
+ Assert.IsTrue(user.StartsWith("auth_windows@"));
+
+ // check if mysql user is correct
+ // (foo_user is mapped to current OS user)
+ command.CommandText = "select current_user()";
+ string currentUser = (string)command.ExecuteScalar();
+ Assert.IsTrue(currentUser.StartsWith("foo_user@"));
+ }
+ }
+ finally
+ {
+ // Cleanup
+
+ // Drop test user
+ suExecSQL("drop user foo_user");
+ if (!haveAuthWindowsUser)
+ {
+ // drop proxy user if we created it
+ suExecSQL("drop user auth_windows");
+ }
+ else
+ {
+ // revert changes in the mapping string
+ suExecSQL("UPDATE mysql.user SET authentication_string='" +
+ authenticationString + "'");
+ }
+ }
+ }
+#endif
+
[Test]
public void TestConnectingSocketBadUserName()
{
No bundle (reason: revision is a merge (you can force generation of a bundle with env var BZR_FORCE_BUNDLE=1)).| Thread |
|---|
| • bzr commit into connector-net-trunk branch (reggie.burnett:963) | Reggie Burnett | 31 Mar |