in src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java [5797:6178]
private void sendLogon(LogonCommand logonCommand, SSPIAuthentication authentication,
FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData) throws SQLServerException {
// TDS token handler class for processing logon responses.
//
// Note:
// As a local inner class, LogonProcessor implicitly has access to private
// members of SQLServerConnection. Certain JVM implementations generate
// package scope accessors to any private members touched by this class,
// effectively changing visibility of such members from private to package.
// Therefore, it is IMPORTANT then for this class not to touch private
// member variables in SQLServerConnection that contain secure information.
final class LogonProcessor extends TDSTokenHandler {
private final SSPIAuthentication auth;
private byte[] secBlobOut = null;
StreamLoginAck loginAckToken;
LogonProcessor(SSPIAuthentication auth) {
super("logon");
this.auth = auth;
this.loginAckToken = null;
}
boolean onSSPI(TDSReader tdsReader) throws SQLServerException {
StreamSSPI ack = new StreamSSPI();
ack.setFromTDS(tdsReader);
// Extract SSPI data from the response. If another round trip is
// required then we will start it after we finish processing the
// rest of this response.
boolean[] done = {false};
secBlobOut = auth.generateClientContext(ack.sspiBlob, done);
return true;
}
boolean onLoginAck(TDSReader tdsReader) throws SQLServerException {
loginAckToken = new StreamLoginAck();
loginAckToken.setFromTDS(tdsReader);
sqlServerVersion = loginAckToken.sSQLServerVersion;
tdsVersion = loginAckToken.tdsVersion;
return true;
}
final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQLServerException {
// If we have the login ack already then we're done processing.
if (null != loginAckToken)
return true;
// No login ack yet. Check if there is more SSPI handshake to do...
if (null != secBlobOut && 0 != secBlobOut.length) {
// Yes, there is. So start the next SSPI round trip and indicate to
// our caller that it needs to keep the processing loop going.
logonCommand.startRequest(TDS.PKT_SSPI).writeBytes(secBlobOut, 0, secBlobOut.length);
return false;
}
// The login ack comes in its own complete TDS response message.
// So integrated auth effectively receives more response messages from
// the server than it sends request messages from the driver.
// To ensure that the rest of the response can be read, fake another
// request to the server so that the channel sees int auth login
// as a symmetric conversation.
logonCommand.startRequest(TDS.PKT_SSPI);
logonCommand.onRequestComplete();
++tdsChannel.numMsgsSent;
TDSParser.parse(tdsReader, this);
return true;
}
}
// Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option.
assert !(integratedSecurity && fedAuthRequiredPreLoginResponse);
// Cannot use both SSPI and FedAuth
assert (!integratedSecurity) || !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested);
// fedAuthFeatureExtensionData provided without fed auth feature request
assert (null == fedAuthFeatureExtensionData)
|| (federatedAuthenticationInfoRequested || federatedAuthenticationRequested);
// Fed Auth feature requested without specifying fedAuthFeatureExtensionData.
assert (null != fedAuthFeatureExtensionData
|| !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested));
String sUser = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString());
String sPwd = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString());
String appName = activeConnectionProperties
.getProperty(SQLServerDriverStringProperty.APPLICATION_NAME.toString());
String interfaceLibName = "Microsoft JDBC Driver " + SQLJdbcVersion.major + "." + SQLJdbcVersion.minor;
String databaseName = activeConnectionProperties
.getProperty(SQLServerDriverStringProperty.DATABASE_NAME.toString());
String serverName = (null != currentConnectPlaceHolder) ? currentConnectPlaceHolder.getServerName()
: activeConnectionProperties.getProperty(
SQLServerDriverStringProperty.SERVER_NAME
.toString());
if (null != serverName && serverName.length() > 128) {
serverName = serverName.substring(0, 128);
}
byte[] secBlob = new byte[0];
boolean[] done = {false};
if (null != authentication) {
secBlob = authentication.generateClientContext(secBlob, done);
sUser = null;
sPwd = null;
}
byte hostnameBytes[] = toUCS16(hostName);
byte userBytes[] = toUCS16(sUser);
byte passwordBytes[] = encryptPassword(sPwd);
int passwordLen = (null != passwordBytes) ? passwordBytes.length : 0;
byte appNameBytes[] = toUCS16(appName);
byte serverNameBytes[] = toUCS16(serverName);
byte interfaceLibNameBytes[] = toUCS16(interfaceLibName);
byte interfaceLibVersionBytes[] = {(byte) SQLJdbcVersion.build, (byte) SQLJdbcVersion.patch,
(byte) SQLJdbcVersion.minor, (byte) SQLJdbcVersion.major};
byte databaseNameBytes[] = toUCS16(databaseName);
byte netAddress[] = new byte[6];
int dataLen = 0;
// Denali --> TDS 7.4, Katmai (10.0) & later 7.3B, Prelogin disconnects anything older
if (serverMajorVersion >= 11) {
tdsVersion = TDS.VER_DENALI;
} else if (serverMajorVersion >= 10) {
tdsVersion = TDS.VER_KATMAI;
} else if (serverMajorVersion >= 9) {
tdsVersion = TDS.VER_YUKON;
} else {
assert false : "prelogin did not disconnect for the old version: " + serverMajorVersion;
}
final int tdsLoginRequestBaseLength = 94;
TDSWriter tdsWriter = logonCommand.startRequest(TDS.PKT_LOGON70);
int len = tdsLoginRequestBaseLength + hostnameBytes.length + appNameBytes.length + serverNameBytes.length
+ interfaceLibNameBytes.length + databaseNameBytes.length + ((secBlob != null) ? secBlob.length : 0)
+ 4; // AE is always on;
// only add lengths of password and username if not using SSPI or requesting federated authentication info
if (!integratedSecurity && !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested)
&& null == clientCertificate) {
len = len + passwordLen + userBytes.length;
}
int aeOffset = len;
// AE is always ON
len += writeAEFeatureRequest(false, tdsWriter);
if (federatedAuthenticationInfoRequested || federatedAuthenticationRequested) {
len = len + writeFedAuthFeatureRequest(false, tdsWriter, fedAuthFeatureExtensionData);
}
// Data Classification is always enabled (by default)
len += writeDataClassificationFeatureRequest(false, tdsWriter);
len = len + writeUTF8SupportFeatureRequest(false, tdsWriter);
len = len + writeDNSCacheFeatureRequest(false, tdsWriter);
len = len + 1; // add 1 to length because of FeatureEx terminator
// Idle Connection Resiliency is requested
if (connectRetryCount > 0) {
len = len + writeIdleConnectionResiliencyRequest(false, tdsWriter);
}
// Length of entire Login 7 packet
tdsWriter.writeInt(len);
tdsWriter.writeInt(tdsVersion);
tdsWriter.writeInt(requestedPacketSize);
tdsWriter.writeBytes(interfaceLibVersionBytes); // writeBytes() is little endian
tdsWriter.writeInt(DriverJDBCVersion.getProcessId()); // Client process ID
tdsWriter.writeInt(0); // Primary server connection ID
tdsWriter.writeByte((byte) (// OptionFlags1:
TDS.LOGIN_OPTION1_ORDER_X86 | // X86 byte order for numeric & datetime types
TDS.LOGIN_OPTION1_CHARSET_ASCII | // ASCII character set
TDS.LOGIN_OPTION1_FLOAT_IEEE_754 | // IEEE 754 floating point representation
TDS.LOGIN_OPTION1_DUMPLOAD_ON | // Require dump/load BCP capabilities
TDS.LOGIN_OPTION1_USE_DB_OFF | // No ENVCHANGE after USE DATABASE
TDS.LOGIN_OPTION1_INIT_DB_FATAL | // Fail connection if initial database change fails
TDS.LOGIN_OPTION1_SET_LANG_ON // Warn on SET LANGUAGE stmt
));
// OptionFlags2:
tdsWriter.writeByte((byte) (TDS.LOGIN_OPTION2_INIT_LANG_FATAL | // Fail connection if initial language change
// fails
TDS.LOGIN_OPTION2_ODBC_ON | // Use ODBC defaults (ANSI_DEFAULTS ON, IMPLICIT_TRANSACTIONS OFF, TEXTSIZE
// inf, ROWCOUNT inf)
(replication ? TDS.LOGIN_OPTION2_USER_SQLREPL_ON : TDS.LOGIN_OPTION2_USER_SQLREPL_OFF)
| (integratedSecurity ? // integrated security if integratedSecurity requested
TDS.LOGIN_OPTION2_INTEGRATED_SECURITY_ON
: TDS.LOGIN_OPTION2_INTEGRATED_SECURITY_OFF)));
// TypeFlags
tdsWriter.writeByte((byte) (TDS.LOGIN_SQLTYPE_DEFAULT | (applicationIntent != null
&& applicationIntent.equals(ApplicationIntent.READ_ONLY) ? TDS.LOGIN_READ_ONLY_INTENT
: TDS.LOGIN_READ_WRITE_INTENT)));
// OptionFlags3
byte colEncSetting;
// AE is always ON
{
colEncSetting = TDS.LOGIN_OPTION3_FEATURE_EXTENSION;
}
// Accept unknown collations from Katmai & later servers
tdsWriter.writeByte((byte) (TDS.LOGIN_OPTION3_DEFAULT | colEncSetting
| ((serverMajorVersion >= 10) ? TDS.LOGIN_OPTION3_UNKNOWN_COLLATION_HANDLING : 0)));
tdsWriter.writeInt((byte) 0); // Client time zone
tdsWriter.writeInt((byte) 0); // Client LCID
tdsWriter.writeShort((short) tdsLoginRequestBaseLength);
// Hostname
tdsWriter.writeShort((short) ((hostName != null && !hostName.isEmpty()) ? hostName.length() : 0));
dataLen += hostnameBytes.length;
// Only send user/password over if not NTLM or fSSPI or fed auth ADAL... If both user/password and SSPI are in
// login rec, only SSPI is used.
if (ntlmAuthentication) {
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
tdsWriter.writeShort((short) (0));
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
tdsWriter.writeShort((short) (0));
} else if (!integratedSecurity && !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested)
&& null == clientCertificate) {
// User and Password
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
tdsWriter.writeShort((short) (sUser == null ? 0 : sUser.length()));
dataLen += userBytes.length;
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
tdsWriter.writeShort((short) (sPwd == null ? 0 : sPwd.length()));
dataLen += passwordLen;
} else {
// User and Password are null
tdsWriter.writeShort((short) (0));
tdsWriter.writeShort((short) (0));
tdsWriter.writeShort((short) (0));
tdsWriter.writeShort((short) (0));
}
// App name
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
tdsWriter.writeShort((short) (appName == null ? 0 : appName.length()));
dataLen += appNameBytes.length;
// Server name
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
tdsWriter.writeShort((short) (serverName == null ? 0 : serverName.length()));
dataLen += serverNameBytes.length;
// Unused
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
// AE is always ON
{
tdsWriter.writeShort((short) 4);
dataLen += 4;
}
// Interface library name
assert null != interfaceLibName;
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
tdsWriter.writeShort((short) (interfaceLibName.length()));
dataLen += interfaceLibNameBytes.length;
// Language
tdsWriter.writeShort((short) 0);
tdsWriter.writeShort((short) 0);
// Database
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
tdsWriter.writeShort((short) (databaseName == null ? 0 : databaseName.length()));
dataLen += databaseNameBytes.length;
// Client ID (from MAC addr)
tdsWriter.writeBytes(netAddress);
final int uShortMax = 65535;
// SSPI data
if (!integratedSecurity) {
tdsWriter.writeShort((short) 0);
tdsWriter.writeShort((short) 0);
} else {
tdsWriter.writeShort((short) (tdsLoginRequestBaseLength + dataLen));
if (uShortMax <= secBlob.length) {
tdsWriter.writeShort((short) (uShortMax));
} else {
tdsWriter.writeShort((short) (secBlob.length));
}
}
// Database to attach during connection process
tdsWriter.writeShort((short) 0);
tdsWriter.writeShort((short) 0);
if (tdsVersion >= TDS.VER_YUKON) {
// TDS 7.2: Password change
tdsWriter.writeShort((short) 0);
tdsWriter.writeShort((short) 0);
// TDS 7.2: 32-bit SSPI byte count (used if 16 bits above were not sufficient)
if (null != secBlob && uShortMax <= secBlob.length) {
tdsWriter.writeInt(secBlob.length);
} else {
tdsWriter.writeInt((short) 0);
}
}
tdsWriter.writeBytes(hostnameBytes);
// Don't allow user credentials to be logged
tdsWriter.setDataLoggable(false);
// if we are using NTLM or SSPI or fed auth ADAL, do not send over username/password, since we will use SSPI
// instead
// Also do not send username or password if user is attempting client certificate authentication.
if (!integratedSecurity && !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested)
&& null == clientCertificate) {
tdsWriter.writeBytes(userBytes); // Username
tdsWriter.writeBytes(passwordBytes); // Password (encrypted)
}
tdsWriter.setDataLoggable(true);
tdsWriter.writeBytes(appNameBytes); // application name
tdsWriter.writeBytes(serverNameBytes); // server name
// AE is always ON
tdsWriter.writeInt(aeOffset);
tdsWriter.writeBytes(interfaceLibNameBytes); // interfaceLibName
tdsWriter.writeBytes(databaseNameBytes); // databaseName
// Don't allow user credentials to be logged
tdsWriter.setDataLoggable(false);
// SSPI data
if (integratedSecurity) {
tdsWriter.writeBytes(secBlob, 0, secBlob.length);
}
// AE is always ON
writeAEFeatureRequest(true, tdsWriter);
if (federatedAuthenticationInfoRequested || federatedAuthenticationRequested) {
writeFedAuthFeatureRequest(true, tdsWriter, fedAuthFeatureExtensionData);
}
writeDataClassificationFeatureRequest(true, tdsWriter);
writeUTF8SupportFeatureRequest(true, tdsWriter);
writeDNSCacheFeatureRequest(true, tdsWriter);
// Idle Connection Resiliency is requested
if (connectRetryCount > 0) {
writeIdleConnectionResiliencyRequest(true, tdsWriter);
}
tdsWriter.writeByte((byte) TDS.FEATURE_EXT_TERMINATOR);
tdsWriter.setDataLoggable(true);
LogonProcessor logonProcessor = new LogonProcessor(authentication);
TDSReader tdsReader;
do {
tdsReader = logonCommand.startResponse();
sessionRecovery.setConnectionRecoveryPossible(false);
TDSParser.parse(tdsReader, logonProcessor);
} while (!logonProcessor.complete(logonCommand, tdsReader));
if (sessionRecovery.getReconnectThread().isAlive() && !sessionRecovery.isConnectionRecoveryPossible()) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(this.toString()
+ "SessionRecovery feature extension ack was not sent by the server during reconnection.");
}
terminate(SQLServerException.DRIVER_ERROR_INVALID_TDS,
SQLServerException.getErrString("R_crClientNoRecoveryAckFromLogin"));
}
if (connectRetryCount > 0 && !sessionRecovery.getReconnectThread().isAlive()) {
sessionRecovery.getSessionStateTable().setOriginalCatalog(sCatalog);
sessionRecovery.getSessionStateTable().setOriginalCollation(databaseCollation);
sessionRecovery.getSessionStateTable().setOriginalLanguage(sLanguage);
}
}