//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] // [....] //------------------------------------------------------------------------------ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("System.Data.DataSetExtensions, PublicKey="+AssemblyRef.EcmaPublicKeyFull)] // DevDiv Bugs 92166 namespace System.Data.SqlClient { using System; using System.Collections; using System.Configuration.Assemblies; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Data.ProviderBase; using System.Data.Sql; using System.Data.SqlTypes; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Runtime.Remoting; using System.Runtime.Serialization.Formatters; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Security; using System.Security.Permissions; using System.Reflection; using System.Runtime.Versioning; using Microsoft.SqlServer.Server; using System.Security.Principal; using System.Diagnostics.CodeAnalysis; [DefaultEvent("InfoMessage")] public sealed partial class SqlConnection: DbConnection, ICloneable { static private readonly object EventInfoMessage = new object(); private SqlDebugContext _sdc; // SQL Debugging support private bool _AsyncCommandInProgress; // SQLStatistics support internal SqlStatistics _statistics; private bool _collectstats; private bool _fireInfoMessageEventOnUserErrors; // False by default // root task associated with current async invocation Tuple, Task> _currentCompletion; private SqlCredential _credential; // SQL authentication password stored in SecureString private string _connectionString; private int _connectRetryCount; // connection resiliency private object _reconnectLock = new object(); internal Task _currentReconnectionTask; private Task _asyncWaitingForReconnection; // current async task waiting for reconnection in non-MARS connections private Guid _originalConnectionId = Guid.Empty; private CancellationTokenSource _reconnectionCancellationSource; internal SessionData _recoverySessionData; internal WindowsIdentity _lastIdentity; internal WindowsIdentity _impersonateIdentity; private int _reconnectCount; public SqlConnection(string connectionString) : this(connectionString, null) { } public SqlConnection(string connectionString, SqlCredential credential) : this() { ConnectionString = connectionString; // setting connection string first so that ConnectionOption is available if (credential != null) { // The following checks are necessary as setting Credential property will call CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential it will throw InvalidOperationException rather than Arguemtn exception // Need to call setter on Credential property rather than setting _credential directly as pool groups need to be checked SqlConnectionString connectionOptions = (SqlConnectionString) ConnectionOptions; if (UsesClearUserIdOrPassword(connectionOptions)) { throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); } if (UsesIntegratedSecurity(connectionOptions)) { throw ADP.InvalidMixedArgumentOfSecureCredentialAndIntegratedSecurity(); } if (UsesContextConnection(connectionOptions)) { throw ADP.InvalidMixedArgumentOfSecureCredentialAndContextConnection(); } Credential = credential; } // else // credential == null: we should not set "Credential" as this will do additional validation check and // checking pool groups which is not necessary. All necessary operation is already done by calling "ConnectionString = connectionString" CacheConnectionStringProperties(); } private SqlConnection(SqlConnection connection) { // Clone GC.SuppressFinalize(this); CopyFrom(connection); _connectionString = connection._connectionString; if (connection._credential != null) { SecureString password = connection._credential.Password.Copy(); password.MakeReadOnly(); _credential = new SqlCredential(connection._credential.UserId, password); } CacheConnectionStringProperties(); } // This method will be called once connection string is set or changed. private void CacheConnectionStringProperties() { SqlConnectionString connString = ConnectionOptions as SqlConnectionString; if (connString != null) { _connectRetryCount = connString.ConnectRetryCount; } } // // PUBLIC PROPERTIES // // used to start/stop collection of statistics data and do verify the current state // // devnote: start/stop should not performed using a property since it requires execution of code // // start statistics // set the internal flag (_statisticsEnabled) to true. // Create a new SqlStatistics object if not already there. // connect the parser to the object. // if there is no parser at this time we need to connect it after creation. // [ DefaultValue(false), ResCategoryAttribute(Res.DataCategory_Data), ResDescriptionAttribute(Res.SqlConnection_StatisticsEnabled), ] public bool StatisticsEnabled { get { return (_collectstats); } set { if (IsContextConnection) { if (value) { throw SQL.NotAvailableOnContextConnection(); } } else { if (value) { // start if (ConnectionState.Open == State) { if (null == _statistics) { _statistics = new SqlStatistics(); ADP.TimerCurrent(out _statistics._openTimestamp); } // set statistics on the parser // update timestamp; Debug.Assert(Parser != null, "Where's the parser?"); Parser.Statistics = _statistics; } } else { // stop if (null != _statistics) { if (ConnectionState.Open == State) { // remove statistics from parser // update timestamp; TdsParser parser = Parser; Debug.Assert(parser != null, "Where's the parser?"); parser.Statistics = null; ADP.TimerCurrent(out _statistics._closeTimestamp); } } } this._collectstats = value; } } } internal bool AsyncCommandInProgress { get { return (_AsyncCommandInProgress); } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] set { _AsyncCommandInProgress = value; } } internal bool IsContextConnection { get { SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; return UsesContextConnection(opt); } } // Is this connection is a Context Connection? private bool UsesContextConnection(SqlConnectionString opt) { return opt != null ? opt.ContextConnection : false; } // Does this connection uses Integrated Security? private bool UsesIntegratedSecurity(SqlConnectionString opt) { return opt != null ? opt.IntegratedSecurity : false; } // Does this connection uses old style of clear userID or Password in connection string? private bool UsesClearUserIdOrPassword(SqlConnectionString opt) { bool result = false; if (null != opt) { result = (!ADP.IsEmpty(opt.UserID) || !ADP.IsEmpty(opt.Password)); } return result; } internal SqlConnectionString.TransactionBindingEnum TransactionBinding { get { return ((SqlConnectionString)ConnectionOptions).TransactionBinding; } } internal SqlConnectionString.TypeSystem TypeSystem { get { return ((SqlConnectionString)ConnectionOptions).TypeSystemVersion; } } internal Version TypeSystemAssemblyVersion { get { return ((SqlConnectionString)ConnectionOptions).TypeSystemAssemblyVersion; } } internal int ConnectRetryInterval { get { return ((SqlConnectionString)ConnectionOptions).ConnectRetryInterval; } } override protected DbProviderFactory DbProviderFactory { get { return SqlClientFactory.Instance; } } [ DefaultValue(""), #pragma warning disable 618 // ignore obsolete warning about RecommendedAsConfigurable to use SettingsBindableAttribute RecommendedAsConfigurable(true), #pragma warning restore 618 SettingsBindableAttribute(true), RefreshProperties(RefreshProperties.All), ResCategoryAttribute(Res.DataCategory_Data), Editor("Microsoft.VSDesigner.Data.SQL.Design.SqlConnectionStringEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing), ResDescriptionAttribute(Res.SqlConnection_ConnectionString), ] override public string ConnectionString { get { return ConnectionString_Get(); } set { if (_credential != null) { SqlConnectionString connectionOptions = new SqlConnectionString(value); CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); } ConnectionString_Set(new SqlConnectionPoolKey(value, _credential)); _connectionString = value; // Change _connectionString value only after value is validated CacheConnectionStringProperties(); } } [ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResDescriptionAttribute(Res.SqlConnection_ConnectionTimeout), ] override public int ConnectionTimeout { get { SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; return ((null != constr) ? constr.ConnectTimeout : SqlConnectionString.DEFAULT.Connect_Timeout); } } [ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResDescriptionAttribute(Res.SqlConnection_Database), ] override public string Database { // if the connection is open, we need to ask the inner connection what it's // current catalog is because it may have gotten changed, otherwise we can // just return what the connection string had. get { SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection); string result; if (null != innerConnection) { result = innerConnection.CurrentDatabase; } else { SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; result = ((null != constr) ? constr.InitialCatalog : SqlConnectionString.DEFAULT.Initial_Catalog); } return result; } } [ Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResDescriptionAttribute(Res.SqlConnection_DataSource), ] override public string DataSource { get { SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection); string result; if (null != innerConnection) { result = innerConnection.CurrentDataSource; } else { SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; result = ((null != constr) ? constr.DataSource : SqlConnectionString.DEFAULT.Data_Source); } return result; } } [ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResCategoryAttribute(Res.DataCategory_Data), ResDescriptionAttribute(Res.SqlConnection_PacketSize), ] public int PacketSize { // if the connection is open, we need to ask the inner connection what it's // current packet size is because it may have gotten changed, otherwise we // can just return what the connection string had. get { if (IsContextConnection) { throw SQL.NotAvailableOnContextConnection(); } SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); int result; if (null != innerConnection) { result = innerConnection.PacketSize; } else { SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; result = ((null != constr) ? constr.PacketSize : SqlConnectionString.DEFAULT.Packet_Size); } return result; } } [ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResCategoryAttribute(Res.DataCategory_Data), ResDescriptionAttribute(Res.SqlConnection_ClientConnectionId), ] public Guid ClientConnectionId { get { SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); if (null != innerConnection) { return innerConnection.ClientConnectionId; } else { Task reconnectTask = _currentReconnectionTask; if (reconnectTask != null && !reconnectTask.IsCompleted) { return _originalConnectionId; } return Guid.Empty; } } } [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResDescriptionAttribute(Res.SqlConnection_ServerVersion), ] override public string ServerVersion { get { return GetOpenConnection().ServerVersion; } } [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResDescriptionAttribute(Res.DbConnection_State), ] override public ConnectionState State { get { Task reconnectTask=_currentReconnectionTask; if (reconnectTask != null && !reconnectTask.IsCompleted) { return ConnectionState.Open; } return InnerConnection.State; } } internal SqlStatistics Statistics { get { return _statistics; } } [ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResCategoryAttribute(Res.DataCategory_Data), ResDescriptionAttribute(Res.SqlConnection_WorkstationId), ] public string WorkstationId { get { if (IsContextConnection) { throw SQL.NotAvailableOnContextConnection(); } // If not supplied by the user, the default value is the MachineName // Note: In Longhorn you'll be able to rename a machine without // rebooting. Therefore, don't cache this machine name. SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; string result = ((null != constr) ? constr.WorkstationId : null); if (null == result) { // getting machine name requires Environment.Permission // user must have that permission in order to retrieve this result = Environment.MachineName; } return result; } } // SqlCredential: Pair User Id and password in SecureString which are to be used for SQL authentication [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResDescriptionAttribute(Res.SqlConnection_Credential), ] public SqlCredential Credential { get { SqlCredential result = _credential; // When a connection is connecting or is ever opened, make credential available only if "Persist Security Info" is set to true // otherwise, return null SqlConnectionString connectionOptions = (SqlConnectionString) UserConnectionOptions; if (InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo) { result = null; } return result; } set { // If a connection is connecting or is ever opened, user id/password cannot be set if (!InnerConnection.AllowSetConnectionString) { throw ADP.OpenConnectionPropertySet("Credential", InnerConnection.State); } // check if the usage of credential has any conflict with the keys used in connection string if (value != null) { CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential((SqlConnectionString) ConnectionOptions); } _credential = value; // Need to call ConnectionString_Set to do proper pool group check ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential)); } } // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential: check if the usage of credential has any conflict // with the keys used in connection string // If there is any conflict, it throws InvalidOperationException // This is to be used setter of ConnectionString and Credential properties private void CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(SqlConnectionString connectionOptions) { if (UsesClearUserIdOrPassword(connectionOptions)) { throw ADP.InvalidMixedUsageOfSecureAndClearCredential(); } if (UsesIntegratedSecurity(connectionOptions)) { throw ADP.InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity(); } if (UsesContextConnection(connectionOptions)) { throw ADP.InvalidMixedArgumentOfSecureCredentialAndContextConnection(); } } // // PUBLIC EVENTS // [ ResCategoryAttribute(Res.DataCategory_InfoMessage), ResDescriptionAttribute(Res.DbConnection_InfoMessage), ] public event SqlInfoMessageEventHandler InfoMessage { add { Events.AddHandler(EventInfoMessage, value); } remove { Events.RemoveHandler(EventInfoMessage, value); } } public bool FireInfoMessageEventOnUserErrors { get { return _fireInfoMessageEventOnUserErrors; } set { _fireInfoMessageEventOnUserErrors = value; } } // Approx. number of times that the internal connection has been reconnected internal int ReconnectCount { get { return _reconnectCount; } } // // PUBLIC METHODS // new public SqlTransaction BeginTransaction() { // this is just a delegate. The actual method tracks executiontime return BeginTransaction(IsolationLevel.Unspecified, null); } new public SqlTransaction BeginTransaction(IsolationLevel iso) { // this is just a delegate. The actual method tracks executiontime return BeginTransaction(iso, null); } public SqlTransaction BeginTransaction(string transactionName) { // Use transaction names only on the outermost pair of nested // BEGIN...COMMIT or BEGIN...ROLLBACK statements. Transaction names // are ignored for nested BEGIN's. The only way to rollback a nested // transaction is to have a save point from a SAVE TRANSACTION call. return BeginTransaction(IsolationLevel.Unspecified, transactionName); } // suppress this message - we cannot use SafeHandle here. Also, see notes in the code (VSTFDEVDIV# 560355) [SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")] override protected DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#, isolationLevel=%d{ds.IsolationLevel}", ObjectID, (int)isolationLevel); try { DbTransaction transaction = BeginTransaction(isolationLevel); // VSTFDEVDIV# 560355 - InnerConnection doesn't maintain a ref on the outer connection (this) and // subsequently leaves open the possibility that the outer connection could be GC'ed before the SqlTransaction // is fully hooked up (leaving a DbTransaction with a null connection property). Ensure that this is reachable // until the completion of BeginTransaction with KeepAlive GC.KeepAlive(this); return transaction; } finally { Bid.ScopeLeave(ref hscp); } } public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionName) { WaitForPendingReconnection(); SqlStatistics statistics = null; IntPtr hscp; string xactName = ADP.IsEmpty(transactionName)? "None" : transactionName; Bid.ScopeEnter(out hscp, " %d#, iso=%d{ds.IsolationLevel}, transactionName='%ls'\n", ObjectID, (int)iso, xactName); try { statistics = SqlStatistics.StartTimer(Statistics); // NOTE: we used to throw an exception if the transaction name was empty // (see MDAC 50292) but that was incorrect because we have a BeginTransaction // method that doesn't have a transactionName argument. SqlTransaction transaction; bool isFirstAttempt = true; do { transaction = GetOpenConnection().BeginSqlTransaction(iso, transactionName, isFirstAttempt); // do not reconnect twice Debug.Assert(isFirstAttempt || !transaction.InternalTransaction.ConnectionHasBeenRestored, "Restored connection on non-first attempt"); isFirstAttempt = false; } while (transaction.InternalTransaction.ConnectionHasBeenRestored); // SQLBU 503873 The GetOpenConnection line above doesn't keep a ref on the outer connection (this), // and it could be collected before the inner connection can hook it to the transaction, resulting in // a transaction with a null connection property. Use GC.KeepAlive to ensure this doesn't happen. GC.KeepAlive(this); return transaction; } finally { Bid.ScopeLeave(ref hscp); SqlStatistics.StopTimer(statistics); } } override public void ChangeDatabase(string database) { SqlStatistics statistics = null; RepairInnerConnection(); Bid.CorrelationTrace(" ObjectID%d#, ActivityID %ls\n", ObjectID); TdsParser bestEffortCleanupTarget = null; RuntimeHelpers.PrepareConstrainedRegions(); try { #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); statistics = SqlStatistics.StartTimer(Statistics); InnerConnection.ChangeDatabase(database); } #if DEBUG finally { tdsReliabilitySection.Stop(); } #endif //DEBUG } catch (System.OutOfMemoryException e) { Abort(e); throw; } catch (System.StackOverflowException e) { Abort(e); throw; } catch (System.Threading.ThreadAbortException e) { Abort(e); SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); throw; } finally { SqlStatistics.StopTimer(statistics); } } static public void ClearAllPools() { (new SqlClientPermission(PermissionState.Unrestricted)).Demand(); SqlConnectionFactory.SingletonInstance.ClearAllPools(); } static public void ClearPool(SqlConnection connection) { ADP.CheckArgumentNull(connection, "connection"); DbConnectionOptions connectionOptions = connection.UserConnectionOptions; if (null != connectionOptions) { connectionOptions.DemandPermission(); if (connection.IsContextConnection) { throw SQL.NotAvailableOnContextConnection(); } SqlConnectionFactory.SingletonInstance.ClearPool(connection); } } object ICloneable.Clone() { SqlConnection clone = new SqlConnection(this); Bid.Trace(" %d#, clone=%d#\n", ObjectID, clone.ObjectID); return clone; } void CloseInnerConnection() { // CloseConnection() now handles the lock // The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and // the command will no longer be cancelable. It might be desirable to be able to cancel the close opperation, but this is // outside of the scope of Whidbey RTM. See (SqlCommand::Cancel) for other lock. InnerConnection.CloseConnection(this, ConnectionFactory); } override public void Close() { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#" , ObjectID); Bid.CorrelationTrace(" ObjectID%d#, ActivityID %ls\n", ObjectID); try { SqlStatistics statistics = null; TdsParser bestEffortCleanupTarget = null; RuntimeHelpers.PrepareConstrainedRegions(); try { #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); statistics = SqlStatistics.StartTimer(Statistics); Task reconnectTask = _currentReconnectionTask; if (reconnectTask != null && !reconnectTask.IsCompleted) { CancellationTokenSource cts = _reconnectionCancellationSource; if (cts != null) { cts.Cancel(); } AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false); // we do not need to deal with possible exceptions in reconnection if (State != ConnectionState.Open) {// if we cancelled before the connection was opened OnStateChange(DbConnectionInternal.StateChangeClosed); } } CancelOpenAndWait(); CloseInnerConnection(); GC.SuppressFinalize(this); if (null != Statistics) { ADP.TimerCurrent(out _statistics._closeTimestamp); } } #if DEBUG finally { tdsReliabilitySection.Stop(); } #endif //DEBUG } catch (System.OutOfMemoryException e) { Abort(e); throw; } catch (System.StackOverflowException e) { Abort(e); throw; } catch (System.Threading.ThreadAbortException e) { Abort(e); SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); throw; } finally { SqlStatistics.StopTimer(statistics); } } finally { SqlDebugContext sdc = _sdc; _sdc = null; Bid.ScopeLeave(ref hscp); if (sdc != null) { sdc.Dispose(); } } } new public SqlCommand CreateCommand() { return new SqlCommand(null, this); } private void DisposeMe(bool disposing) { // MDAC 65459 _credential = null; // clear credential here rather than in IDisposable.Dispose as this is only specific to SqlConnection only // IDisposable.Dispose is generated code from a template and used by other providers as well if (!disposing) { // DevDiv2 Bug 457934:SQLConnection leaks when not disposed // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/457934 // For non-pooled connections we need to make sure that if the SqlConnection was not closed, then we release the GCHandle on the stateObject to allow it to be GCed // For pooled connections, we will rely on the pool reclaiming the connection var innerConnection = (InnerConnection as SqlInternalConnectionTds); if ((innerConnection != null) && (!innerConnection.ConnectionOptions.Pooling)) { var parser = innerConnection.Parser; if ((parser != null) && (parser._physicalStateObj != null)) { parser._physicalStateObj.DecrementPendingCallbacks(release: false); } } } } #if !MOBILE public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction transaction) { if (IsContextConnection) { throw SQL.NotAvailableOnContextConnection(); } EnlistDistributedTransactionHelper(transaction); } #endif override public void Open() { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#", ObjectID) ; Bid.CorrelationTrace(" ObjectID%d#, ActivityID %ls\n", ObjectID); try { if (StatisticsEnabled) { if (null == _statistics) { _statistics = new SqlStatistics(); } else { _statistics.ContinueOnNewConnection(); } } SqlStatistics statistics = null; RuntimeHelpers.PrepareConstrainedRegions(); try { statistics = SqlStatistics.StartTimer(Statistics); if (!TryOpen(null)) { throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending); } } finally { SqlStatistics.StopTimer(statistics); } } finally { Bid.ScopeLeave(ref hscp); } } internal void RegisterWaitingForReconnect(Task waitingTask) { if (((SqlConnectionString)ConnectionOptions).MARS) { return; } Interlocked.CompareExchange(ref _asyncWaitingForReconnection, waitingTask, null); if (_asyncWaitingForReconnection != waitingTask) { // somebody else managed to register throw SQL.MARSUnspportedOnConnection(); } } private async Task ReconnectAsync(int timeout) { try { long commandTimeoutExpiration = 0; if (timeout > 0) { commandTimeoutExpiration = ADP.TimerCurrent() + ADP.TimerFromSeconds(timeout); } CancellationTokenSource cts = new CancellationTokenSource(); _reconnectionCancellationSource = cts; CancellationToken ctoken = cts.Token; int retryCount = _connectRetryCount; // take a snapshot: could be changed by modifying the connection string for (int attempt = 0; attempt < retryCount; attempt++) { if (ctoken.IsCancellationRequested) { Bid.Trace(" Orginal ClientConnectionID %ls - reconnection cancelled\n", _originalConnectionId.ToString()); return; } try { _impersonateIdentity = _lastIdentity; try { ForceNewConnection = true; await OpenAsync(ctoken).ConfigureAwait(false); // On success, increment the reconnect count - we don't really care if it rolls over since it is approx. _reconnectCount = unchecked(_reconnectCount + 1); #if DEBUG Debug.Assert(_recoverySessionData._debugReconnectDataApplied, "Reconnect data was not applied !"); #endif } finally { _impersonateIdentity = null; ForceNewConnection = false; } Bid.Trace(" Reconnection suceeded. ClientConnectionID %ls -> %ls \n", _originalConnectionId.ToString(), ClientConnectionId.ToString()); return; } catch (SqlException e) { Bid.Trace(" Orginal ClientConnectionID %ls - reconnection attempt failed error %ls\n", _originalConnectionId.ToString(), e.Message); if (attempt == retryCount - 1) { Bid.Trace(" Orginal ClientConnectionID %ls - give up reconnection\n", _originalConnectionId.ToString()); throw SQL.CR_AllAttemptsFailed(e, _originalConnectionId); } if (timeout > 0 && ADP.TimerRemaining(commandTimeoutExpiration) < ADP.TimerFromSeconds(ConnectRetryInterval)) { throw SQL.CR_NextAttemptWillExceedQueryTimeout(e, _originalConnectionId); } } await Task.Delay(1000 * ConnectRetryInterval, ctoken).ConfigureAwait(false); } } finally { _recoverySessionData = null; _supressStateChangeForReconnection = false; } Debug.Assert(false, "Should not reach this point"); } internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) { Task runningReconnect = _currentReconnectionTask; // This loop in the end will return not completed reconnect task or null while (runningReconnect != null && runningReconnect.IsCompleted) { // clean current reconnect task (if it is the same one we checked Interlocked.CompareExchange(ref _currentReconnectionTask, null, runningReconnect); // make sure nobody started new task (if which case we did not clean it) runningReconnect = _currentReconnectionTask; } if (runningReconnect == null) { if (_connectRetryCount > 0) { SqlInternalConnectionTds tdsConn = GetOpenTdsConnection(); if (tdsConn._sessionRecoveryAcknowledged) { TdsParserStateObject stateObj = tdsConn.Parser._physicalStateObj; if (!stateObj.ValidateSNIConnection()) { if (tdsConn.Parser._sessionPool != null) { if (tdsConn.Parser._sessionPool.ActiveSessionsCount > 0) { // >1 MARS session if (beforeDisconnect != null) { beforeDisconnect(); } OnError(SQL.CR_UnrecoverableClient(ClientConnectionId), true, null); } } SessionData cData = tdsConn.CurrentSessionData; cData.AssertUnrecoverableStateCountIsCorrect(); if (cData._unrecoverableStatesCount == 0) { bool callDisconnect = false; lock (_reconnectLock) { tdsConn.CheckEnlistedTransactionBinding(); runningReconnect = _currentReconnectionTask; // double check after obtaining the lock if (runningReconnect == null) { if (cData._unrecoverableStatesCount == 0) { // could change since the first check, but now is stable since connection is know to be broken _originalConnectionId = ClientConnectionId; Bid.Trace(" Connection ClientConnectionID %ls is invalid, reconnecting\n", _originalConnectionId.ToString()); _recoverySessionData = cData; if (beforeDisconnect != null) { beforeDisconnect(); } try { _supressStateChangeForReconnection = true; tdsConn.DoomThisConnection(); } catch (SqlException) { } runningReconnect = Task.Run(() => ReconnectAsync(timeout)); // if current reconnect is not null, somebody already started reconnection task - some kind of race condition Debug.Assert(_currentReconnectionTask == null, "Duplicate reconnection tasks detected"); _currentReconnectionTask = runningReconnect; } } else { callDisconnect = true; } } if (callDisconnect && beforeDisconnect != null) { beforeDisconnect(); } } else { if (beforeDisconnect != null) { beforeDisconnect(); } OnError(SQL.CR_UnrecoverableServer(ClientConnectionId), true, null); } } // ValidateSNIConnection } // sessionRecoverySupported } // connectRetryCount>0 } else { // runningReconnect = null if (beforeDisconnect != null) { beforeDisconnect(); } } return runningReconnect; } // this is straightforward, but expensive method to do connection resiliency - it take locks and all prepartions as for TDS request partial void RepairInnerConnection() { WaitForPendingReconnection(); if (_connectRetryCount == 0) { return; } SqlInternalConnectionTds tdsConn = InnerConnection as SqlInternalConnectionTds; if (tdsConn != null) { tdsConn.ValidateConnectionForExecute(null); tdsConn.GetSessionAndReconnectIfNeeded((SqlConnection)this); } } private void WaitForPendingReconnection() { Task reconnectTask = _currentReconnectionTask; if (reconnectTask != null && !reconnectTask.IsCompleted) { AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false); } } void CancelOpenAndWait() { // copy from member to avoid changes by background thread var completion = _currentCompletion; if (completion != null) { completion.Item1.TrySetCanceled(); ((IAsyncResult)completion.Item2).AsyncWaitHandle.WaitOne(); } Debug.Assert(_currentCompletion == null, "After waiting for an async call to complete, there should be no completion source"); } public override Task OpenAsync(CancellationToken cancellationToken) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#", ObjectID) ; Bid.CorrelationTrace(" ObjectID%d#, ActivityID %ls\n", ObjectID); try { if (StatisticsEnabled) { if (null == _statistics) { _statistics = new SqlStatistics(); } else { _statistics.ContinueOnNewConnection(); } } SqlStatistics statistics = null; RuntimeHelpers.PrepareConstrainedRegions(); try { statistics = SqlStatistics.StartTimer(Statistics); System.Transactions.Transaction transaction = ADP.GetCurrentTransaction(); TaskCompletionSource completion = new TaskCompletionSource(transaction); TaskCompletionSource result = new TaskCompletionSource(); if (cancellationToken.IsCancellationRequested) { result.SetCanceled(); return result.Task; } if (IsContextConnection) { // Async not supported on Context Connections result.SetException(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection())); return result.Task; } bool completed; try { completed = TryOpen(completion); } catch (Exception e) { result.SetException(e); return result.Task; } if (completed) { result.SetResult(null); } else { CancellationTokenRegistration registration = new CancellationTokenRegistration(); if (cancellationToken.CanBeCanceled) { registration = cancellationToken.Register(() => completion.TrySetCanceled()); } OpenAsyncRetry retry = new OpenAsyncRetry(this, completion, result, registration); _currentCompletion = new Tuple, Task>(completion, result.Task); completion.Task.ContinueWith(retry.Retry, TaskScheduler.Default); return result.Task; } return result.Task; } finally { SqlStatistics.StopTimer(statistics); } } finally { Bid.ScopeLeave(ref hscp); } } private class OpenAsyncRetry { SqlConnection _parent; TaskCompletionSource _retry; TaskCompletionSource _result; CancellationTokenRegistration _registration; public OpenAsyncRetry(SqlConnection parent, TaskCompletionSource retry, TaskCompletionSource result, CancellationTokenRegistration registration) { _parent = parent; _retry = retry; _result = result; _registration = registration; } internal void Retry(Task retryTask) { Bid.Trace(" %d#\n", _parent.ObjectID); _registration.Dispose(); try { SqlStatistics statistics = null; RuntimeHelpers.PrepareConstrainedRegions(); try { statistics = SqlStatistics.StartTimer(_parent.Statistics); if (retryTask.IsFaulted) { Exception e = retryTask.Exception.InnerException; _parent.CloseInnerConnection(); _parent._currentCompletion = null; _result.SetException(retryTask.Exception.InnerException); } else if (retryTask.IsCanceled) { _parent.CloseInnerConnection(); _parent._currentCompletion = null; _result.SetCanceled(); } else { bool result; // protect continuation from ----s with close and cancel lock (_parent.InnerConnection) { result = _parent.TryOpen(_retry); } if (result) { _parent._currentCompletion = null; _result.SetResult(null); } else { _parent.CloseInnerConnection(); _parent._currentCompletion = null; _result.SetException(ADP.ExceptionWithStackTrace(ADP.InternalError(ADP.InternalErrorCode.CompletedConnectReturnedPending))); } } } finally { SqlStatistics.StopTimer(statistics); } } catch (Exception e) { _parent.CloseInnerConnection(); _parent._currentCompletion = null; _result.SetException(e); } } } private bool TryOpen(TaskCompletionSource retry) { if (_impersonateIdentity != null) { if (_impersonateIdentity.User == DbConnectionPoolIdentity.GetCurrentWindowsIdentity().User) { return TryOpenInner(retry); } else { using (WindowsImpersonationContext context = _impersonateIdentity.Impersonate()) { return TryOpenInner(retry); } } } else { if (this.UsesIntegratedSecurity((SqlConnectionString)ConnectionOptions)) { _lastIdentity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity(); } else { _lastIdentity = null; } return TryOpenInner(retry); } } private bool TryOpenInner(TaskCompletionSource retry) { TdsParser bestEffortCleanupTarget = null; RuntimeHelpers.PrepareConstrainedRegions(); try { #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else { #endif //DEBUG if (ForceNewConnection) { if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry, UserConnectionOptions)) { return false; } } else { if (!InnerConnection.TryOpenConnection(this, ConnectionFactory, retry, UserConnectionOptions)) { return false; } } // does not require GC.KeepAlive(this) because of OnStateChange // GetBestEffortCleanup must happen AFTER OpenConnection to get the correct target. bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); var tdsInnerConnection = (InnerConnection as SqlInternalConnectionTds); if (tdsInnerConnection == null) { SqlInternalConnectionSmi innerConnection = (InnerConnection as SqlInternalConnectionSmi); innerConnection.AutomaticEnlistment(); } else { Debug.Assert(tdsInnerConnection.Parser != null, "Where's the parser?"); if (!tdsInnerConnection.ConnectionOptions.Pooling) { // For non-pooled connections, we need to make sure that the finalizer does actually run to avoid leaking SNI handles GC.ReRegisterForFinalize(this); } if (StatisticsEnabled) { ADP.TimerCurrent(out _statistics._openTimestamp); tdsInnerConnection.Parser.Statistics = _statistics; } else { tdsInnerConnection.Parser.Statistics = null; _statistics = null; // in case of previous Open/Close/reset_CollectStats sequence } CompleteOpen(); } } #if DEBUG finally { tdsReliabilitySection.Stop(); } #endif //DEBUG } catch (System.OutOfMemoryException e) { Abort(e); throw; } catch (System.StackOverflowException e) { Abort(e); throw; } catch (System.Threading.ThreadAbortException e) { Abort(e); SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); throw; } return true; } // // INTERNAL PROPERTIES // internal bool HasLocalTransaction { get { return GetOpenConnection().HasLocalTransaction; } } internal bool HasLocalTransactionFromAPI { get { Task reconnectTask = _currentReconnectionTask; if (reconnectTask != null && !reconnectTask.IsCompleted) { return false; //we will not go into reconnection if we are inside the transaction } return GetOpenConnection().HasLocalTransactionFromAPI; } } internal bool IsShiloh { get { if (_currentReconnectionTask != null) { // holds true even if task is completed return true; // if CR is enabled, connection, if established, will be Katmai+ } return GetOpenConnection().IsShiloh; } } internal bool IsYukonOrNewer { get { if (_currentReconnectionTask != null) { // holds true even if task is completed return true; // if CR is enabled, connection, if established, will be Katmai+ } return GetOpenConnection().IsYukonOrNewer; } } internal bool IsKatmaiOrNewer { get { if (_currentReconnectionTask != null) { // holds true even if task is completed return true; // if CR is enabled, connection, if established, will be Katmai+ } return GetOpenConnection().IsKatmaiOrNewer; } } internal TdsParser Parser { get { SqlInternalConnectionTds tdsConnection = (GetOpenConnection() as SqlInternalConnectionTds); if (null == tdsConnection) { throw SQL.NotAvailableOnContextConnection(); } return tdsConnection.Parser; } } internal bool Asynchronous { get { SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; return ((null != constr) ? constr.Asynchronous : SqlConnectionString.DEFAULT.Asynchronous); } } // // INTERNAL METHODS // internal void ValidateConnectionForExecute(string method, SqlCommand command) { Task asyncWaitingForReconnection=_asyncWaitingForReconnection; if (asyncWaitingForReconnection!=null) { if (!asyncWaitingForReconnection.IsCompleted) { throw SQL.MARSUnspportedOnConnection(); } else { Interlocked.CompareExchange(ref _asyncWaitingForReconnection, null, asyncWaitingForReconnection); } } if (_currentReconnectionTask != null) { Task currentReconnectionTask = _currentReconnectionTask; if (currentReconnectionTask != null && !currentReconnectionTask.IsCompleted) { return; // execution will wait for this task later } } SqlInternalConnection innerConnection = GetOpenConnection(method); innerConnection.ValidateConnectionForExecute(command); } // Surround name in brackets and then escape any end bracket to protect against SQL Injection. // NOTE: if the user escapes it themselves it will not work, but this was the case in V1 as well // as native OleDb and Odbc. static internal string FixupDatabaseTransactionName(string name) { if (!ADP.IsEmpty(name)) { return SqlServerEscapeHelper.EscapeIdentifier(name); } else { return name; } } // If wrapCloseInAction is defined, then the action it defines will be run with the connection close action passed in as a parameter // The close action also supports being run asynchronously internal void OnError(SqlException exception, bool breakConnection, Action wrapCloseInAction) { Debug.Assert(exception != null && exception.Errors.Count != 0, "SqlConnection: OnError called with null or empty exception!"); // Bug fix - MDAC 49022 - connection open after failure... Problem was parser was passing // Open as a state - because the parser's connection to the netlib was open. We would // then set the connection state to the parser's state - which is not correct. The only // time the connection state should change to what is passed in to this function is if // the parser is broken, then we should be closed. Changed to passing in // TdsParserState, not ConnectionState. // fixed by [....] if (breakConnection && (ConnectionState.Open == State)) { if (wrapCloseInAction != null) { int capturedCloseCount = _closeCount; Action closeAction = () => { if (capturedCloseCount == _closeCount) { Bid.Trace(" %d#, Connection broken.\n", ObjectID); Close(); } }; wrapCloseInAction(closeAction); } else { Bid.Trace(" %d#, Connection broken.\n", ObjectID); Close(); } } if (exception.Class >= TdsEnums.MIN_ERROR_CLASS) { // It is an error, and should be thrown. Class of TdsEnums.MIN_ERROR_CLASS or above is an error, // below TdsEnums.MIN_ERROR_CLASS denotes an info message. throw exception; } else { // If it is a class < TdsEnums.MIN_ERROR_CLASS, it is a warning collection - so pass to handler this.OnInfoMessage(new SqlInfoMessageEventArgs(exception)); } } // // PRIVATE METHODS // // SxS: using Debugger.IsAttached // [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] private void CompleteOpen() { Debug.Assert(ConnectionState.Open == State, "CompleteOpen not open"); // be sure to mark as open so SqlDebugCheck can issue Query // check to see if we need to hook up sql-debugging if a debugger is attached // We only need this check for Shiloh and earlier servers. if (!GetOpenConnection().IsYukonOrNewer && System.Diagnostics.Debugger.IsAttached) { bool debugCheck = false; try { new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); // MDAC 66682, 69017 debugCheck = true; } catch (SecurityException e) { ADP.TraceExceptionWithoutRethrow(e); } if (debugCheck) { // if we don't have Unmanaged code permission, don't check for debugging // but let the connection be opened while under the debugger CheckSQLDebugOnConnect(); } } } internal SqlInternalConnection GetOpenConnection() { SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection); if (null == innerConnection) { throw ADP.ClosedConnectionError(); } return innerConnection; } internal SqlInternalConnection GetOpenConnection(string method) { DbConnectionInternal innerConnection = InnerConnection; SqlInternalConnection innerSqlConnection = (innerConnection as SqlInternalConnection); if (null == innerSqlConnection) { throw ADP.OpenConnectionRequired(method, innerConnection.State); } return innerSqlConnection; } internal SqlInternalConnectionTds GetOpenTdsConnection() { SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); if (null == innerConnection) { throw ADP.ClosedConnectionError(); } return innerConnection; } internal SqlInternalConnectionTds GetOpenTdsConnection(string method) { SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); if (null == innerConnection) { throw ADP.OpenConnectionRequired(method, InnerConnection.State); } return innerConnection; } internal void OnInfoMessage(SqlInfoMessageEventArgs imevent) { bool notified; OnInfoMessage(imevent, out notified); } internal void OnInfoMessage(SqlInfoMessageEventArgs imevent, out bool notified) { if (Bid.TraceOn) { Debug.Assert(null != imevent, "null SqlInfoMessageEventArgs"); Bid.Trace(" %d#, Message='%ls'\n", ObjectID, ((null != imevent) ? imevent.Message : "")); } SqlInfoMessageEventHandler handler = (SqlInfoMessageEventHandler)Events[EventInfoMessage]; if (null != handler) { notified = true; try { handler(this, imevent); } catch (Exception e) { // MDAC 53175 if (!ADP.IsCatchableOrSecurityExceptionType(e)) { throw; } ADP.TraceExceptionWithoutRethrow(e); } } else { notified = false; } } // // SQL DEBUGGING SUPPORT // // this only happens once per connection // SxS: using named file mapping APIs // [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] private void CheckSQLDebugOnConnect() { IntPtr hFileMap; uint pid = (uint)SafeNativeMethods.GetCurrentProcessId(); string mapFileName; // If Win2k or later, prepend "Global\\" to enable this to work through TerminalServices. if (ADP.IsPlatformNT5) { mapFileName = "Global\\" + TdsEnums.SDCI_MAPFILENAME; } else { mapFileName = TdsEnums.SDCI_MAPFILENAME; } mapFileName = mapFileName + pid.ToString(CultureInfo.InvariantCulture); hFileMap = NativeMethods.OpenFileMappingA(0x4/*FILE_MAP_READ*/, false, mapFileName); if (ADP.PtrZero != hFileMap) { IntPtr pMemMap = NativeMethods.MapViewOfFile(hFileMap, 0x4/*FILE_MAP_READ*/, 0, 0, IntPtr.Zero); if (ADP.PtrZero != pMemMap) { SqlDebugContext sdc = new SqlDebugContext(); sdc.hMemMap = hFileMap; sdc.pMemMap = pMemMap; sdc.pid = pid; // optimization: if we only have to refresh memory-mapped data at connection open time // optimization: then call here instead of in CheckSQLDebug() which gets called // optimization: at command execution time // RefreshMemoryMappedData(sdc); // delaying setting out global state until after we issue this first SQLDebug command so that // we don't reentrantly call into CheckSQLDebug CheckSQLDebug(sdc); // now set our global state _sdc = sdc; } } } // This overload is called by the Command object when executing stored procedures. Note that // if SQLDebug has never been called, it is a noop. internal void CheckSQLDebug() { if (null != _sdc) CheckSQLDebug(_sdc); } // SxS: using GetCurrentThreadId [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] // MDAC 66682, 69017 private void CheckSQLDebug(SqlDebugContext sdc) { // check to see if debugging has been activated Debug.Assert(null != sdc, "SQL Debug: invalid null debugging context!"); #pragma warning disable 618 uint tid = (uint)AppDomain.GetCurrentThreadId(); // Sql Debugging doesn't need fiber support; #pragma warning restore 618 RefreshMemoryMappedData(sdc); // // If we get here, the debugger must be hooked up. if (!sdc.active) { if (sdc.fOption/*TdsEnums.SQLDEBUG_ON*/) { // turn on sdc.active = true; sdc.tid = tid; try { IssueSQLDebug(TdsEnums.SQLDEBUG_ON, sdc.machineName, sdc.pid, sdc.dbgpid, sdc.sdiDllName, sdc.data); sdc.tid = 0; // reset so that the first successful time through, we notify the server of the context switch } catch { sdc.active = false; throw; } } } // be sure to pick up thread context switch, especially the first time through if (sdc.active) { if (!sdc.fOption/*TdsEnums.SQLDEBUG_OFF*/) { // turn off and free the memory sdc.Dispose(); // okay if we throw out here, no state to clean up IssueSQLDebug(TdsEnums.SQLDEBUG_OFF, null, 0, 0, null, null); } else { // notify server of context change if (sdc.tid != tid) { sdc.tid = tid; try { IssueSQLDebug(TdsEnums.SQLDEBUG_CONTEXT, null, sdc.pid, sdc.tid, null, null); } catch { sdc.tid = 0; throw; } } } } } private void IssueSQLDebug(uint option, string machineName, uint pid, uint id, string sdiDllName, byte[] data) { if (GetOpenConnection().IsYukonOrNewer) { // return; } // SqlCommand c = new SqlCommand(TdsEnums.SP_SDIDEBUG, this); c.CommandType = CommandType.StoredProcedure; // context param SqlParameter p = new SqlParameter(null, SqlDbType.VarChar, TdsEnums.SQLDEBUG_MODE_NAMES[option].Length); p.Value = TdsEnums.SQLDEBUG_MODE_NAMES[option]; c.Parameters.Add(p); if (option == TdsEnums.SQLDEBUG_ON) { // debug dll name p = new SqlParameter(null, SqlDbType.VarChar, sdiDllName.Length); p.Value = sdiDllName; c.Parameters.Add(p); // debug machine name p = new SqlParameter(null, SqlDbType.VarChar, machineName.Length); p.Value = machineName; c.Parameters.Add(p); } if (option != TdsEnums.SQLDEBUG_OFF) { // client pid p = new SqlParameter(null, SqlDbType.Int); p.Value = pid; c.Parameters.Add(p); // dbgpid or tid p = new SqlParameter(null, SqlDbType.Int); p.Value = id; c.Parameters.Add(p); } if (option == TdsEnums.SQLDEBUG_ON) { // debug data p = new SqlParameter(null, SqlDbType.VarBinary, (null != data) ? data.Length : 0); p.Value = data; c.Parameters.Add(p); } c.ExecuteNonQuery(); } public static void ChangePassword(string connectionString, string newPassword) { IntPtr hscp; Bid.ScopeEnter(out hscp, "") ; Bid.CorrelationTrace(" ActivityID %ls\n"); try { if (ADP.IsEmpty(connectionString)) { throw SQL.ChangePasswordArgumentMissing("connectionString"); } if (ADP.IsEmpty(newPassword)) { throw SQL.ChangePasswordArgumentMissing("newPassword"); } if (TdsEnums.MAXLEN_NEWPASSWORD < newPassword.Length) { throw ADP.InvalidArgumentLength("newPassword", TdsEnums.MAXLEN_NEWPASSWORD); } SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, null); SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key); if (connectionOptions.IntegratedSecurity) { throw SQL.ChangePasswordConflictsWithSSPI(); } if (! ADP.IsEmpty(connectionOptions.AttachDBFilename)) { throw SQL.ChangePasswordUseOfUnallowedKey(SqlConnectionString.KEY.AttachDBFilename); } if (connectionOptions.ContextConnection) { throw SQL.ChangePasswordUseOfUnallowedKey(SqlConnectionString.KEY.Context_Connection); } System.Security.PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); permissionSet.Demand(); ChangePassword(connectionString, connectionOptions, null, newPassword, null); } finally { Bid.ScopeLeave(ref hscp) ; } } public static void ChangePassword(string connectionString, SqlCredential credential, SecureString newSecurePassword) { IntPtr hscp; Bid.ScopeEnter(out hscp, "") ; Bid.CorrelationTrace(" ActivityID %ls\n"); try { if (ADP.IsEmpty(connectionString)) { throw SQL.ChangePasswordArgumentMissing("connectionString"); } // check credential; not necessary to check the length of password in credential as the check is done by SqlCredential class if (credential == null) { throw SQL.ChangePasswordArgumentMissing("credential"); } if (newSecurePassword == null || newSecurePassword.Length == 0) { throw SQL.ChangePasswordArgumentMissing("newSecurePassword");; } if (!newSecurePassword.IsReadOnly()) { throw ADP.MustBeReadOnly("newSecurePassword"); } if (TdsEnums.MAXLEN_NEWPASSWORD < newSecurePassword.Length) { throw ADP.InvalidArgumentLength("newSecurePassword", TdsEnums.MAXLEN_NEWPASSWORD); } SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential); SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key); // Check for incompatible connection string value with SqlCredential if (!ADP.IsEmpty(connectionOptions.UserID) || !ADP.IsEmpty(connectionOptions.Password)) { throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); } if (connectionOptions.IntegratedSecurity) { throw SQL.ChangePasswordConflictsWithSSPI(); } if (! ADP.IsEmpty(connectionOptions.AttachDBFilename)) { throw SQL.ChangePasswordUseOfUnallowedKey(SqlConnectionString.KEY.AttachDBFilename); } if (connectionOptions.ContextConnection) { throw SQL.ChangePasswordUseOfUnallowedKey(SqlConnectionString.KEY.Context_Connection); } System.Security.PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); permissionSet.Demand(); ChangePassword(connectionString, connectionOptions, credential, null, newSecurePassword); } finally { Bid.ScopeLeave(ref hscp) ; } } private static void ChangePassword(string connectionString, SqlConnectionString connectionOptions, SqlCredential credential, string newPassword, SecureString newSecurePassword ) { // note: This is the only case where we directly construt the internal connection, passing in the new password. // Normally we would simply create a regular connectoin and open it but there is no other way to pass the // new password down to the constructor. Also it would have an unwanted impact on the connection pool // using (SqlInternalConnectionTds con = new SqlInternalConnectionTds(null, connectionOptions, credential, null, newPassword, newSecurePassword, false)) { if (!con.IsYukonOrNewer) { throw SQL.ChangePasswordRequiresYukon(); } } SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential); SqlConnectionFactory.SingletonInstance.ClearPool(key); } internal void RegisterForConnectionCloseNotification(ref Task outterTask, object value, int tag) { // Connection exists, schedule removal, will be added to ref collection after calling ValidateAndReconnect outterTask = outterTask.ContinueWith(task => { RemoveWeakReference(value); return task; }, TaskScheduler.Default).Unwrap(); } // updates our context with any changes made to the memory-mapped data by an external process static private void RefreshMemoryMappedData(SqlDebugContext sdc) { Debug.Assert(ADP.PtrZero != sdc.pMemMap, "SQL Debug: invalid null value for pMemMap!"); // copy memory mapped file contents into managed types MEMMAP memMap = (MEMMAP)Marshal.PtrToStructure(sdc.pMemMap, typeof(MEMMAP)); sdc.dbgpid = memMap.dbgpid; sdc.fOption = (memMap.fOption == 1) ? true : false; // xlate ansi byte[] -> managed strings Encoding cp = System.Text.Encoding.GetEncoding(TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE); sdc.machineName = cp.GetString(memMap.rgbMachineName, 0, memMap.rgbMachineName.Length); sdc.sdiDllName = cp.GetString(memMap.rgbDllName, 0, memMap.rgbDllName.Length); // just get data reference sdc.data = memMap.rgbData; } public void ResetStatistics() { if (IsContextConnection) { throw SQL.NotAvailableOnContextConnection(); } if (null != Statistics) { Statistics.Reset(); if (ConnectionState.Open == State) { // update timestamp; ADP.TimerCurrent(out _statistics._openTimestamp); } } } public IDictionary RetrieveStatistics() { if (IsContextConnection) { throw SQL.NotAvailableOnContextConnection(); } if (null != Statistics) { UpdateStatistics(); return Statistics.GetHashtable(); } else { return new SqlStatistics().GetHashtable(); } } private void UpdateStatistics() { if (ConnectionState.Open == State) { // update timestamp ADP.TimerCurrent(out _statistics._closeTimestamp); } // delegate the rest of the work to the SqlStatistics class Statistics.UpdateStatistics(); } // // UDT SUPPORT // private Assembly ResolveTypeAssembly(AssemblyName asmRef, bool throwOnError) { Debug.Assert(TypeSystemAssemblyVersion != null, "TypeSystemAssembly should be set !"); if (string.Compare(asmRef.Name, "Microsoft.SqlServer.Types", StringComparison.OrdinalIgnoreCase) == 0) { if (Bid.TraceOn) { if (asmRef.Version!=TypeSystemAssemblyVersion) { Bid.Trace(" SQL CLR type version change: Server sent %ls, client will instantiate %ls", asmRef.Version.ToString(), TypeSystemAssemblyVersion.ToString()); } } asmRef.Version = TypeSystemAssemblyVersion; } try { return Assembly.Load(asmRef); } catch (Exception e) { if (throwOnError || !ADP.IsCatchableExceptionType(e)) { throw; } else { return null; }; } } // internal void CheckGetExtendedUDTInfo(SqlMetaDataPriv metaData, bool fThrow) { if (metaData.udtType == null) { // If null, we have not obtained extended info. Debug.Assert(!ADP.IsEmpty(metaData.udtAssemblyQualifiedName), "Unexpected state on GetUDTInfo"); // Parameter throwOnError determines whether exception from Assembly.Load is thrown. metaData.udtType = Type.GetType(typeName:metaData.udtAssemblyQualifiedName, assemblyResolver:asmRef => ResolveTypeAssembly(asmRef, fThrow), typeResolver:null, throwOnError: fThrow); if (fThrow && metaData.udtType == null) { // throw SQL.UDTUnexpectedResult(metaData.udtAssemblyQualifiedName); } } } internal object GetUdtValue(object value, SqlMetaDataPriv metaData, bool returnDBNull) { if (returnDBNull && ADP.IsNull(value)) { return DBNull.Value; } object o = null; // Since the serializer doesn't handle nulls... if (ADP.IsNull(value)) { Type t = metaData.udtType; Debug.Assert(t != null, "Unexpected null of udtType on GetUdtValue!"); o = t.InvokeMember("Null", BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static, null, null, new Object[]{}, CultureInfo.InvariantCulture); Debug.Assert(o != null); return o; } else { MemoryStream stm = new MemoryStream((byte[]) value); o = SerializationHelperSql9.Deserialize(stm, metaData.udtType); Debug.Assert(o != null, "object could NOT be created"); return o; } } internal byte[] GetBytes(object o) { Microsoft.SqlServer.Server.Format format = Microsoft.SqlServer.Server.Format.Native; int maxSize = 0; return GetBytes(o, out format, out maxSize); } internal byte[] GetBytes(object o, out Microsoft.SqlServer.Server.Format format, out int maxSize) { SqlUdtInfo attr = AssemblyCache.GetInfoFromType(o.GetType()); maxSize = attr.MaxByteSize; format = attr.SerializationFormat; if (maxSize < -1 || maxSize >= UInt16.MaxValue) { // Do we need this? Is this the right place? throw new InvalidOperationException(o.GetType() + ": invalid Size"); } byte[] retval; using (MemoryStream stm = new MemoryStream(maxSize < 0 ? 0 : maxSize)) { SerializationHelperSql9.Serialize(stm, o); retval = stm.ToArray(); } return retval; } } // SqlConnection // [ ComVisible(true), ClassInterface(ClassInterfaceType.None), Guid("afef65ad-4577-447a-a148-83acadd3d4b9"), ] [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")] public sealed class SQLDebugging: ISQLDebug { // Security stuff const int STANDARD_RIGHTS_REQUIRED = (0x000F0000); const int DELETE = (0x00010000); const int READ_CONTROL = (0x00020000); const int WRITE_DAC = (0x00040000); const int WRITE_OWNER = (0x00080000); const int SYNCHRONIZE = (0x00100000); const int FILE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x000001FF); const uint GENERIC_READ = (0x80000000); const uint GENERIC_WRITE = (0x40000000); const uint GENERIC_EXECUTE = (0x20000000); const uint GENERIC_ALL = (0x10000000); const int SECURITY_DESCRIPTOR_REVISION = (1); const int ACL_REVISION = (2); const int SECURITY_AUTHENTICATED_USER_RID = (0x0000000B); const int SECURITY_LOCAL_SYSTEM_RID = (0x00000012); const int SECURITY_BUILTIN_DOMAIN_RID = (0x00000020); const int SECURITY_WORLD_RID = (0x00000000); const byte SECURITY_NT_AUTHORITY = 5; const int DOMAIN_GROUP_RID_ADMINS = (0x00000200); const int DOMAIN_ALIAS_RID_ADMINS = (0x00000220); const int sizeofSECURITY_ATTRIBUTES = 12; // sizeof(SECURITY_ATTRIBUTES); const int sizeofSECURITY_DESCRIPTOR = 20; // sizeof(SECURITY_DESCRIPTOR); const int sizeofACCESS_ALLOWED_ACE = 12; // sizeof(ACCESS_ALLOWED_ACE); const int sizeofACCESS_DENIED_ACE = 12; // sizeof(ACCESS_DENIED_ACE); const int sizeofSID_IDENTIFIER_AUTHORITY = 6; // sizeof(SID_IDENTIFIER_AUTHORITY) const int sizeofACL = 8; // sizeof(ACL); private IntPtr CreateSD(ref IntPtr pDacl) { IntPtr pSecurityDescriptor = IntPtr.Zero; IntPtr pUserSid = IntPtr.Zero; IntPtr pAdminSid = IntPtr.Zero; IntPtr pNtAuthority = IntPtr.Zero; int cbAcl = 0; bool status = false; pNtAuthority = Marshal.AllocHGlobal(sizeofSID_IDENTIFIER_AUTHORITY); if (pNtAuthority == IntPtr.Zero) goto cleanup; Marshal.WriteInt32(pNtAuthority, 0, 0); Marshal.WriteByte(pNtAuthority, 4, 0); Marshal.WriteByte(pNtAuthority, 5, SECURITY_NT_AUTHORITY); status = NativeMethods.AllocateAndInitializeSid( pNtAuthority, (byte)1, SECURITY_AUTHENTICATED_USER_RID, 0, 0, 0, 0, 0, 0, 0, ref pUserSid); if (!status || pUserSid == IntPtr.Zero) { goto cleanup; } status = NativeMethods.AllocateAndInitializeSid( pNtAuthority, (byte)2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, ref pAdminSid); if (!status || pAdminSid == IntPtr.Zero) { goto cleanup; } status = false; pSecurityDescriptor = Marshal.AllocHGlobal(sizeofSECURITY_DESCRIPTOR); if (pSecurityDescriptor == IntPtr.Zero) { goto cleanup; } for (int i = 0; i < sizeofSECURITY_DESCRIPTOR; i++) Marshal.WriteByte(pSecurityDescriptor, i, (byte)0); cbAcl = sizeofACL + (2 * (sizeofACCESS_ALLOWED_ACE)) + sizeofACCESS_DENIED_ACE + NativeMethods.GetLengthSid(pUserSid) + NativeMethods.GetLengthSid(pAdminSid); pDacl = Marshal.AllocHGlobal(cbAcl); if (pDacl == IntPtr.Zero) { goto cleanup; } // rights must be added in a certain order. Namely, deny access first, then add access if (NativeMethods.InitializeAcl(pDacl, cbAcl, ACL_REVISION)) if (NativeMethods.AddAccessDeniedAce(pDacl, ACL_REVISION, WRITE_DAC, pUserSid)) if (NativeMethods.AddAccessAllowedAce(pDacl, ACL_REVISION, GENERIC_READ, pUserSid)) if (NativeMethods.AddAccessAllowedAce(pDacl, ACL_REVISION, GENERIC_ALL, pAdminSid)) if (NativeMethods.InitializeSecurityDescriptor(pSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION)) if (NativeMethods.SetSecurityDescriptorDacl(pSecurityDescriptor, true, pDacl, false)) { status = true; } cleanup : if (pNtAuthority != IntPtr.Zero) { Marshal.FreeHGlobal(pNtAuthority); } if (pAdminSid != IntPtr.Zero) NativeMethods.FreeSid(pAdminSid); if (pUserSid != IntPtr.Zero) NativeMethods.FreeSid(pUserSid); if (status) return pSecurityDescriptor; else { if (pSecurityDescriptor != IntPtr.Zero) { Marshal.FreeHGlobal(pSecurityDescriptor); } } return IntPtr.Zero; } // SxS: using file mapping API (CreateFileMapping) // [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] bool ISQLDebug.SQLDebug(int dwpidDebugger, int dwpidDebuggee, [MarshalAs(UnmanagedType.LPStr)] string pszMachineName, [MarshalAs(UnmanagedType.LPStr)] string pszSDIDLLName, int dwOption, int cbData, byte[] rgbData) { bool result = false; IntPtr hFileMap = IntPtr.Zero; IntPtr pMemMap = IntPtr.Zero; IntPtr pSecurityDescriptor = IntPtr.Zero; IntPtr pSecurityAttributes = IntPtr.Zero; IntPtr pDacl = IntPtr.Zero; // validate the structure if (null == pszMachineName || null == pszSDIDLLName) return false; if (pszMachineName.Length > TdsEnums.SDCI_MAX_MACHINENAME || pszSDIDLLName.Length > TdsEnums.SDCI_MAX_DLLNAME) return false; // note that these are ansi strings Encoding cp = System.Text.Encoding.GetEncoding(TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE); byte[] rgbMachineName = cp.GetBytes(pszMachineName); byte[] rgbSDIDLLName = cp.GetBytes(pszSDIDLLName); if (null != rgbData && cbData > TdsEnums.SDCI_MAX_DATA) return false; string mapFileName; // If Win2k or later, prepend "Global\\" to enable this to work through TerminalServices. if (ADP.IsPlatformNT5) { mapFileName = "Global\\" + TdsEnums.SDCI_MAPFILENAME; } else { mapFileName = TdsEnums.SDCI_MAPFILENAME; } mapFileName = mapFileName + dwpidDebuggee.ToString(CultureInfo.InvariantCulture); // Create Security Descriptor pSecurityDescriptor = CreateSD(ref pDacl); pSecurityAttributes = Marshal.AllocHGlobal(sizeofSECURITY_ATTRIBUTES); if ((pSecurityDescriptor == IntPtr.Zero) || (pSecurityAttributes == IntPtr.Zero)) return false; Marshal.WriteInt32(pSecurityAttributes, 0, sizeofSECURITY_ATTRIBUTES); // nLength = sizeof(SECURITY_ATTRIBUTES) Marshal.WriteIntPtr(pSecurityAttributes, 4, pSecurityDescriptor); // lpSecurityDescriptor = pSecurityDescriptor Marshal.WriteInt32(pSecurityAttributes, 8, 0); // bInheritHandle = FALSE hFileMap = NativeMethods.CreateFileMappingA( ADP.InvalidPtr/*INVALID_HANDLE_VALUE*/, pSecurityAttributes, 0x4/*PAGE_READWRITE*/, 0, Marshal.SizeOf(typeof(MEMMAP)), mapFileName); if (IntPtr.Zero == hFileMap) { goto cleanup; } pMemMap = NativeMethods.MapViewOfFile(hFileMap, 0x6/*FILE_MAP_READ|FILE_MAP_WRITE*/, 0, 0, IntPtr.Zero); if (IntPtr.Zero == pMemMap) { goto cleanup; } // copy data to memory-mapped file // layout of MEMMAP structure is: // uint dbgpid // uint fOption // byte[32] machineName // byte[16] sdiDllName // uint dbData // byte[255] vData int offset = 0; Marshal.WriteInt32(pMemMap, offset, (int)dwpidDebugger); offset += 4; Marshal.WriteInt32(pMemMap, offset, (int)dwOption); offset += 4; Marshal.Copy(rgbMachineName, 0, ADP.IntPtrOffset(pMemMap, offset), rgbMachineName.Length); offset += TdsEnums.SDCI_MAX_MACHINENAME; Marshal.Copy(rgbSDIDLLName, 0, ADP.IntPtrOffset(pMemMap, offset), rgbSDIDLLName.Length); offset += TdsEnums.SDCI_MAX_DLLNAME; Marshal.WriteInt32(pMemMap, offset, (int)cbData); offset += 4; if (null != rgbData) { Marshal.Copy(rgbData, 0, ADP.IntPtrOffset(pMemMap, offset), (int)cbData); } NativeMethods.UnmapViewOfFile(pMemMap); result = true; cleanup : if (result == false) { if (hFileMap != IntPtr.Zero) NativeMethods.CloseHandle(hFileMap); } if (pSecurityAttributes != IntPtr.Zero) Marshal.FreeHGlobal(pSecurityAttributes); if (pSecurityDescriptor != IntPtr.Zero) Marshal.FreeHGlobal(pSecurityDescriptor); if (pDacl != IntPtr.Zero) Marshal.FreeHGlobal(pDacl); return result; } } // this is a private interface to com+ users // do not change this guid [ ComImport, ComVisible(true), Guid("6cb925bf-c3c0-45b3-9f44-5dd67c7b7fe8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), BestFitMapping(false, ThrowOnUnmappableChar = true), ] interface ISQLDebug { [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")] bool SQLDebug( int dwpidDebugger, int dwpidDebuggee, [MarshalAs(UnmanagedType.LPStr)] string pszMachineName, [MarshalAs(UnmanagedType.LPStr)] string pszSDIDLLName, int dwOption, int cbData, byte[] rgbData); } sealed class SqlDebugContext: IDisposable { // context data internal uint pid = 0; internal uint tid = 0; internal bool active = false; // memory-mapped data internal IntPtr pMemMap = ADP.PtrZero; internal IntPtr hMemMap = ADP.PtrZero; internal uint dbgpid = 0; internal bool fOption = false; internal string machineName = null; internal string sdiDllName = null; internal byte[] data = null; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } // using CloseHandle and UnmapViewOfFile - no exposure [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] private void Dispose(bool disposing) { if (disposing) { // Nothing to do here ; } if (pMemMap != IntPtr.Zero) { NativeMethods.UnmapViewOfFile(pMemMap); pMemMap = IntPtr.Zero; } if (hMemMap != IntPtr.Zero) { NativeMethods.CloseHandle(hMemMap); hMemMap = IntPtr.Zero; } active = false; } ~SqlDebugContext() { Dispose(false); } } // native interop memory mapped structure for sdi debugging [StructLayoutAttribute(LayoutKind.Sequential, Pack = 1)] internal struct MEMMAP { [MarshalAs(UnmanagedType.U4)] internal uint dbgpid; // id of debugger [MarshalAs(UnmanagedType.U4)] internal uint fOption; // 1 - start debugging, 0 - stop debugging [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] internal byte[] rgbMachineName; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] internal byte[] rgbDllName; [MarshalAs(UnmanagedType.U4)] internal uint cbData; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)] internal byte[] rgbData; } } // System.Data.SqlClient namespace