private static void WriteTransactionHeader(TdsPackageWriter writer, TdsEnums.TransactionManagerRequestType request, long sqlTransactionId) { writer.NewPackage(TdsEnums.MT_TRANS); writer.WriteMarsHeader(sqlTransactionId); writer.WriteInt16((short)request); // write TransactionManager Request type }
public static void SendRpc(this TdsPackageWriter writer, SqlCollations defaultCollation, FormattableString sql, long sqlConnectionId) { writer.NewPackage(TdsEnums.MT_RPC); writer.WriteMarsHeader(sqlConnectionId); writer.WriteInt16(0xffff); writer.WriteInt16(TdsEnums.RPC_PROCID_EXECUTESQL); // Options writer.WriteInt16(TdsEnums.RPC_PARAM_DEFAULT); // Stream out parameters var parameters = CreateParameters(sql); foreach (var parameter in parameters) { // parameters can be unnamed var param = parameter; var value = parameter.Value; var isNull = value == null; writer.WriteByteLenString(param.Name); // Write parameter status writer.WriteByte(parameter.Status); var p = parameter; var mt = p.MetaData; WriteTdsTypeInfo(writer, mt, p.Size, p.IsNull, defaultCollation, p.Scale); WriteValue(writer, value, mt, isNull); } writer.SendLastMessage(); }
public static void SendExecuteBatch(this TdsPackageWriter tdsPackageWriter, string text, long sqlConnectionId) { tdsPackageWriter.NewPackage(TdsEnums.MT_SQL); tdsPackageWriter.WriteMarsHeader(sqlConnectionId); tdsPackageWriter.WriteUnicodeString(text); tdsPackageWriter.SendLastMessage(); }
public static void SendPreLoginHandshake(this TdsPackageWriter writer, string instanceName, bool marsOn) { // PreLoginHandshake buffer consists of: // 1) Standard header, with type = MT_PRELOGIN // 2) Consecutive 5 bytes for each option, (1 byte length, 2 byte offset, 2 byte buffer length) // 3) Consecutive data blocks for each option // NOTE: packet data needs to be big endian - not the standard little endian used by // the rest of the parser. writer.NewPackage(TdsEnums.MT_PRELOGIN); WriteHeader(writer, instanceName); WriteOptions(writer, instanceName, marsOn); // Write out last option - to let server know the second part of packet completed writer.SendLastMessage(); }
private void TestIntN(object value, int tdsType, bool nulltest, byte precision = 0, byte scale = 0, bool isPlp = false) { var stream = new TestStream(); var writer = new TdsPackageWriter(stream); var reader = new TdsPackageReader(stream); SetupColMetaData(reader, writer, tdsType, precision, scale, isPlp); var columwriter = new TdsColumnWriter(writer); var columnReader = new TdsColumnReader(reader); object result; if (nulltest) { writer.NewPackage(TdsEnums.MT_RPC); ObjectWriter(columwriter, tdsType, value, nulltest); writer.SendLastMessage(); result = ObjectReader(columnReader, value); Assert.Null(result); } writer.NewPackage(TdsEnums.MT_RPC); ObjectWriter(columwriter, tdsType, value, false); writer.SendLastMessage(); result = ObjectReader(columnReader, value); switch (value) { case Money v: Assert.Equal((decimal)v, result); break; case Money4 v: Assert.Equal((decimal)v, result); break; case SqlDate v: Assert.Equal((DateTime)v, result); break; case SqlDateTime2 v: Assert.Equal((DateTime)v, result); break; case SqlDateTime4 v: Assert.Equal((DateTime)v, result); break; case SqlImage v: Assert.Equal((byte[])v, result); break; case SqlUnicode v: Assert.Equal((string)v, result); break; case SqlXml v: Assert.Equal((string)v, result); break; case SqlVariant v: switch (v.Value) { case bool b: Assert.Equal(b, result); break; case byte b: Assert.Equal(b, result); break; case short b: Assert.Equal(b, result); break; case int b: Assert.Equal(b, result); break; case long b: Assert.Equal(b, result); break; case float b: Assert.Equal(b, result); break; case double b: Assert.Equal(b, result); break; case DateTime b: Assert.Equal(b, result); break; case Guid b: Assert.Equal(b, result); break; case decimal b: Assert.Equal(b, result); break; case byte[] b: Assert.Equal(b, result); break; case string b: Assert.Equal(b, result); break; case TimeSpan b: Assert.Equal(b, result); break; case DateTimeOffset b: Assert.Equal(b, result); break; default: Assert.False(true); break; } break; default: Assert.Equal(value, result); break; } Assert.Equal(reader.GetReadEndPos(), reader.GetReadPos()); if (!new[] { TdsEnums.SQLBIGBINARY, TdsEnums.SQLBIGVARBINARY, TdsEnums.SQLBIGVARCHAR, TdsEnums.SQLBIGCHAR, TdsEnums.SQLTEXT, TdsEnums.SQLNVARCHAR, TdsEnums.SQLNTEXT, TdsEnums.SQLNCHAR, TdsEnums.SQLXMLTYPE, TdsEnums.SQLIMAGE, }.Contains(tdsType) && !(value is SqlVariant v1 && (v1.Value is string s || v1.Value is byte[]))) { Assert.InRange(reader.GetReadPos() - 8, 0, TdsEnums.MaxSizeSqlValue); } }
public static void SendTdsLogin(this TdsPackageWriter writer, LoginOptions rec, SessionData recoverySessionData) { const string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME; var requestedFeatures = rec.RequestedFeatures; if (recoverySessionData != null && (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) == 0) { throw new Exception("Recovery session data without session recovery feature request"); } if (rec.HostName.Length > TdsEnums.MAXLEN_HOSTNAME) { throw new Exception("_workstationId.Length exceeds the max length for this value"); } if (!rec.UseSspi && rec.UserName.Length > TdsEnums.MAXLEN_USERNAME) { throw new Exception("_userID.Length exceeds the max length for this value"); } if (!rec.UseSspi && rec.Password.Length > TdsEnums.MAXLEN_PASSWORD) { throw new Exception("_password.Length exceeds the max length for this value"); } if (rec.ApplicationName.Length > TdsEnums.MAXLEN_APPNAME) { throw new Exception("_applicationName.Length exceeds the max length for this value"); } if (rec.ServerName.Length > TdsEnums.MAXLEN_SERVERNAME) { throw new Exception("_dataSource.Length exceeds the max length for this value"); } if (rec.Language.Length > TdsEnums.MAXLEN_LANGUAGE) { throw new Exception("_currentLanguage .Length exceeds the max length for this value"); } if (rec.Database.Length > TdsEnums.MAXLEN_DATABASE) { throw new Exception("_initialCatalog.Length exceeds the max length for this value"); } if (rec.AttachDbFilename.Length > TdsEnums.MAXLEN_ATTACHDBFILE) { throw new Exception("_attachDBFileName.Length exceeds the max length for this value"); } if (clientInterfaceName.Length > TdsEnums.MAXLEN_CLIENTINTERFACE) { throw new Exception("cchCltIntName can specify at most 128 unicode characters. See Tds spec"); } // get the password up front to use in sspi logic below var useFeatureExt = requestedFeatures != TdsEnums.FeatureExtension.None; // set the message type // length in bytes var length = TdsEnums.YUKON_LOG_REC_FIXED_LEN; // add up variable-len portions (multiply by 2 for byte len of char strings) // length += (rec.HostName.Length + rec.ApplicationName.Length + rec.ServerName.Length + clientInterfaceName.Length + rec.Language.Length + rec.Database.Length + rec.AttachDbFilename.Length) * 2; if (useFeatureExt) { length += 4; } // allocate memory for SSPI variables // only add lengths of password and username if not using SSPI var userName = ""; var encryptedPassword = new byte[0]; if (rec.UseSspi) { length += rec.ClientToken.Length; } else { userName = rec.UserName; encryptedPassword = ObfuscatePassword(rec.Password); length += userName.Length * 2 + encryptedPassword.Length; } var feOffset = length; if (useFeatureExt) { if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) { length += SessionRecoveryFeatureRequestLengths(recoverySessionData).totalLength; } if ((requestedFeatures & TdsEnums.FeatureExtension.GlobalTransactions) != 0) { length += 5; } length++; // for terminator } writer.NewPackage(TdsEnums.MT_LOGIN7); writer.WriteInt32(length); if (recoverySessionData == null) { writer.WriteInt32((TdsEnums.DENALI_MAJOR << 24) | (TdsEnums.DENALI_INCREMENT << 16) | TdsEnums.DENALI_MINOR); } else { writer.WriteUInt32(recoverySessionData.TdsVersion); } writer.WriteInt32(rec.PacketSize); writer.WriteInt32(TdsEnums.CLIENT_PROG_VER); writer.WriteInt32(GetCurrentProcessIdForTdsLoginOnly()); writer.WriteInt32(0); // connectionID is unused // Log7Flags (DWORD) var log7Flags = 0; /* * Current snapshot from TDS spec with the offsets added: * 0) fByteOrder:1, // byte order of numeric data types on client * 1) fCharSet:1, // character set on client * 2) fFloat:2, // Type of floating point on client * 4) fDumpLoad:1, // Dump/Load and BCP enable * 5) fUseDb:1, // USE notification * 6) fDatabase:1, // Initial database fatal flag * 7) fSetLang:1, // SET LANGUAGE notification * 8) fLanguage:1, // Initial language fatal flag * 9) fODBC:1, // Set if client is ODBC driver * 10) fTranBoundary:1, // Transaction boundary notification * 11) fDelegatedSec:1, // Security with delegation is available * 12) fUserType:3, // Type of user * 15) fIntegratedSecurity:1, // Set if client is using integrated security * 16) fSQLType:4, // Type of SQL sent from client * 20) fOLEDB:1, // Set if client is OLEDB driver * 21) fSpare1:3, // first bit used for read-only intent, rest unused * 24) fResetPassword:1, // set if client wants to reset password * 25) fNoNBCAndSparse:1, // set if client does not support NBC and Sparse column * 26) fUserInstance:1, // This connection wants to connect to a SQL "user instance" * 27) fUnknownCollationHandling:1, // This connection can handle unknown collation correctly. * 28) fExtension:1 // Extensions are used * 32 - total */ // first byte log7Flags |= TdsEnums.USE_DB_ON << 5; log7Flags |= TdsEnums.INIT_DB_FATAL << 6; log7Flags |= TdsEnums.SET_LANG_ON << 7; // second byte log7Flags |= TdsEnums.INIT_LANG_FATAL << 8; log7Flags |= TdsEnums.ODBC_ON << 9; if (rec.UseReplication) { log7Flags |= TdsEnums.REPL_ON << 12; } if (rec.UseSspi) { log7Flags |= TdsEnums.SSPI_ON << 15; } // third byte if (rec.ReadOnlyIntent) { log7Flags |= TdsEnums.READONLY_INTENT_ON << 21; // read-only intent flag is a first bit of fSpare1 } // 4th one if (rec.UserInstance) { log7Flags |= 1 << 26; } if (useFeatureExt) { log7Flags |= 1 << 28; } writer.WriteInt32(log7Flags); writer.WriteInt32(0); // ClientTimeZone is not used writer.WriteInt32(0); // LCID is unused by server // Start writing offset and length of variable length portions var offset = TdsEnums.YUKON_LOG_REC_FIXED_LEN; // write offset/length pairs // note that you must always set ibHostName since it indicates the beginning of the variable length section of the login record writer.WriteInt16(offset); // host name offset writer.WriteInt16(rec.HostName.Length); offset += rec.HostName.Length * 2; // Only send user/password over if not fSSPI... If both user/password and SSPI are in login // rec, only SSPI is used. Confirmed same behavior as in luxor. if (rec.UseSspi) { // case where user/password data is not used, send over zeros writer.WriteInt16(0); // userName offset writer.WriteInt16(0); writer.WriteInt16(0); // password offset writer.WriteInt16(0); } else { writer.WriteInt16(offset); // userName offset writer.WriteInt16(userName.Length); offset += userName.Length * 2; // the encrypted password is a byte array - so length computations different than strings writer.WriteInt16(offset); // password offset writer.WriteInt16(encryptedPassword.Length / 2); offset += encryptedPassword.Length; } writer.WriteInt16(offset); // app name offset writer.WriteInt16(rec.ApplicationName.Length); offset += rec.ApplicationName.Length * 2; writer.WriteInt16(offset); // server name offset writer.WriteInt16(rec.ServerName.Length); offset += rec.ServerName.Length * 2; writer.WriteInt16(offset); if (useFeatureExt) { writer.WriteInt16(4); // length of ibFeatgureExtLong (which is a DWORD) offset += 4; } else { writer.WriteInt16(0); // unused (was remote password ?) } writer.WriteInt16(offset); // client interface name offset writer.WriteInt16(clientInterfaceName.Length); offset += clientInterfaceName.Length * 2; writer.WriteInt16(offset); // language name offset writer.WriteInt16(rec.Language.Length); offset += rec.Language.Length * 2; writer.WriteInt16(offset); // database name offset writer.WriteInt16(rec.Database.Length); offset += rec.Database.Length * 2; var nicAddress = GetNetworkPhysicalAddressForTdsLoginOnly(); writer.WriteByteArray(nicAddress); writer.WriteInt16(offset); // ibSSPI offset if (rec.UseSspi) { writer.WriteInt16(rec.ClientToken.Length); offset += rec.ClientToken.Length; } else { writer.WriteInt16(0); } writer.WriteInt16(offset); // DB filename offset writer.WriteInt16(rec.AttachDbFilename.Length); offset += rec.AttachDbFilename.Length * 2; writer.WriteInt16(offset); // reset password offset writer.WriteInt16(0); writer.WriteInt32(0); // reserved for chSSPI // write variable length portion writer.WriteUnicodeString(rec.HostName); // if we are using SSPI, do not send over username/password, since we will use SSPI instead // same behavior as Luxor if (!rec.UseSspi) { writer.WriteUnicodeString(userName); writer.WriteByteArray(encryptedPassword); } writer.WriteUnicodeString(rec.ApplicationName); writer.WriteUnicodeString(rec.ServerName); // write ibFeatureExtLong if (useFeatureExt) { writer.WriteInt32(feOffset); } writer.WriteUnicodeString(clientInterfaceName); writer.WriteUnicodeString(rec.Language); writer.WriteUnicodeString(rec.Database); // send over SSPI data if we are using SSPI if (rec.UseSspi) { writer.WriteByteArray(rec.ClientToken); } writer.WriteUnicodeString(rec.AttachDbFilename); if (useFeatureExt) { if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) { WriteSessionRecoveryFeatureRequest(writer, recoverySessionData); } if ((requestedFeatures & TdsEnums.FeatureExtension.GlobalTransactions) != 0) { WriteGlobalTransactionsFeatureRequest(writer); } writer.WriteByte(0xFF); // terminator } }
public static void SendSspi(this TdsPackageWriter writer, byte[] sspiData) { writer.NewPackage(TdsEnums.MT_SSPI); writer.WriteByteArray(sspiData); writer.SendLastMessage(); }