private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trustServerCert, bool integratedSecurity, out bool marsCapable)
{
marsCapable = _fMARS; // Assign default value
bool isYukonOrLater = false;
Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
bool result = _physicalStateObj.TryReadNetworkPacket();
if (!result) { throw SQL.SynchronousCallMayNotPend(); }
if (_physicalStateObj._inBytesRead == 0)
{
// If the server did not respond then something has gone wrong and we need to close the connection
_physicalStateObj.AddError(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.PreloginError(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}
if (!_physicalStateObj.TryProcessHeader()) { throw SQL.SynchronousCallMayNotPend(); }
if(_physicalStateObj._inBytesPacket > TdsEnums.MAX_PACKET_SIZE || _physicalStateObj._inBytesPacket < 0)
{
throw SQL.InvalidPacketSize();
}
byte[] payload = new byte[_physicalStateObj._inBytesPacket];
Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
result = _physicalStateObj.TryReadByteArray(payload, 0, payload.Length);
if (!result) { throw SQL.SynchronousCallMayNotPend(); }
if (payload[0] == 0xaa)
{
// If the first byte is 0xAA, we are connecting to a 6.5 or earlier server, which
// is not supported.
throw SQL.InvalidSQLServerVersionUnknown();
}
int offset = 0;
int payloadOffset = 0;
int payloadLength = 0;
int option = payload[offset++];
while (option != (byte)PreLoginOptions.LASTOPT)
{
switch (option)
{
case (int)PreLoginOptions.VERSION:
payloadOffset = payload[offset++] << 8 | payload[offset++];
payloadLength = payload[offset++] << 8 | payload[offset++];
byte majorVersion = payload[payloadOffset];
byte minorVersion = payload[payloadOffset + 1];
int level = (payload[payloadOffset + 2] << 8) |
payload[payloadOffset + 3];
isYukonOrLater = majorVersion >= 9;
if (!isYukonOrLater)
{
marsCapable = false; // If pre-Yukon, MARS not supported.
}
break;
case (int)PreLoginOptions.ENCRYPT:
payloadOffset = payload[offset++] << 8 | payload[offset++];
payloadLength = payload[offset++] << 8 | payload[offset++];
EncryptionOptions serverOption = (EncryptionOptions)payload[payloadOffset];
/* internal enum EncryptionOptions {
OFF,
ON,
NOT_SUP,
REQ,
LOGIN
} */
switch (_encryptionOption)
{
case (EncryptionOptions.ON):
if (serverOption == EncryptionOptions.NOT_SUP)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}
break;
case (EncryptionOptions.OFF):
if (serverOption == EncryptionOptions.OFF)
{
// Only encrypt login.
_encryptionOption = EncryptionOptions.LOGIN;
}
else if (serverOption == EncryptionOptions.REQ)
{
// Encrypt all.
_encryptionOption = EncryptionOptions.ON;
}
break;
case (EncryptionOptions.NOT_SUP):
if (serverOption == EncryptionOptions.REQ)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}
break;
default:
Debug.Assert(false, "Invalid client encryption option detected");
break;
}
if (_encryptionOption == EncryptionOptions.ON ||
_encryptionOption == EncryptionOptions.LOGIN)
{
UInt32 error = 0;
UInt32 info = ((encrypt && !trustServerCert) ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0)
| (isYukonOrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0);
#if MANAGED_SNI
error = SNIProxy.Singleton.EnableSsl(_physicalStateObj.Handle, info);
#else
if (encrypt && !integratedSecurity)
{
// optimization: in case of SQL Authentication and encryption, set SNI_SSL_IGNORE_CHANNEL_BINDINGS to let SNI
// know that it does not need to allocate/retrieve the Channel Bindings from the SSL context.
info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS;
}
// Add SSL (Encryption) SNI provider.
error = SNINativeMethodWrapper.SNIAddProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.SSL_PROV, ref info);
#endif // MANAGED_SNI
if (error != TdsEnums.SNI_SUCCESS)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
ThrowExceptionAndWarning(_physicalStateObj);
}
#if !MANAGED_SNI
// in the case where an async connection is made, encryption is used and Windows Authentication is used,
// wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its
// Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
// before calling SNISecGenClientContext).
error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining());
if (error != TdsEnums.SNI_SUCCESS)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
ThrowExceptionAndWarning(_physicalStateObj);
}
#endif
// create a new packet encryption changes the internal packet size
try { } // EmptyTry/Finally to avoid FXCop violation
finally
{
_physicalStateObj.ClearAllWritePackets();
}
}
break;
case (int)PreLoginOptions.INSTANCE:
payloadOffset = payload[offset++] << 8 | payload[offset++];
payloadLength = payload[offset++] << 8 | payload[offset++];
byte ERROR_INST = 0x1;
byte instanceResult = payload[payloadOffset];
if (instanceResult == ERROR_INST)
{
// Check if server says ERROR_INST. That either means the cached info
// we used to connect is not valid or we connected to a named instance
// listening on default params.
return PreLoginHandshakeStatus.InstanceFailure;
}
break;
case (int)PreLoginOptions.THREADID:
// DO NOTHING FOR THREADID
offset += 4;
break;
case (int)PreLoginOptions.MARS:
payloadOffset = payload[offset++] << 8 | payload[offset++];
payloadLength = payload[offset++] << 8 | payload[offset++];
marsCapable = (payload[payloadOffset] == 0 ? false : true);
Debug.Assert(payload[payloadOffset] == 0 || payload[payloadOffset] == 1, "Value for Mars PreLoginHandshake option not equal to 1 or 0!");
break;
case (int)PreLoginOptions.TRACEID:
// DO NOTHING FOR TRACEID
offset += 4;
break;
default:
Debug.Assert(false, "UNKNOWN option in ConsumePreLoginHandshake, option:" + option);
// DO NOTHING FOR THESE UNKNOWN OPTIONS
offset += 4;
break;
}
if (offset < payload.Length)
{
option = payload[offset++];
}
else
{
break;
}
}
return PreLoginHandshakeStatus.Successful;
}