in src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java [3246:3649]
void Prelogin(String serverName, int portNumber) throws SQLServerException {
// Build a TDS Pre-Login packet to send to the server.
if ((!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString()))
|| (null != accessTokenInByte)) {
fedAuthRequiredByUser = true;
}
// Message length (including header)
final byte messageLength;
final byte fedAuthOffset;
if (fedAuthRequiredByUser) {
messageLength = TDS.B_PRELOGIN_MESSAGE_LENGTH_WITH_FEDAUTH;
requestedEncryptionLevel = TDS.ENCRYPT_ON;
// since we added one more line for prelogin option with fedauth,
// we also needed to modify the offsets above, by adding 5 to each offset,
// since the data session of each option is push 5 bytes behind.
fedAuthOffset = 5;
} else {
messageLength = TDS.B_PRELOGIN_MESSAGE_LENGTH;
fedAuthOffset = 0;
}
final byte[] preloginRequest = new byte[messageLength];
int preloginRequestOffset = 0;
byte[] bufferHeader = {
// Buffer Header
TDS.PKT_PRELOGIN, // Message Type
TDS.STATUS_BIT_EOM, 0, messageLength, 0, 0, // SPID (not used)
0, // Packet (not used)
0, // Window (not used)
};
System.arraycopy(bufferHeader, 0, preloginRequest, preloginRequestOffset, bufferHeader.length);
preloginRequestOffset = preloginRequestOffset + bufferHeader.length;
byte[] preloginOptionsBeforeFedAuth = {
// OPTION_TOKEN (BYTE), OFFSET (USHORT), LENGTH (USHORT)
TDS.B_PRELOGIN_OPTION_VERSION, 0, (byte) (16 + fedAuthOffset), 0, 6, // UL_VERSION + US_SUBBUILD
TDS.B_PRELOGIN_OPTION_ENCRYPTION, 0, (byte) (22 + fedAuthOffset), 0, 1, // B_FENCRYPTION
TDS.B_PRELOGIN_OPTION_TRACEID, 0, (byte) (23 + fedAuthOffset), 0, 36, // ClientConnectionId + ActivityId
};
System.arraycopy(preloginOptionsBeforeFedAuth, 0, preloginRequest, preloginRequestOffset,
preloginOptionsBeforeFedAuth.length);
preloginRequestOffset = preloginRequestOffset + preloginOptionsBeforeFedAuth.length;
if (fedAuthRequiredByUser) {
byte[] preloginOptions2 = {TDS.B_PRELOGIN_OPTION_FEDAUTHREQUIRED, 0, 64, 0, 1,};
System.arraycopy(preloginOptions2, 0, preloginRequest, preloginRequestOffset, preloginOptions2.length);
preloginRequestOffset = preloginRequestOffset + preloginOptions2.length;
}
preloginRequest[preloginRequestOffset] = TDS.B_PRELOGIN_OPTION_TERMINATOR;
preloginRequestOffset++;
// PL_OPTION_DATA
byte[] preloginOptionData = {
// Driver major and minor version, 1 byte each
(byte) SQLJdbcVersion.major, (byte) SQLJdbcVersion.minor,
// Revision (Big Endian), 2 bytes
(byte) ((SQLJdbcVersion.patch & 0xff00) >> 8), (byte) (SQLJdbcVersion.patch & 0xff),
// Build (Little Endian), 2 bytes
(byte) (SQLJdbcVersion.build & 0xff), (byte) ((SQLJdbcVersion.build & 0xff00) >> 8),
// - Encryption -
(null == clientCertificate) ? requestedEncryptionLevel
: (byte) (requestedEncryptionLevel | TDS.ENCRYPT_CLIENT_CERT),
// TRACEID Data Session (ClientConnectionId + ActivityId) - Initialize to 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,};
System.arraycopy(preloginOptionData, 0, preloginRequest, preloginRequestOffset, preloginOptionData.length);
preloginRequestOffset = preloginRequestOffset + preloginOptionData.length;
// If the client's PRELOGIN request message contains the FEDAUTHREQUIRED option,
// the client MUST specify 0x01 as the B_FEDAUTHREQUIRED value
if (fedAuthRequiredByUser) {
preloginRequest[preloginRequestOffset] = 1;
preloginRequestOffset = preloginRequestOffset + 1;
}
final byte[] preloginResponse = new byte[TDS.INITIAL_PACKET_SIZE];
String preloginErrorLogString = " Prelogin error: host " + serverName + " port " + portNumber;
final byte[] conIdByteArray = Util.asGuidByteArray(clientConnectionId);
int offset;
if (fedAuthRequiredByUser) {
offset = preloginRequest.length - 36 - 1; // point to the TRACEID Data Session (one more byte for fedauth
// data session)
} else {
offset = preloginRequest.length - 36; // point to the TRACEID Data Session
}
// copy ClientConnectionId
System.arraycopy(conIdByteArray, 0, preloginRequest, offset, conIdByteArray.length);
offset += conIdByteArray.length;
if (Util.isActivityTraceOn()) {
ActivityId activityId = ActivityCorrelator.getNext();
final byte[] actIdByteArray = Util.asGuidByteArray(activityId.getId());
System.arraycopy(actIdByteArray, 0, preloginRequest, offset, actIdByteArray.length);
offset += actIdByteArray.length;
long seqNum = activityId.getSequence();
Util.writeInt((int) seqNum, preloginRequest, offset);
offset += 4;
if (connectionlogger.isLoggable(Level.FINER)) {
connectionlogger.finer(toString() + " ActivityId " + activityId.toString());
}
}
if (connectionlogger.isLoggable(Level.FINER)) {
connectionlogger.finer(
toString() + " Requesting encryption level:" + TDS.getEncryptionLevel(requestedEncryptionLevel));
}
// Write the entire prelogin request
if (tdsChannel.isLoggingPackets())
tdsChannel.logPacket(preloginRequest, 0, preloginRequest.length, toString() + " Prelogin request");
try {
tdsChannel.write(preloginRequest, 0, preloginRequest.length);
tdsChannel.flush();
} catch (SQLServerException e) {
connectionlogger.warning(
toString() + preloginErrorLogString + " Error sending prelogin request: " + e.getMessage());
throw e;
}
if (Util.isActivityTraceOn()) {
ActivityCorrelator.setCurrentActivityIdSentFlag(); // indicate current ActivityId is sent
}
// Read the entire prelogin response
int responseLength = preloginResponse.length;
int responseBytesRead = 0;
boolean processedResponseHeader = false;
while (responseBytesRead < responseLength) {
int bytesRead;
try {
bytesRead = tdsChannel.read(preloginResponse, responseBytesRead, responseLength - responseBytesRead);
} catch (SQLServerException e) {
connectionlogger.warning(
toString() + preloginErrorLogString + " Error reading prelogin response: " + e.getMessage());
throw e;
}
// If we reached EOF before the end of the prelogin response then something is wrong.
//
// Special case: If there was no response at all (i.e. the server closed the connection),
// then maybe we are just trying to talk to an older server that doesn't support prelogin
// (and that we don't support with this driver).
if (-1 == bytesRead) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + preloginErrorLogString
+ " Unexpected end of prelogin response after " + responseBytesRead + " bytes read");
}
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
Object[] msgArgs = {getServerNameString(serverName), Integer.toString(portNumber),
SQLServerException.getErrString("R_notSQLServer")};
terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, form.format(msgArgs));
}
// Otherwise, we must have read some bytes...
assert bytesRead >= 0;
assert bytesRead <= responseLength - responseBytesRead;
if (tdsChannel.isLoggingPackets())
tdsChannel.logPacket(preloginResponse, responseBytesRead, bytesRead, toString() + " Prelogin response");
responseBytesRead += bytesRead;
// Validate the response header if we haven't already done so and
// we've read enough of the response to do it.
if (!processedResponseHeader && responseBytesRead >= TDS.PACKET_HEADER_SIZE) {
// Verify that the response is actually a response...
if (TDS.PKT_REPLY != preloginResponse[0]) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + preloginErrorLogString + " Unexpected response type:"
+ preloginResponse[0]);
}
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
Object[] msgArgs = {getServerNameString(serverName), Integer.toString(portNumber),
SQLServerException.getErrString("R_notSQLServer")};
terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, form.format(msgArgs));
}
// Verify that the response claims to only be one TDS packet long.
// In theory, it can be longer, but in current practice it isn't, as all of the
// prelogin response items easily fit into a single 4K packet.
if (TDS.STATUS_BIT_EOM != (TDS.STATUS_BIT_EOM & preloginResponse[1])) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + preloginErrorLogString + " Unexpected response status:"
+ preloginResponse[1]);
}
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
Object[] msgArgs = {getServerNameString(serverName), Integer.toString(portNumber),
SQLServerException.getErrString("R_notSQLServer")};
terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, form.format(msgArgs));
}
// Verify that the length of the response claims to be small enough to fit in the allocated area
responseLength = Util.readUnsignedShortBigEndian(preloginResponse, 2);
assert responseLength >= 0;
if (responseLength >= preloginResponse.length) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + preloginErrorLogString + " Response length:"
+ responseLength + " is greater than allowed length:" + preloginResponse.length);
}
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
Object[] msgArgs = {getServerNameString(serverName), Integer.toString(portNumber),
SQLServerException.getErrString("R_notSQLServer")};
terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, form.format(msgArgs));
}
processedResponseHeader = true;
}
}
// Walk the response for prelogin options received. We expect at least to get
// back the server version and the encryption level.
boolean receivedVersionOption = false;
negotiatedEncryptionLevel = TDS.ENCRYPT_INVALID;
int responseIndex = TDS.PACKET_HEADER_SIZE;
while (true) {
// Get the option token
if (responseIndex >= responseLength) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Option token not found");
}
throwInvalidTDS();
}
byte optionToken = preloginResponse[responseIndex++];
// When we reach the option terminator, we're done processing option tokens
if (TDS.B_PRELOGIN_OPTION_TERMINATOR == optionToken)
break;
// Get the offset and length that follows the option token
if (responseIndex + 4 >= responseLength) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Offset/Length not found for option:" + optionToken);
}
throwInvalidTDS();
}
int optionOffset = Util.readUnsignedShortBigEndian(preloginResponse, responseIndex)
+ TDS.PACKET_HEADER_SIZE;
responseIndex += 2;
assert optionOffset >= 0;
int optionLength = Util.readUnsignedShortBigEndian(preloginResponse, responseIndex);
responseIndex += 2;
assert optionLength >= 0;
if (optionOffset + optionLength > responseLength) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Offset:" + optionOffset + " and length:" + optionLength
+ " exceed response length:" + responseLength);
}
throwInvalidTDS();
}
switch (optionToken) {
case TDS.B_PRELOGIN_OPTION_VERSION:
if (receivedVersionOption) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Version option already received");
}
throwInvalidTDS();
}
if (6 != optionLength) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Version option length:" + optionLength
+ " is incorrect. Correct value is 6.");
}
throwInvalidTDS();
}
serverMajorVersion = preloginResponse[optionOffset];
if (serverMajorVersion < 9) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Server major version:" + serverMajorVersion
+ " is not supported by this driver.");
}
MessageFormat form = new MessageFormat(
SQLServerException.getErrString("R_unsupportedServerVersion"));
Object[] msgArgs = {Integer.toString(preloginResponse[optionOffset])};
terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, form.format(msgArgs));
}
if (connectionlogger.isLoggable(Level.FINE))
connectionlogger
.fine(toString() + " Server returned major version:" + preloginResponse[optionOffset]);
receivedVersionOption = true;
break;
case TDS.B_PRELOGIN_OPTION_ENCRYPTION:
if (TDS.ENCRYPT_INVALID != negotiatedEncryptionLevel) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Encryption option already received");
}
throwInvalidTDS();
}
if (1 != optionLength) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Encryption option length:" + optionLength
+ " is incorrect. Correct value is 1.");
}
throwInvalidTDS();
}
negotiatedEncryptionLevel = preloginResponse[optionOffset];
// If the server did not return a valid encryption level, terminate the connection.
if (TDS.ENCRYPT_OFF != negotiatedEncryptionLevel && TDS.ENCRYPT_ON != negotiatedEncryptionLevel
&& TDS.ENCRYPT_REQ != negotiatedEncryptionLevel
&& TDS.ENCRYPT_NOT_SUP != negotiatedEncryptionLevel) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Server returned "
+ TDS.getEncryptionLevel(negotiatedEncryptionLevel));
}
throwInvalidTDS();
}
if (connectionlogger.isLoggable(Level.FINER))
connectionlogger.finer(toString() + " Negotiated encryption level:"
+ TDS.getEncryptionLevel(negotiatedEncryptionLevel));
// If we requested SSL encryption and the server does not support it, then terminate the connection.
if (TDS.ENCRYPT_ON == requestedEncryptionLevel && TDS.ENCRYPT_ON != negotiatedEncryptionLevel
&& TDS.ENCRYPT_REQ != negotiatedEncryptionLevel) {
terminate(SQLServerException.DRIVER_ERROR_SSL_FAILED,
SQLServerException.getErrString("R_sslRequiredNoServerSupport"));
}
// If we say we don't support SSL and the server doesn't accept unencrypted connections,
// then terminate the connection.
if (TDS.ENCRYPT_NOT_SUP == requestedEncryptionLevel
&& TDS.ENCRYPT_NOT_SUP != negotiatedEncryptionLevel) {
// If the server required an encrypted connection then terminate with an appropriate error.
if (TDS.ENCRYPT_REQ == negotiatedEncryptionLevel)
terminate(SQLServerException.DRIVER_ERROR_SSL_FAILED,
SQLServerException.getErrString("R_sslRequiredByServer"));
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger.warning(toString() + " Client requested encryption level: "
+ TDS.getEncryptionLevel(requestedEncryptionLevel)
+ " Server returned unexpected encryption level: "
+ TDS.getEncryptionLevel(negotiatedEncryptionLevel));
}
throwInvalidTDS();
}
break;
case TDS.B_PRELOGIN_OPTION_FEDAUTHREQUIRED:
// Only 0x00 and 0x01 are accepted values from the server.
if (0 != preloginResponse[optionOffset] && 1 != preloginResponse[optionOffset]) {
if (connectionlogger.isLoggable(Level.SEVERE)) {
connectionlogger.severe(toString()
+ " Server sent an unexpected value for FedAuthRequired PreLogin Option. Value was "
+ preloginResponse[optionOffset]);
}
MessageFormat form = new MessageFormat(
SQLServerException.getErrString("R_FedAuthRequiredPreLoginResponseInvalidValue"));
throw new SQLServerException(form.format(new Object[] {preloginResponse[optionOffset]}), null);
}
// We must NOT use the response for the FEDAUTHREQUIRED PreLogin option, if the connection string
// option
// was not using the new Authentication keyword or in other words, if Authentication=NotSpecified
// Or AccessToken is not null, mean token based authentication is used.
if (((null != authenticationString)
&& (!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())))
|| (null != accessTokenInByte)) {
fedAuthRequiredPreLoginResponse = (preloginResponse[optionOffset] == 1);
}
break;
default:
if (connectionlogger.isLoggable(Level.FINER))
connectionlogger.finer(toString() + " Ignoring prelogin response option:" + optionToken);
break;
}
}
if (!receivedVersionOption || TDS.ENCRYPT_INVALID == negotiatedEncryptionLevel) {
if (connectionlogger.isLoggable(Level.WARNING)) {
connectionlogger
.warning(toString() + " Prelogin response is missing version and/or encryption option.");
}
throwInvalidTDS();
}
}