├── .gitignore ├── LICENSE ├── README.md └── src ├── SampleAuthenticator ├── Properties │ └── AssemblyInfo.cs ├── SampleAuthenticator.cs └── SampleAuthenticator.csproj ├── TDSProtocol ├── BinaryReaderExtensions.cs ├── BinaryWriterExtensions.cs ├── EnumerableOfByteExtensions.cs ├── JetBrains.Annotations.cs ├── Properties │ └── AssemblyInfo.cs ├── SMPInvalidPacketException.cs ├── SMPPacket.cs ├── SSLInvalidPacketException.cs ├── SSLPacket.cs ├── SmpFlags.cs ├── SmpPacketType.cs ├── SslPacketType.cs ├── StringExtensions.cs ├── TDSDoneToken.cs ├── TDSEnvChangeToken.cs ├── TDSErrorToken.cs ├── TDSFeatureExtAckToken.cs ├── TDSInfoToken.cs ├── TDSInvalidMessageException.cs ├── TDSInvalidPacketException.cs ├── TDSLogin7Message.cs ├── TDSLoginAckToken.cs ├── TDSMessage.cs ├── TDSMessageToken.cs ├── TDSMessageType.cs ├── TDSPacket.cs ├── TDSPreLoginMessage.cs ├── TDSProtocol.csproj ├── TDSStatus.cs ├── TDSTabularDataMessage.cs ├── TDSToken.cs ├── TDSTokenStreamMessage.cs ├── TDSTokenType.cs └── Unicode.cs ├── TDSProtocolTests ├── BinaryReaderExtensionsUnitTests.cs ├── BinaryWriterExtensionsUnitTests.cs ├── EnumerableAssert.cs ├── Properties │ └── AssemblyInfo.cs ├── TDSLogin7MessageTests.cs ├── TDSPreLoginMessageTests.cs └── TDSProtocolTests.csproj ├── TDSProxy.Authentication ├── AuthenticationResult.cs ├── IAuthenticator.cs ├── JetBrains.Annotations.cs ├── Properties │ └── AssemblyInfo.cs └── TDSProxy.Authentication.csproj ├── TDSProxy.sln ├── TDSProxy.sln.DotSettings ├── TDSProxy ├── App.config ├── Configuration.xsd ├── Configuration │ ├── AuthenticatorCollection.cs │ ├── AuthenticatorElement.cs │ ├── IPAddressConverter.cs │ ├── ListenerCollection.cs │ ├── ListenerElement.cs │ └── TdsProxySection.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TDSConnection.cs ├── TDSListener.cs ├── TDSProxy.csproj ├── TDSProxyService.Designer.cs ├── TDSProxyService.cs ├── TDSProxyService.resx └── log4net.config └── TestConnection ├── App.config ├── Program.cs ├── Properties └── AssemblyInfo.cs └── TestConnection.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]og/ 2 | 3 | # Build results 4 | [Dd]ebug/ 5 | [Dd]ebugPublic/ 6 | [Rr]elease/ 7 | [Rr]eleases/ 8 | [Xx]64/ 9 | [Xx]86/ 10 | [Bb]uild/ 11 | bld/ 12 | [Bb]in/ 13 | [Oo]bj/ 14 | 15 | *.pdb 16 | 17 | 18 | ### 19 | ### 20 | !_Externals/** 21 | !_ThirdParty/** 22 | 23 | 24 | ## Ignore Visual Studio temporary files, build results, and 25 | ## files generated by popular Visual Studio add-ons. 26 | 27 | # User-specific files 28 | *.suo 29 | *.user 30 | *.userosscache 31 | *.sln.docstates 32 | 33 | # User-specific files (MonoDevelop/Xamarin Studio) 34 | *.userprefs 35 | 36 | # Visual Studio 2015 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUNIT 46 | *.VisualState.xml 47 | TestResult.xml 48 | 49 | # Build Results of an ATL Project 50 | [Dd]ebugPS/ 51 | [Rr]eleasePS/ 52 | dlldata.c 53 | 54 | # DNX 55 | project.lock.json 56 | artifacts/ 57 | 58 | *_i.c 59 | *_p.c 60 | *_i.h 61 | *.ilk 62 | *.meta 63 | *.obj 64 | *.pch 65 | *.pgc 66 | *.pgd 67 | *.rsp 68 | *.sbr 69 | *.tlb 70 | *.tli 71 | *.tlh 72 | *.tmp 73 | *.tmp_proj 74 | *.log 75 | *.vspscc 76 | *.vssscc 77 | .builds 78 | *.pidb 79 | *.svclog 80 | *.scc 81 | 82 | # Chutzpah Test files 83 | _Chutzpah* 84 | 85 | # Visual C++ cache files 86 | ipch/ 87 | *.aps 88 | *.ncb 89 | *.opendb 90 | *.opensdf 91 | *.sdf 92 | *.cachefile 93 | *.VC.db 94 | 95 | # Visual Studio profiler 96 | *.psess 97 | *.vsp 98 | *.vspx 99 | *.sap 100 | 101 | # TFS 2012 Local Workspace 102 | $tf/ 103 | 104 | # Guidance Automation Toolkit 105 | *.gpState 106 | 107 | # ReSharper is a .NET coding add-in 108 | _ReSharper*/ 109 | *.[Rr]e[Ss]harper 110 | *.DotSettings.user 111 | 112 | # JustCode is a .NET coding add-in 113 | .JustCode 114 | 115 | # TeamCity is a build add-in 116 | _TeamCity* 117 | 118 | # DotCover is a Code Coverage Tool 119 | *.dotCover 120 | 121 | # NCrunch 122 | _NCrunch_* 123 | .*crunch*.local.xml 124 | nCrunchTemp_* 125 | 126 | # MightyMoose 127 | *.mm.* 128 | AutoTest.Net/ 129 | 130 | # Web workbench (sass) 131 | .sass-cache/ 132 | 133 | # Installshield output folder 134 | [Ee]xpress/ 135 | 136 | # DocProject is a documentation generator add-in 137 | DocProject/buildhelp/ 138 | DocProject/Help/*.HxT 139 | DocProject/Help/*.HxC 140 | DocProject/Help/*.hhc 141 | DocProject/Help/*.hhk 142 | DocProject/Help/*.hhp 143 | DocProject/Help/Html2 144 | DocProject/Help/html 145 | 146 | # Click-Once directory 147 | publish/ 148 | 149 | # Publish Web Output 150 | *.[Pp]ublish.xml 151 | *.azurePubxml 152 | 153 | # TODO: Un-comment the next line if you do not want to checkin 154 | # your web deploy settings because they may include unencrypted 155 | # passwords 156 | #*.pubxml 157 | *.publishproj 158 | 159 | # NuGet Packages 160 | *.nupkg 161 | # The packages folder can be ignored because of Package Restore 162 | **/packages/* 163 | # except build/, which is used as an MSBuild target. 164 | !**/packages/build/ 165 | # Uncomment if necessary however generally it will be regenerated when needed 166 | #!**/packages/repositories.config 167 | # NuGet v3's project.json files produces more ignoreable files 168 | *.nuget.props 169 | *.nuget.targets 170 | 171 | # Microsoft Azure Build Output 172 | csx/ 173 | *.build.csdef 174 | 175 | # Microsoft Azure Emulator 176 | ecf/ 177 | rcf/ 178 | 179 | # Microsoft Azure ApplicationInsights config file 180 | ApplicationInsights.config 181 | 182 | # Windows Store app package directory 183 | AppPackages/ 184 | BundleArtifacts/ 185 | 186 | # Visual Studio cache files 187 | # files ending in .cache can be ignored 188 | *.[Cc]ache 189 | # but keep track of directories ending in .cache 190 | !*.[Cc]ache/ 191 | 192 | # Others 193 | ClientBin/ 194 | [Ss]tyle[Cc]op.* 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.pfx 200 | *.publishsettings 201 | node_modules/ 202 | orleans.codegen.cs 203 | 204 | # RIA/Silverlight projects 205 | Generated_Code/ 206 | 207 | # Backup & report files from converting an old project file 208 | # to a newer Visual Studio version. Backup files are not needed, 209 | # because we have git ;-) 210 | _UpgradeReport_Files/ 211 | Backup*/ 212 | UpgradeLog*.XML 213 | UpgradeLog*.htm 214 | 215 | # SQL Server files 216 | *.mdf 217 | *.ldf 218 | 219 | # Business Intelligence projects 220 | *.rdl.data 221 | *.bim.layout 222 | *.bim_*.settings 223 | 224 | # Microsoft Fakes 225 | FakesAssemblies/ 226 | 227 | # GhostDoc plugin setting file 228 | *.GhostDoc.xml 229 | 230 | # Node.js Tools for Visual Studio 231 | .ntvs_analysis.dat 232 | 233 | # Visual Studio 6 build log 234 | *.plg 235 | 236 | # Visual Studio 6 workspace options file 237 | *.opt 238 | 239 | # Visual Studio LightSwitch build output 240 | **/*.HTMLClient/GeneratedArtifacts 241 | **/*.DesktopClient/GeneratedArtifacts 242 | **/*.DesktopClient/ModelManifest.xml 243 | **/*.Server/GeneratedArtifacts 244 | **/*.Server/ModelManifest.xml 245 | _Pvt_Extensions 246 | 247 | # LightSwitch generated files 248 | GeneratedArtifacts/ 249 | ModelManifest.xml 250 | 251 | # Paket dependency manager 252 | .paket/paket.exe 253 | 254 | # FAKE - F# Make 255 | .fake/ 256 | 257 | # GitEye / JGit .project 258 | /.project 259 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tech Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TDSProxy 2 | 3 | TDSProxy is a proxy server for the MS SQL Server TDS Protocol. -------------------------------------------------------------------------------- /src/SampleAuthenticator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("SampleAuthenticator")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Tech Software LLC")] 11 | [assembly: AssemblyProduct("SampleAuthenticator")] 12 | [assembly: AssemblyCopyright("Copyright © 2014-2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("1608c147-156a-4eb8-8309-1e3a3dbf228e")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/SampleAuthenticator/SampleAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.ComponentModel.Composition; 4 | 5 | using TDSProxy.Authentication; 6 | 7 | namespace SampleAuthenticator 8 | { 9 | [Export(typeof(IAuthenticator))] 10 | public class SampleAuthenticator : IAuthenticator 11 | { 12 | public AuthenticationResult Authenticate(IPAddress clientIp, string username, string password, string database) 13 | { 14 | return new AuthenticationResult 15 | { 16 | // ReSharper disable once StringLiteralTypo 17 | AllowConnection = !string.Equals(username, "baduser", StringComparison.OrdinalIgnoreCase), 18 | ConnectToDatabase = database, 19 | ConnectAsUser = username, 20 | ConnectUsingPassword = password, 21 | DisplayUsername = username 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SampleAuthenticator/SampleAuthenticator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {47F41C77-1B67-4CB9-B217-27F1A882A857} 8 | Library 9 | Properties 10 | SampleAuthenticator 11 | SampleAuthenticator 12 | v4.8 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | ..\TDSProxy\bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | ..\TDSProxy\bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {87a5b526-ec83-4237-9a08-7aa977da8ef6} 50 | TDSProxy.Authentication 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /src/TDSProtocol/BinaryReaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using JetBrains.Annotations; 4 | 5 | namespace TDSProtocol 6 | { 7 | [PublicAPI] 8 | public static class BinaryReaderExtensions 9 | { 10 | public static short ReadBigEndianInt16(this BinaryReader reader) 11 | { 12 | var buffer = reader.ReadBytes(2); 13 | return (short)((buffer[0] << 8) | buffer[1]); 14 | } 15 | 16 | public static ushort ReadBigEndianUInt16(this BinaryReader reader) 17 | { 18 | var buffer = reader.ReadBytes(2); 19 | return (ushort)((buffer[0] << 8) | buffer[1]); 20 | } 21 | 22 | public static int ReadBigEndianInt32(this BinaryReader reader) 23 | { 24 | var buffer = reader.ReadBytes(4); 25 | return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; 26 | } 27 | 28 | public static uint ReadBigEndianUInt32(this BinaryReader reader) 29 | { 30 | var buffer = reader.ReadBytes(4); 31 | return (uint)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]); 32 | } 33 | 34 | public static string ReadUnicode(this BinaryReader reader, int charsToRead) 35 | { 36 | return new string(Unicode.Instance.GetChars(reader.ReadBytes(charsToRead << 1))); 37 | } 38 | 39 | public static string ReadUnicodeAt(this BinaryReader reader, int position, int charsToRead) 40 | { 41 | reader.BaseStream.Position = position; 42 | return reader.ReadUnicode(charsToRead); 43 | } 44 | 45 | public static string ReadObfuscatedPassword(this BinaryReader reader, int position, int charsToRead) 46 | { 47 | byte[] passwordBytes = reader.ReadBytes(charsToRead << 1); 48 | for (int idx = 0; idx < passwordBytes.Length; idx++) 49 | { 50 | byte b = (byte)(passwordBytes[idx] ^ 0xA5); 51 | passwordBytes[idx] = (byte)((b >> 4) | (b << 4)); 52 | } 53 | return new string(Unicode.Instance.GetChars(passwordBytes)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/TDSProtocol/BinaryWriterExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace TDSProtocol 5 | { 6 | public static class BinaryWriterExtensions 7 | { 8 | public static void WriteBigEndian(this BinaryWriter writer, short value) 9 | { 10 | writer.Write(new[] { (byte)(value >> 8), (byte)value }); 11 | } 12 | 13 | public static void WriteBigEndian(this BinaryWriter writer, ushort value) 14 | { 15 | writer.Write(new[] { (byte)(value >> 8), (byte)value }); 16 | } 17 | 18 | public static void WriteBigEndian(this BinaryWriter writer, int value) 19 | { 20 | writer.Write(new[] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value }); 21 | } 22 | 23 | public static void WriteBigEndian(this BinaryWriter writer, uint value) 24 | { 25 | writer.Write(new[] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value }); 26 | } 27 | 28 | public static void WriteUnicodeBytes(this BinaryWriter writer, string value) 29 | { 30 | if (null != value) 31 | writer.Write(Unicode.Instance.GetBytes(value)); 32 | } 33 | 34 | public static void WriteBVarchar(this BinaryWriter writer, string value) 35 | { 36 | if (value?.Length > byte.MaxValue) 37 | throw new ArgumentException($"B_VARCHAR strings must not be longer than {byte.MaxValue} characters", nameof(value)); 38 | 39 | if (null == value) 40 | writer.Write((byte)0); 41 | else 42 | { 43 | writer.Write((byte)value.Length); 44 | writer.WriteUnicodeBytes(value); 45 | } 46 | } 47 | 48 | public static void WriteUsVarchar(this BinaryWriter writer, string value) 49 | { 50 | if (value?.Length > ushort.MaxValue) 51 | throw new ArgumentException($"US_VARCHAR strings must not be longer than {ushort.MaxValue} characters", nameof(value)); 52 | 53 | if (null == value) 54 | writer.WriteBigEndian((ushort)0); 55 | else 56 | { 57 | writer.Write((ushort)value.Length); 58 | writer.WriteUnicodeBytes(value); 59 | } 60 | } 61 | 62 | public static void WriteObfuscatedPassword(this BinaryWriter writer, string password) 63 | { 64 | if (null != password) 65 | { 66 | var passwordBytes = Unicode.Instance.GetBytes(password); 67 | for (var idx = 0; idx < passwordBytes.Length; idx++) 68 | { 69 | byte b = passwordBytes[idx]; 70 | passwordBytes[idx] = (byte)(((b >> 4) | (b << 4)) ^ 0xA5); 71 | } 72 | writer.Write(passwordBytes); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/TDSProtocol/EnumerableOfByteExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TDSProtocol 6 | { 7 | public static class EnumerableOfByteExtensions 8 | { 9 | public static string FormatAsHex(this IEnumerable bytes, string prefix = null) 10 | { 11 | if (null == bytes) 12 | return null; 13 | 14 | using (var enumerator = bytes.GetEnumerator()) 15 | { 16 | if (!enumerator.MoveNext()) 17 | return "(no data)"; 18 | 19 | StringBuilder sb = new StringBuilder(); 20 | bool readByte = true; 21 | uint i = 0; 22 | while (readByte) 23 | { 24 | if (i > 0) sb.AppendLine(); 25 | sb.Append(prefix).Append(i.ToString("X8")).Append(":"); 26 | 27 | for (var j = 0; readByte && j < 16; j++, readByte = enumerator.MoveNext()) 28 | { 29 | sb.Append(" ").Append(enumerator.Current.ToString("X2")); 30 | } 31 | 32 | i += 16; 33 | } 34 | 35 | return sb.ToString(); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/TDSProtocol/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TDSProtocol")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TDSProtocol")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2619e04a-0058-45c2-81cc-a05200103595")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | 38 | [assembly: InternalsVisibleTo("TDSProtocolTests")] 39 | -------------------------------------------------------------------------------- /src/TDSProtocol/SMPInvalidPacketException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public class SMPInvalidPacketException : Exception 8 | { 9 | public byte[] PacketData { get; } 10 | 11 | public SMPInvalidPacketException(string message, byte[] packetData, int packetDataLength) 12 | : base(message) 13 | { 14 | PacketData = new byte[packetDataLength]; 15 | if (packetDataLength > 0) 16 | Buffer.BlockCopy(packetData, 0, PacketData, 0, packetDataLength); 17 | } 18 | 19 | public string PacketDataFormatted => PacketData.FormatAsHex(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/TDSProtocol/SMPPacket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using JetBrains.Annotations; 7 | 8 | namespace TDSProtocol 9 | { 10 | [PublicAPI] 11 | public class SMPPacket 12 | { 13 | #region Log4Net 14 | 15 | static readonly log4net.ILog log = 16 | log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 17 | 18 | #endregion 19 | 20 | protected const int HeaderLength = 16; 21 | 22 | private static readonly HashSet KnownPacketTypes = 23 | new HashSet(Enum.GetValues(typeof(SmpPacketType)).Cast()); 24 | 25 | public static bool IsSMPPacketType(byte packetTypeByte) 26 | { 27 | return KnownPacketTypes.Contains((SmpPacketType)packetTypeByte); 28 | } 29 | 30 | internal readonly byte[] Data; 31 | 32 | protected SMPPacket(byte[] data) 33 | { 34 | if (null == data) 35 | throw new ArgumentNullException(nameof(data)); 36 | if (data.Length < HeaderLength) 37 | throw new ArgumentOutOfRangeException(nameof(data), "Data length insufficient to contain SMP header."); 38 | if (!IsSMPPacketType(data[0])) 39 | throw new ArgumentOutOfRangeException(nameof(data), 40 | "Not an SMP packet type: " + data[0].ToString("X2")); 41 | if (data.Length != GetPacketLength(data)) 42 | throw new ArgumentOutOfRangeException(nameof(data), "Data length does not match length in header."); 43 | Data = data; 44 | } 45 | 46 | private static uint GetPacketLength(byte[] data) 47 | { 48 | return unchecked ((uint)((data[7] << 24) | (data[6] << 16) | (data[5] << 8) | (data[4]))); 49 | } 50 | 51 | public SmpPacketType PacketType => (SmpPacketType)Data[0]; 52 | 53 | public SmpFlags Flags => (SmpFlags)Data[1]; 54 | 55 | public ushort SID => unchecked ((ushort)((Data[3] << 8) | Data[2])); 56 | 57 | public uint Length => GetPacketLength(Data); 58 | 59 | public uint SeqNum => unchecked ((uint)((Data[11] << 24) | (Data[10] << 16) | (Data[9] << 8) | Data[8])); 60 | 61 | public uint Window => unchecked ((uint)((Data[15] << 24) | (Data[14] << 16) | (Data[13] << 8) | Data[12])); 62 | 63 | public byte[] Payload 64 | { 65 | get 66 | { 67 | var pl = new byte[Length - HeaderLength]; 68 | Buffer.BlockCopy(Data, HeaderLength, pl, 0, pl.Length); 69 | return pl; 70 | } 71 | } 72 | 73 | public static SMPPacket ReadFromStream(Stream stream, bool readPacketType, SmpPacketType? packetType) 74 | { 75 | return ReadFromStreamAsync(stream, readPacketType, packetType).Result; 76 | } 77 | 78 | public static async Task ReadFromStreamAsync(Stream stream, 79 | bool readPacketType, 80 | SmpPacketType? packetType) 81 | { 82 | if ((!readPacketType) && (!packetType.HasValue)) 83 | throw new ArgumentException("packetType must be specified if readPacketType is false."); 84 | 85 | var header = new byte[HeaderLength]; 86 | int packetBytesRead; 87 | 88 | if (readPacketType) 89 | { 90 | packetBytesRead = await stream.ReadAsync(header, 0, 1).ConfigureAwait(false); 91 | if (packetBytesRead == 0) 92 | return null; 93 | 94 | if (!IsSMPPacketType(header[0])) 95 | throw new SMPInvalidPacketException( 96 | "Packet type " + header[0].ToString("X2") + " is not a recognized MC-SMP packet type.", 97 | header, 98 | 1); 99 | } 100 | else 101 | { 102 | header[0] = (byte)packetType.Value; 103 | packetBytesRead = 1; 104 | } 105 | 106 | if (packetType.HasValue && (byte)packetType.Value != header[0]) 107 | throw new SMPInvalidPacketException( 108 | "Packet type " + 109 | (SmpPacketType)header[0] + 110 | " does not match specified value of " + 111 | packetType.Value + 112 | ".", 113 | header, 114 | 1); 115 | 116 | while (packetBytesRead < HeaderLength) 117 | { 118 | var thisRead = await stream.ReadAsync(header, packetBytesRead, HeaderLength - packetBytesRead) 119 | .ConfigureAwait(false); 120 | if (thisRead == 0) 121 | throw new SSLInvalidPacketException("Stream was closed mid-header", header, packetBytesRead); 122 | packetBytesRead += thisRead; 123 | } 124 | 125 | var packetLength = (int)GetPacketLength(header); 126 | var data = new byte[packetLength]; 127 | Buffer.BlockCopy(header, 0, data, 0, HeaderLength); 128 | 129 | while (packetBytesRead < packetLength) 130 | { 131 | var thisRead = await stream.ReadAsync(data, packetBytesRead, packetLength - packetBytesRead) 132 | .ConfigureAwait(false); 133 | if (thisRead == 0) 134 | throw new SSLInvalidPacketException("Stream was closed mid-payload", 135 | data, 136 | HeaderLength + packetBytesRead); 137 | packetBytesRead += thisRead; 138 | } 139 | 140 | return new SMPPacket(data); 141 | } 142 | 143 | public void WriteToStream(Stream stream) 144 | { 145 | WriteToStreamAsync(stream).Wait(); 146 | } 147 | 148 | public Task WriteToStreamAsync(Stream stream) 149 | { 150 | return stream.WriteAsync(Data, 0, Data.Length); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/TDSProtocol/SSLInvalidPacketException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public class SSLInvalidPacketException : Exception 8 | { 9 | public byte[] PacketData { get; } 10 | 11 | public SSLInvalidPacketException(string message, byte[] packetData, int packetDataLength) : base(message) 12 | { 13 | PacketData = new byte[packetDataLength]; 14 | if (packetDataLength > 0) 15 | Buffer.BlockCopy(packetData, 0, PacketData, 0, packetDataLength); 16 | } 17 | 18 | public string PacketDataFormatted => PacketData.FormatAsHex(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/TDSProtocol/SSLPacket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.Authentication; 6 | using System.Threading.Tasks; 7 | using JetBrains.Annotations; 8 | 9 | namespace TDSProtocol 10 | { 11 | [PublicAPI] 12 | public class SSLPacket 13 | { 14 | #region Log4Net 15 | static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 16 | #endregion 17 | 18 | protected const int HeaderLength = 5; 19 | 20 | private static readonly Dictionary ProtocolVersionNumbers = new Dictionary 21 | { 22 | { 0x0200, SslProtocols.Ssl2 }, 23 | { 0x0300, SslProtocols.Ssl3 }, 24 | { 0x0301, SslProtocols.Tls }, 25 | { 0x0302, SslProtocols.Tls11 }, 26 | { 0x0303, SslProtocols.Tls12 } 27 | }; 28 | 29 | private static readonly HashSet KnownPacketTypes = 30 | new HashSet(Enum.GetValues(typeof(SslPacketType)).Cast()); 31 | public static bool IsSSLPacketType(byte packetTypeByte) 32 | { 33 | return KnownPacketTypes.Contains((SslPacketType)packetTypeByte); 34 | } 35 | 36 | internal readonly byte[] Data; 37 | 38 | protected SSLPacket(byte[] data) 39 | { 40 | if (null == data) 41 | throw new ArgumentNullException(nameof(data)); 42 | if (data.Length < HeaderLength || data.Length > ushort.MaxValue + HeaderLength) 43 | throw new ArgumentOutOfRangeException(nameof(data), "data must be at least " + HeaderLength + " bytes and no more than " + (ushort.MaxValue + HeaderLength) + " bytes long."); 44 | if (!IsSSLPacketType(data[0])) 45 | throw new ArgumentOutOfRangeException(nameof(data), "Unrecognized SSL packet type " + data[0]); 46 | if (HeaderLength + GetPayloadLength(data) != data.Length) 47 | throw new ArgumentOutOfRangeException(nameof(data), "Payload length in data does not match length of data."); 48 | if (!ProtocolVersionNumbers.ContainsKey((data[1] << 8) | data[2])) 49 | throw new ArgumentOutOfRangeException(nameof(data), "Unrecognized SSL protocol version " + data[1] + "." + data[2] + "."); 50 | Data = data; 51 | } 52 | 53 | public SslPacketType PacketType => (SslPacketType)Data[0]; 54 | 55 | public SslProtocols SslProtocol => ProtocolVersionNumbers[(Data[1] << 8) | Data[2]]; 56 | 57 | public ushort PayloadLength => GetPayloadLength(Data); 58 | 59 | private static ushort GetPayloadLength(byte[] data) 60 | { 61 | return unchecked((ushort)((data[3] << 8) | data[4])); 62 | } 63 | 64 | public byte[] Payload 65 | { 66 | get 67 | { 68 | var pl = new byte[PayloadLength]; 69 | Buffer.BlockCopy(Data, HeaderLength, pl, 0, pl.Length); 70 | return pl; 71 | } 72 | } 73 | 74 | public static async Task ReadFromStreamAsync(Stream stream, bool readPacketType, SslPacketType? packetType = null) 75 | { 76 | if ((!readPacketType) && (!packetType.HasValue)) 77 | throw new ArgumentException("packetType must be specified if readPacketType is false."); 78 | 79 | var header = new byte[HeaderLength]; 80 | int headerBytesRead; 81 | 82 | if (readPacketType) 83 | { 84 | headerBytesRead = await stream.ReadAsync(header, 0, 1).ConfigureAwait(false); 85 | if (headerBytesRead == 0) 86 | return null; 87 | 88 | if (!IsSSLPacketType(header[0])) 89 | throw new SSLInvalidPacketException("Packet type " + header[0].ToString("X2") + " is not a recognized SSL/TLS packet type.", header, 1); 90 | } 91 | else 92 | { 93 | header[0] = (byte)packetType.Value; 94 | headerBytesRead = 1; 95 | } 96 | if (packetType.HasValue && (byte)packetType.Value != header[0]) 97 | throw new SSLInvalidPacketException("Packet type " + (SslPacketType)header[0] + " does not match specified value of " + packetType.Value + ".", header, 1); 98 | 99 | while (headerBytesRead < HeaderLength) 100 | { 101 | var thisRead = await stream.ReadAsync(header, headerBytesRead, HeaderLength - headerBytesRead).ConfigureAwait(false); 102 | if (thisRead == 0) 103 | throw new SSLInvalidPacketException("Stream was closed mid-header", header, headerBytesRead); 104 | headerBytesRead += thisRead; 105 | } 106 | 107 | var payloadLength = GetPayloadLength(header); 108 | var data = new byte[HeaderLength + payloadLength]; 109 | Buffer.BlockCopy(header, 0, data, 0, HeaderLength); 110 | 111 | int payloadBytesRead = 0; 112 | while (payloadBytesRead < payloadLength) 113 | { 114 | var thisRead = await stream.ReadAsync(data, payloadBytesRead + HeaderLength, payloadLength - payloadBytesRead).ConfigureAwait(false); 115 | if (thisRead == 0) 116 | throw new SSLInvalidPacketException("Stream was closed mid-payload", data, HeaderLength + payloadBytesRead); 117 | payloadBytesRead += thisRead; 118 | } 119 | 120 | return new SSLPacket(data); 121 | } 122 | 123 | public Task WriteToStreamAsync(Stream stream) 124 | { 125 | return stream.WriteAsync(Data, 0, Data.Length); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/TDSProtocol/SmpFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public enum SmpFlags : byte 8 | { 9 | Syn = 0x01, 10 | Ack = 0x02, 11 | Fin = 0x04, 12 | Data = 0x08 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/TDSProtocol/SmpPacketType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public enum SmpPacketType : byte 8 | { 9 | SMP = 0x53 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/TDSProtocol/SslPacketType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TDSProtocol 4 | { 5 | public enum SslPacketType : byte 6 | { 7 | ChangeCipherSpec = 20, 8 | Alert = 21, 9 | Handshake = 22, 10 | ApplicationData = 23 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/TDSProtocol/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TDSProtocol 4 | { 5 | public static class StringExtensions 6 | { 7 | public static int UnicodeByteLength(this string theString) 8 | { 9 | if (string.IsNullOrEmpty(theString)) 10 | return 0; 11 | return Unicode.Instance.GetByteCount(theString); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSDoneToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using JetBrains.Annotations; 4 | 5 | namespace TDSProtocol 6 | { 7 | public class TDSDoneToken : TDSToken 8 | { 9 | public TDSDoneToken(TDSTokenStreamMessage owningMessage) : base(owningMessage) 10 | { 11 | } 12 | 13 | public override TDSTokenType TokenId => TDSTokenType.Done; 14 | 15 | private const int LengthBeforeTDS72 = 8; 16 | private const int LengthTDS72AndAfter = 12; 17 | 18 | public int Length => TdsVersion >= 0x72000000 ? LengthTDS72AndAfter : LengthBeforeTDS72; 19 | 20 | #region Status 21 | 22 | [PublicAPI, Flags] 23 | public enum StatusEnum : ushort 24 | { 25 | Final = 0x0000, 26 | More = 0x0001, 27 | Error = 0x0002, 28 | 29 | // ReSharper disable once IdentifierTypo 30 | InXact = 0x0004, 31 | Count = 0x0010, 32 | Attn = 0x0020, 33 | SrvError = 0x0100 34 | } 35 | 36 | private StatusEnum _status; 37 | 38 | public StatusEnum Status 39 | { 40 | get => _status; 41 | set 42 | { 43 | Message.Payload = null; 44 | _status = value; 45 | } 46 | } 47 | 48 | #endregion 49 | 50 | #region CurCmd 51 | 52 | private ushort _curCmd; 53 | 54 | public ushort CurCmd 55 | { 56 | get => _curCmd; 57 | set 58 | { 59 | Message.Payload = null; 60 | _curCmd = value; 61 | } 62 | } 63 | 64 | #endregion 65 | 66 | #region DoneRowCount 67 | 68 | private ulong _doneRowCount; 69 | 70 | public ulong DoneRowCount 71 | { 72 | get => _doneRowCount; 73 | set 74 | { 75 | Message.Payload = null; 76 | _doneRowCount = value; 77 | } 78 | } 79 | 80 | #endregion 81 | 82 | #region WriteToBinaryWriter 83 | 84 | protected override void WriteBodyToBinaryWriter(BinaryWriter bw) 85 | { 86 | bw.Write((ushort)Status); 87 | bw.Write(CurCmd); 88 | if (TdsVersion >= 0x72000000) 89 | bw.Write(DoneRowCount); 90 | else 91 | bw.Write((uint)DoneRowCount); 92 | } 93 | 94 | #endregion 95 | 96 | #region ReadFromBinaryReader 97 | 98 | protected override int ReadFromBinaryReader(BinaryReader br) 99 | { 100 | try 101 | { 102 | _status = (StatusEnum)br.ReadUInt16(); 103 | _curCmd = br.ReadUInt16(); 104 | _doneRowCount = TdsVersion >= 0x72000000 ? br.ReadUInt64() : br.ReadUInt32(); 105 | return Length; 106 | } 107 | catch (EndOfStreamException) 108 | { 109 | throw new TDSInvalidMessageException("Attempted to read longer than payload", 110 | Message.MessageType, 111 | Message.Payload); 112 | } 113 | } 114 | 115 | #endregion 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSEnvChangeToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using JetBrains.Annotations; 4 | 5 | namespace TDSProtocol 6 | { 7 | [PublicAPI] 8 | public class TDSEnvChangeToken : TDSToken 9 | { 10 | public TDSEnvChangeToken(TDSTokenStreamMessage message) : base(message) 11 | { 12 | } 13 | 14 | public override TDSTokenType TokenId => TDSTokenType.EnvChange; 15 | 16 | #region Length 17 | 18 | private ushort? _length; 19 | 20 | public ushort Length => _length ?? (ushort)(1 + _newValue.Length + _oldValue.Length); 21 | 22 | #endregion 23 | 24 | #region Type 25 | 26 | [PublicAPI] 27 | public enum EnvChangeType : byte 28 | { 29 | Database = 1, 30 | Language = 2, 31 | CharacterSet = 3, 32 | PacketSize = 4, 33 | UnicodeSortingLocalId = 5, 34 | UnicodeSortingComparisonFlags = 6, 35 | SqlCollation = 7, 36 | BeginTransaction = 8, 37 | CommitTransaction = 9, 38 | RollbackTransaction = 10, 39 | EnlistDtcTransaction = 11, 40 | DefectTransaction = 12, 41 | DatabaseMirroringPartner = 13, 42 | PromoteTransaction = 15, 43 | TransactionManagerAddress = 16, 44 | TransactionEnded = 17, 45 | ResetConnectionCompletionAck = 18, 46 | NameOfUserInstanceStarted = 19, 47 | RoutingInformation = 20, 48 | } 49 | 50 | private EnvChangeType _type; 51 | 52 | public EnvChangeType Type 53 | { 54 | get => _type; 55 | set 56 | { 57 | Message.Payload = null; 58 | _length = null; 59 | _type = value; 60 | 61 | switch (value) 62 | { 63 | case EnvChangeType.UnicodeSortingLocalId: 64 | case EnvChangeType.UnicodeSortingComparisonFlags: 65 | case EnvChangeType.BeginTransaction: 66 | case EnvChangeType.DefectTransaction: 67 | case EnvChangeType.DatabaseMirroringPartner: 68 | case EnvChangeType.PromoteTransaction: 69 | case EnvChangeType.TransactionManagerAddress: 70 | case EnvChangeType.NameOfUserInstanceStarted: 71 | _oldValue = new byte[] {0}; 72 | break; 73 | 74 | case EnvChangeType.RoutingInformation: 75 | _oldValue = new byte[] {0, 0}; 76 | break; 77 | 78 | case EnvChangeType.CommitTransaction: 79 | case EnvChangeType.RollbackTransaction: 80 | case EnvChangeType.EnlistDtcTransaction: 81 | case EnvChangeType.TransactionEnded: 82 | _newValue = new byte[] {0}; 83 | break; 84 | 85 | case EnvChangeType.ResetConnectionCompletionAck: 86 | _oldValue = new byte[] {0}; 87 | _newValue = new byte[] {0}; 88 | break; 89 | 90 | } 91 | } 92 | } 93 | 94 | #endregion 95 | 96 | #region OldValue 97 | 98 | private byte[] _oldValue = {0}; 99 | 100 | public byte[] OldValue 101 | { 102 | get => _oldValue; 103 | set 104 | { 105 | if (value is null) throw new ArgumentNullException(nameof(OldValue)); 106 | 107 | switch (_type) 108 | { 109 | case EnvChangeType.UnicodeSortingLocalId: 110 | case EnvChangeType.UnicodeSortingComparisonFlags: 111 | case EnvChangeType.BeginTransaction: 112 | case EnvChangeType.DefectTransaction: 113 | case EnvChangeType.DatabaseMirroringPartner: 114 | case EnvChangeType.PromoteTransaction: 115 | case EnvChangeType.TransactionManagerAddress: 116 | case EnvChangeType.ResetConnectionCompletionAck: 117 | case EnvChangeType.NameOfUserInstanceStarted: 118 | case EnvChangeType.RoutingInformation: 119 | throw new InvalidOperationException($"EnvChange of type {_type} has null/fixed OldValue"); 120 | } 121 | 122 | Message.Payload = null; 123 | _length = null; 124 | _oldValue = value; 125 | } 126 | } 127 | 128 | #endregion 129 | 130 | #region NewValue 131 | 132 | private byte[] _newValue = {0}; 133 | 134 | public byte[] NewValue 135 | { 136 | get => _newValue; 137 | set 138 | { 139 | if (value is null) throw new ArgumentNullException(nameof(NewValue)); 140 | 141 | switch (_type) 142 | { 143 | case EnvChangeType.CommitTransaction: 144 | case EnvChangeType.RollbackTransaction: 145 | case EnvChangeType.EnlistDtcTransaction: 146 | case EnvChangeType.TransactionEnded: 147 | case EnvChangeType.ResetConnectionCompletionAck: 148 | throw new InvalidOperationException($"EnvChange of type {_type} has null/fixed NewValue"); 149 | } 150 | 151 | Message.Payload = null; 152 | _length = null; 153 | _newValue = value; 154 | } 155 | } 156 | 157 | #endregion 158 | 159 | #region WriteBodyToBinaryWriter 160 | 161 | protected override void WriteBodyToBinaryWriter(BinaryWriter bw) 162 | { 163 | bw.Write(Length); 164 | bw.Write((byte)Type); 165 | bw.Write(_oldValue); 166 | bw.Write(_newValue); 167 | } 168 | 169 | #endregion 170 | 171 | #region ReadFromBinaryReader 172 | 173 | protected override int ReadFromBinaryReader(BinaryReader br) 174 | { 175 | try 176 | { 177 | _length = br.ReadUInt16(); 178 | _type = (EnvChangeType)br.ReadByte(); 179 | } 180 | catch (EndOfStreamException) 181 | { 182 | throw new TDSInvalidMessageException("Attempted to read longer than payload", 183 | Message.MessageType, 184 | Message.Payload); 185 | } 186 | 187 | switch (_type) 188 | { 189 | case EnvChangeType.Database: 190 | case EnvChangeType.Language: 191 | case EnvChangeType.CharacterSet: 192 | case EnvChangeType.PacketSize: 193 | case EnvChangeType.UnicodeSortingLocalId: 194 | case EnvChangeType.UnicodeSortingComparisonFlags: 195 | case EnvChangeType.DatabaseMirroringPartner: 196 | case EnvChangeType.NameOfUserInstanceStarted: 197 | // B_VARCHAR 198 | _newValue = ReadData(br, (uint)(_length.GetValueOrDefault() - 1), true, 1, true); 199 | _oldValue = ReadData(br, (uint)(_length.GetValueOrDefault() - (1 + _newValue.Length)), true, 1, true); 200 | break; 201 | 202 | case EnvChangeType.SqlCollation: 203 | case EnvChangeType.BeginTransaction: 204 | case EnvChangeType.CommitTransaction: 205 | case EnvChangeType.RollbackTransaction: 206 | case EnvChangeType.EnlistDtcTransaction: 207 | case EnvChangeType.DefectTransaction: 208 | case EnvChangeType.TransactionManagerAddress: 209 | case EnvChangeType.TransactionEnded: 210 | case EnvChangeType.ResetConnectionCompletionAck: 211 | // B_VARBYTE 212 | _newValue = ReadData(br, (uint)(_length.GetValueOrDefault() - 1), true, 1, false); 213 | _oldValue = ReadData(br, (uint)(_length.GetValueOrDefault() - (1 + _newValue.Length)), true, 1, false); 214 | break; 215 | 216 | case EnvChangeType.RoutingInformation: 217 | // US_VARBYTE 218 | _newValue = ReadData(br, (uint)(_length.GetValueOrDefault() - 1), true, 2, false); 219 | _oldValue = ReadData(br, (uint)(_length.GetValueOrDefault() - (1 + _newValue.Length)), true, 2, false); 220 | break; 221 | 222 | case EnvChangeType.PromoteTransaction: 223 | // L_VARBYTE 224 | _newValue = ReadData(br, (uint)(_length.GetValueOrDefault() - 1), true, 4, false); 225 | _oldValue = ReadData(br, (uint)(_length.GetValueOrDefault() - (1 + _newValue.Length)), true, 4, false); 226 | break; 227 | 228 | default: 229 | throw new TDSInvalidMessageException($"Unknown EnvChange type {_type}", 230 | Message.MessageType, 231 | Message.Payload); 232 | } 233 | 234 | return _length.GetValueOrDefault() + 2; 235 | } 236 | 237 | #endregion 238 | 239 | #region Helper Methods 240 | 241 | private byte[] ReadData(BinaryReader br, uint lengthRemaining, bool isNew, int lengthOfLength, bool isVarchar) 242 | { 243 | if (lengthRemaining < lengthOfLength) 244 | throw ValueOverflowsToken(isNew); 245 | 246 | int length; 247 | switch (lengthOfLength) 248 | { 249 | case 1: 250 | length = br.ReadByte(); 251 | break; 252 | case 2: 253 | length = br.ReadUInt16(); 254 | break; 255 | case 4: 256 | length = br.ReadInt32(); 257 | break; 258 | default: 259 | throw new ArgumentOutOfRangeException(nameof(lengthOfLength), lengthOfLength, "Must be 1, 2, or 4."); 260 | } 261 | 262 | var byteLen = isVarchar ? length << 1 : length; 263 | 264 | if (byteLen + lengthOfLength > lengthRemaining) 265 | throw ValueOverflowsToken(isNew); 266 | 267 | var data = new byte[lengthOfLength + byteLen]; 268 | data[0] = (byte)length; 269 | if (lengthOfLength > 1) 270 | { 271 | data[1] = (byte)(length >> 8); 272 | if (lengthOfLength > 2) 273 | { 274 | data[2] = (byte)(length >> 16); 275 | data[3] = (byte)(length >> 24); 276 | } 277 | } 278 | 279 | if (byteLen > 0) 280 | br.Read(data, lengthOfLength, byteLen); 281 | 282 | return data; 283 | } 284 | 285 | private TDSInvalidMessageException ValueOverflowsToken(bool isNew) => 286 | new TDSInvalidMessageException( 287 | $"{(isNew ? "NewValue" : "OldValue")} of {_type} Environment Change Token overflows token length", 288 | Message.MessageType, 289 | Message.Payload); 290 | 291 | #endregion 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSErrorToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public class TDSErrorToken : TDSMessageToken 8 | { 9 | public TDSErrorToken(TDSTokenStreamMessage owningMessage) : base(owningMessage) { } 10 | 11 | public override TDSTokenType TokenId => TDSTokenType.Error; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSFeatureExtAckToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace TDSProtocol 7 | { 8 | public class TDSFeatureExtAckToken : TDSToken 9 | { 10 | public TDSFeatureExtAckToken(TDSTokenStreamMessage message) : base(message) 11 | { 12 | } 13 | 14 | public override TDSTokenType TokenId => TDSTokenType.FeatureExtAck; 15 | 16 | #region FeatureAckOpts 17 | 18 | public class FeatureAckOpt 19 | { 20 | public TDSLogin7Message.FeatureId FeatureId { get; set; } 21 | public byte[] FeatureAckData { get; set; } 22 | } 23 | 24 | private IEnumerable _featureAckOpts = Enumerable.Empty(); 25 | 26 | public IEnumerable FeatureAckOpts 27 | { 28 | get => _featureAckOpts; 29 | set 30 | { 31 | Message.Payload = null; 32 | _featureAckOpts = value ?? Enumerable.Empty(); 33 | } 34 | } 35 | 36 | #endregion 37 | 38 | #region WriteBodyToBinaryWriter 39 | 40 | protected override void WriteBodyToBinaryWriter(BinaryWriter bw) 41 | { 42 | foreach (var fo in FeatureAckOpts) 43 | { 44 | bw.Write((byte)fo.FeatureId); 45 | if (fo.FeatureId == TDSLogin7Message.FeatureId.Terminator) 46 | return; 47 | if (fo.FeatureAckData?.Length > 0) 48 | { 49 | bw.Write(fo.FeatureAckData.Length); 50 | bw.Write(fo.FeatureAckData); 51 | } 52 | else 53 | { 54 | bw.Write(0); 55 | } 56 | } 57 | 58 | bw.Write((byte)TDSLogin7Message.FeatureId.Terminator); 59 | } 60 | 61 | #endregion 62 | 63 | #region ReadFromBinaryReader 64 | 65 | protected override int ReadFromBinaryReader(BinaryReader br) 66 | { 67 | var opts = new List(); 68 | 69 | int length = 0; 70 | 71 | while (true) 72 | { 73 | var fo = new FeatureAckOpt(); 74 | int len; 75 | 76 | try 77 | { 78 | fo.FeatureId = (TDSLogin7Message.FeatureId)br.ReadByte(); 79 | if (fo.FeatureId == TDSLogin7Message.FeatureId.Terminator) 80 | { 81 | opts.Add(fo); 82 | _featureAckOpts = opts; 83 | return length + 1; 84 | } 85 | 86 | len = br.ReadInt32(); 87 | fo.FeatureAckData = br.ReadBytes(len); 88 | } 89 | catch (EndOfStreamException) 90 | { 91 | throw new TDSInvalidMessageException("Attempted to read longer than payload", 92 | Message.MessageType, 93 | Message.Payload); 94 | } 95 | 96 | opts.Add(fo); 97 | length += len + 3; 98 | } 99 | } 100 | 101 | #endregion 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSInfoToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TDSProtocol 4 | { 5 | public class TDSInfoToken : TDSMessageToken 6 | { 7 | public TDSInfoToken(TDSTokenStreamMessage message) : base(message) { } 8 | 9 | public override TDSTokenType TokenId => TDSTokenType.Info; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSInvalidMessageException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public class TDSInvalidMessageException : Exception 8 | { 9 | public TDSMessageType MessageType { get; } 10 | 11 | public byte[] Payload { get; } 12 | 13 | public TDSInvalidMessageException(string message, 14 | TDSMessageType type, 15 | byte[] payload, 16 | Exception innerException = null) : base(message, innerException) 17 | { 18 | MessageType = type; 19 | Payload = new byte[payload.Length]; 20 | Buffer.BlockCopy(payload, 0, Payload, 0, payload.Length); 21 | } 22 | 23 | 24 | public string PayloadFormatted => Payload.FormatAsHex(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSInvalidPacketException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public class TDSInvalidPacketException : Exception 8 | { 9 | public byte[] PacketData { get; } 10 | 11 | public TDSInvalidPacketException(string message, byte[] packetData, int packetDataLength) : base(message) 12 | { 13 | PacketData = new byte[packetDataLength]; 14 | if (packetDataLength > 0) 15 | Buffer.BlockCopy(packetData, 0, PacketData, 0, packetDataLength); 16 | } 17 | 18 | public string PacketDataFormatted => PacketData.FormatAsHex(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSLoginAckToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TDSProtocol 9 | { 10 | public class TDSLoginAckToken : TDSToken 11 | { 12 | protected TDSLoginAckToken(TDSTokenStreamMessage owningMessage) : base(owningMessage) 13 | { 14 | } 15 | 16 | public override TDSTokenType TokenId => TDSTokenType.LoginAck; 17 | 18 | private const ushort FixedLength = 10; 19 | 20 | #region Length 21 | 22 | public ushort Length => (ushort)(FixedLength + (ProgName?.Length).GetValueOrDefault()); 23 | 24 | #endregion 25 | 26 | #region Interface 27 | 28 | private byte _interface; 29 | 30 | public byte Interface 31 | { 32 | get => _interface; 33 | set 34 | { 35 | Message.Payload = null; 36 | _interface = value; 37 | } 38 | } 39 | 40 | #endregion 41 | 42 | #region TDSVersion 43 | 44 | private uint _tdsVersion; 45 | 46 | public uint TDSVersion 47 | { 48 | get => _tdsVersion; 49 | set 50 | { 51 | Message.Payload = null; 52 | _tdsVersion = value; 53 | } 54 | } 55 | 56 | #endregion 57 | 58 | #region ProgName 59 | 60 | // ReSharper disable IdentifierTypo 61 | 62 | private string _progName; 63 | 64 | public string ProgName 65 | { 66 | get => _progName; 67 | set 68 | { 69 | Message.Payload = null; 70 | _progName = value; 71 | } 72 | } 73 | 74 | // ReSharper restore IdentifierTypo 75 | 76 | #endregion 77 | 78 | #region ProgVersion 79 | 80 | // ReSharper disable IdentifierTypo 81 | 82 | public struct ProgVersionStruct 83 | { 84 | public byte MajorVer { get; set; } 85 | public byte MinorVer { get; set; } 86 | public byte BuildNumHi { get; set; } 87 | public byte BuildNumLo { get; set; } 88 | } 89 | 90 | private ProgVersionStruct _progVersion; 91 | 92 | public ProgVersionStruct ProgVersion 93 | { 94 | get => _progVersion; 95 | set 96 | { 97 | Message.Payload = null; 98 | _progVersion = value; 99 | } 100 | } 101 | 102 | // ReSharper restore IdentifierTypo 103 | 104 | #endregion 105 | 106 | #region WriteBodyToBinaryWriter 107 | 108 | protected override void WriteBodyToBinaryWriter(BinaryWriter bw) 109 | { 110 | bw.Write(Length); 111 | bw.Write(Interface); 112 | bw.Write(TDSVersion); 113 | bw.WriteBVarchar(ProgName); 114 | bw.Write(ProgVersion.MajorVer); 115 | bw.Write(ProgVersion.MinorVer); 116 | bw.Write(ProgVersion.BuildNumHi); 117 | bw.Write(ProgVersion.BuildNumLo); 118 | } 119 | 120 | #endregion 121 | 122 | #region ReadBodyFromBinaryReader 123 | 124 | protected override int ReadFromBinaryReader(BinaryReader br) 125 | { 126 | if (null == br) throw new ArgumentNullException(nameof(br)); 127 | 128 | try 129 | { 130 | var length = br.ReadUInt16(); 131 | if (length < FixedLength) 132 | throw new TDSInvalidMessageException( 133 | $"LoginAck token too short (min length {FixedLength}, actual length {length})", 134 | Message.MessageType, 135 | Message.Payload); 136 | 137 | _interface = br.ReadByte(); 138 | _tdsVersion = br.ReadUInt32(); 139 | var pnLength = br.ReadByte(); 140 | if (pnLength + FixedLength > length) 141 | throw new TDSInvalidMessageException( 142 | // ReSharper disable StringLiteralTypo 143 | $"ProgName exceeds LoginAck token size (ProgName length {pnLength}, fixed part {FixedLength}, token length {length}", 144 | // ReSharper restore StringLiteralTypo 145 | Message.MessageType, 146 | Message.Payload); 147 | _progName = br.ReadUnicode(pnLength); 148 | _progVersion = new ProgVersionStruct 149 | { 150 | MajorVer = br.ReadByte(), 151 | MinorVer = br.ReadByte(), 152 | BuildNumHi = br.ReadByte(), 153 | BuildNumLo = br.ReadByte() 154 | }; 155 | 156 | // Note: big-L length is calculated from the fields we've read 157 | if (Length < length) br.BaseStream.Seek(length - Length, SeekOrigin.Current); 158 | 159 | return length + 2; 160 | } 161 | catch (EndOfStreamException) 162 | { 163 | throw new TDSInvalidMessageException("Attempted to read longer than remainder of payload", 164 | Message.MessageType, 165 | Message.Payload); 166 | } 167 | } 168 | 169 | #endregion 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Reflection; 7 | using System.Reflection.Emit; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using JetBrains.Annotations; 11 | 12 | namespace TDSProtocol 13 | { 14 | [PublicAPI] 15 | public abstract class TDSMessage 16 | { 17 | public abstract TDSMessageType MessageType { get; } 18 | protected internal byte[] Payload { get; set; } 19 | protected internal byte[] ReceivedPayload { get; private set; } 20 | 21 | public static TDSMessage FromPackets(IEnumerable packets, TDSMessageType? overrideMessageType = null, TDSMessageType? expectMessageType = null) 22 | { 23 | if (null == packets) 24 | return null; 25 | 26 | var packetList = packets as List ?? packets.ToList(); 27 | if (packetList.Count == 0) 28 | return null; 29 | 30 | var firstPacket = packetList[0]; 31 | if (!ConcreteTypeConstructors.TryGetValue(overrideMessageType ?? firstPacket.PacketType, out var constructor)) 32 | { 33 | var packetData = firstPacket.PacketData; 34 | throw new TDSInvalidPacketException("Unrecognized TDS message type 0x" + ((byte)firstPacket.PacketType).ToString("X2"), packetData, packetData.Length); 35 | } 36 | 37 | // Instantiate the concrete message type and fill out the payload 38 | TDSMessage message = constructor(); 39 | 40 | if (expectMessageType != null && message.MessageType != expectMessageType) 41 | { 42 | throw new ProtocolViolationException($"Got a {message.MessageType}, expected a {expectMessageType}"); 43 | } 44 | 45 | byte[] payload = new byte[packetList.Sum(p => p.Payload.Length)]; 46 | int payloadOffset = 0; 47 | foreach (var packet in packetList) 48 | { 49 | Buffer.BlockCopy(packet.Payload, 0, payload, payloadOffset, packet.Payload.Length); 50 | payloadOffset += packet.Payload.Length; 51 | } 52 | message.Payload = payload; 53 | message.ReceivedPayload = payload; 54 | 55 | message.InterpretPayload(); 56 | 57 | return message; 58 | } 59 | 60 | public virtual string DumpReceivedPayload(string prefix = null) => ReceivedPayload.FormatAsHex(prefix); 61 | public virtual string DumpPayload(string prefix = null) => Payload.FormatAsHex(prefix); 62 | 63 | public void WriteAsPackets(Stream stream, ushort packetLength, ushort spid, TDSStatus status = TDSStatus.Normal, TDSMessageType? overrideMessageType = null) 64 | { 65 | TDSPacket.WriteMessage(stream, packetLength, spid, this, status, overrideMessageType); 66 | } 67 | 68 | public Task WriteAsPacketsAsync( 69 | Stream stream, ushort packetLength, ushort spid, TDSStatus status = TDSStatus.Normal, TDSMessageType? overrideMessageType = null) 70 | { 71 | return WriteAsPacketsAsync(stream, packetLength, spid, CancellationToken.None, status, overrideMessageType); 72 | } 73 | 74 | public Task WriteAsPacketsAsync( 75 | Stream stream, 76 | ushort packetLength, 77 | ushort spid, 78 | CancellationToken cancellationToken, 79 | TDSStatus status = TDSStatus.Normal, 80 | TDSMessageType? overrideMessageType = null) 81 | { 82 | return TDSPacket.WriteMessageAsync(stream, packetLength, spid, this, cancellationToken, status, overrideMessageType); 83 | } 84 | 85 | internal void EnsurePayload() 86 | { 87 | if (null == Payload) 88 | GeneratePayload(); 89 | } 90 | 91 | protected internal abstract void GeneratePayload(); 92 | protected internal abstract void InterpretPayload(); 93 | 94 | #region Implementation registry 95 | 96 | private static Func MakeConstructor(Type t) 97 | { 98 | // Get default constructor 99 | var ci = t.GetConstructor( 100 | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, 101 | null, 102 | Type.EmptyTypes, 103 | null) ?? 104 | throw new InvalidOperationException($"Unable to find default constructor for type {t.FullName}"); 105 | 106 | // Generate IL function to invoke the default constructor 107 | var dm = new DynamicMethod("_dynamic_constructor", t, Type.EmptyTypes, t); 108 | var ilg = dm.GetILGenerator(); 109 | ilg.Emit(OpCodes.Newobj, ci); 110 | ilg.Emit(OpCodes.Ret); 111 | return (Func)dm.CreateDelegate(typeof(Func)); 112 | } 113 | 114 | private static readonly Dictionary> ConcreteTypeConstructors = 115 | ( 116 | from cls in Assembly.GetExecutingAssembly().GetTypes() 117 | where !cls.IsAbstract && typeof(TDSMessage).IsAssignableFrom(cls) 118 | select cls 119 | ).ToDictionary(t => MakeConstructor(t)().MessageType, MakeConstructor); 120 | 121 | #endregion 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSMessageToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using JetBrains.Annotations; 5 | 6 | namespace TDSProtocol 7 | { 8 | [PublicAPI] 9 | public abstract class TDSMessageToken : TDSToken 10 | { 11 | protected TDSMessageToken(TDSTokenStreamMessage owningMessage) : base(owningMessage) 12 | { 13 | } 14 | 15 | private const int FixedLength = 16 | 4 + // Number 17 | 1 + // State 18 | 1 + // Class 19 | 2 + // MsgText length 20 | 1 + // ServerName length 21 | 1; // ProcName length 22 | 23 | #region Number 24 | 25 | private int _number; 26 | 27 | public int Number 28 | { 29 | get => _number; 30 | set 31 | { 32 | Message.Payload = null; 33 | _number = value; 34 | } 35 | } 36 | 37 | #endregion 38 | 39 | #region State 40 | 41 | private byte _state; 42 | 43 | public byte State 44 | { 45 | get => _state; 46 | set 47 | { 48 | Message.Payload = null; 49 | _state = value; 50 | } 51 | } 52 | 53 | #endregion 54 | 55 | #region Class 56 | 57 | private byte _class; 58 | 59 | public byte Class 60 | { 61 | get => _class; 62 | set 63 | { 64 | Message.Payload = null; 65 | _class = value; 66 | } 67 | } 68 | 69 | #endregion 70 | 71 | #region MsgText 72 | 73 | private string _msgText; 74 | 75 | public string MsgText 76 | { 77 | get => _msgText; 78 | set 79 | { 80 | Message.Payload = null; 81 | _msgText = value; 82 | } 83 | } 84 | 85 | #endregion 86 | 87 | #region ServerName 88 | 89 | private string _serverName; 90 | 91 | public string ServerName 92 | { 93 | get => _serverName; 94 | set 95 | { 96 | Message.Payload = null; 97 | _serverName = value; 98 | } 99 | } 100 | 101 | #endregion 102 | 103 | #region ProcName 104 | 105 | private string _procName; 106 | 107 | public string ProcName 108 | { 109 | get => _procName; 110 | set 111 | { 112 | Message.Payload = null; 113 | _procName = value; 114 | } 115 | } 116 | 117 | #endregion 118 | 119 | #region LineNumber 120 | 121 | private int _lineNumber; 122 | 123 | public int LineNumber 124 | { 125 | get => _lineNumber; 126 | set 127 | { 128 | Message.Payload = null; 129 | _lineNumber = value; 130 | } 131 | } 132 | 133 | #endregion 134 | 135 | #region WriteBodyToBinaryWriter 136 | 137 | protected override void WriteBodyToBinaryWriter(BinaryWriter bw) 138 | { 139 | var length = (ushort)( 140 | FixedLength + 141 | (MsgText?.Length * 2 ?? 0) + 142 | (ServerName?.Length * 2 ?? 0) + 143 | (ProcName?.Length * 2 ?? 0) + 144 | (TdsVersion >= 0x7200000 ? 4 : 2) // LineNumber 145 | ); 146 | bw.Write(length); 147 | bw.Write(Number); 148 | bw.Write(State); 149 | bw.Write(Class); 150 | bw.WriteUsVarchar(MsgText); 151 | bw.WriteBVarchar(ServerName); 152 | bw.WriteBVarchar(ProcName); 153 | if (TdsVersion >= 0x72000000) 154 | bw.Write(LineNumber); 155 | else 156 | bw.Write((ushort)LineNumber); 157 | } 158 | 159 | #endregion 160 | 161 | #region ReadFromBinaryReader 162 | 163 | protected override int ReadFromBinaryReader(BinaryReader br) 164 | { 165 | if (null == br) throw new ArgumentNullException(nameof(br)); 166 | 167 | try 168 | { 169 | var vsFixedLength = FixedLength + (TdsVersion >= 0x7200000 ? 4 : 2); 170 | 171 | var length = br.ReadUInt16(); 172 | 173 | if (length < vsFixedLength) 174 | throw new TDSInvalidMessageException( 175 | $"{TokenId} token too short (min length {FixedLength}, actual length {length})", 176 | Message.MessageType, 177 | Message.Payload); 178 | 179 | _number = br.ReadInt32(); 180 | _state = br.ReadByte(); 181 | _class = br.ReadByte(); 182 | 183 | var textLen = br.ReadUInt16(); 184 | _msgText = br.ReadUnicode(textLen); 185 | 186 | textLen = br.ReadByte(); 187 | _serverName = br.ReadUnicode(textLen); 188 | 189 | textLen = br.ReadByte(); 190 | _procName = br.ReadUnicode(textLen); 191 | 192 | _lineNumber = TdsVersion >= 0x72000000 ? br.ReadInt32() : br.ReadUInt16(); 193 | 194 | var lenRead = vsFixedLength + 2 * (_msgText.Length + _serverName.Length + _procName.Length); 195 | if (length > lenRead) br.BaseStream.Seek(length - lenRead, SeekOrigin.Current); 196 | 197 | return length + 3; 198 | } 199 | catch (EndOfStreamException) 200 | { 201 | throw new TDSInvalidMessageException("Attempted to read longer than remainder of payload", 202 | Message.MessageType, 203 | Message.Payload); 204 | } 205 | } 206 | 207 | #endregion 208 | 209 | #region Override ToString 210 | 211 | public override string ToString() 212 | { 213 | return string.IsNullOrEmpty(ProcName) 214 | ? $"Message {Number}, State {State}, Class {Class}, Line {LineNumber}: {MsgText}" 215 | : $"Message {Number}, State {State}, Class {Class}, Procedure {ProcName}, Line {LineNumber}: {MsgText}"; 216 | } 217 | 218 | #endregion 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSMessageType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public enum TDSMessageType : byte 8 | { 9 | SqlBatch = 1, 10 | PreTDS7Login = 2, 11 | Rpc = 3, 12 | TabularResult = 4, 13 | Attention = 6, 14 | BulkLoad = 7, 15 | FederatedAuth = 8, 16 | TransactionManagerRequest = 14, 17 | Login7 = 16, 18 | SspiMessage = 17, 19 | PreLogin = 18 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSPacket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using JetBrains.Annotations; 8 | 9 | namespace TDSProtocol 10 | { 11 | [PublicAPI] 12 | public class TDSPacket 13 | { 14 | #region Log4Net 15 | static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 16 | #endregion 17 | 18 | private static readonly HashSet KnownPacketTypes = 19 | new HashSet(Enum.GetValues(typeof(TDSMessageType)).Cast()); 20 | public static bool IsTDSPacketType(byte packetTypeByte) 21 | { 22 | return KnownPacketTypes.Contains((TDSMessageType)packetTypeByte); 23 | } 24 | 25 | public static bool DumpPackets { get; set; } 26 | 27 | public TDSMessageType PacketType { get; set; } 28 | 29 | public TDSStatus Status { get; set; } 30 | public ushort Length { get; private set; } 31 | 32 | public ushort SPID { get; set; } 33 | public byte PacketID { get; set; } 34 | public byte Window { get; set; } 35 | 36 | public byte[] Payload { get; set; } 37 | 38 | public byte[] PacketData 39 | { 40 | get 41 | { 42 | byte[] packetData = new byte[Length]; 43 | packetData[0] = (byte)PacketType; 44 | packetData[1] = (byte)Status; 45 | packetData[2] = (byte)((Length >> 8) & 0xff); 46 | packetData[3] = (byte)(Length & 0xff); 47 | packetData[4] = (byte)((SPID >> 8) & 0xff); 48 | packetData[5] = (byte)(SPID & 0xff); 49 | packetData[6] = PacketID; 50 | packetData[7] = Window; 51 | Buffer.BlockCopy(Payload, 0, packetData, HeaderLength, Payload.Length); 52 | 53 | return packetData; 54 | } 55 | } 56 | 57 | protected const int HeaderLength = 8; 58 | 59 | public static void WriteMessage( 60 | Stream stream, ushort packetLength, ushort spid, TDSMessage message, TDSStatus status = TDSStatus.Normal, TDSMessageType? overrideMessageType = null) 61 | { 62 | WriteMessageAsync(stream, packetLength, spid, message, status, overrideMessageType).Wait(); 63 | } 64 | 65 | public static Task WriteMessageAsync( 66 | Stream stream, ushort packetLength, ushort spid, TDSMessage message, TDSStatus status = TDSStatus.Normal, TDSMessageType? overrideMessageType = null) 67 | { 68 | return WriteMessageAsync(stream, packetLength, spid, message, CancellationToken.None, status, overrideMessageType); 69 | } 70 | 71 | public static async Task WriteMessageAsync( 72 | Stream stream, 73 | ushort packetLength, 74 | ushort spid, 75 | TDSMessage message, 76 | CancellationToken cancellationToken, 77 | TDSStatus status = TDSStatus.Normal, 78 | TDSMessageType? overrideMessageType = null) 79 | { 80 | status &= ~TDSStatus.EndOfMessage; 81 | 82 | message.EnsurePayload(); 83 | var payload = message.Payload; 84 | var packet = new byte[Math.Min(payload.Length + HeaderLength, packetLength)]; 85 | int payloadOffset = 0; 86 | int maxPacketPayload = packetLength - HeaderLength; 87 | var messageType = overrideMessageType ?? message.MessageType; 88 | 89 | byte packetId = 1; 90 | while (payloadOffset < payload.Length) 91 | { 92 | ushort thisPayloadLength = (ushort)Math.Min(payload.Length - payloadOffset, maxPacketPayload); 93 | ushort thisPacketLength = (ushort)(thisPayloadLength + HeaderLength); 94 | if (payloadOffset + thisPayloadLength >= payload.Length) 95 | status |= TDSStatus.EndOfMessage; 96 | 97 | packet[0] = (byte)messageType; 98 | packet[1] = (byte)status; 99 | packet[2] = (byte)((thisPacketLength >> 8) & 0xff); 100 | packet[3] = (byte)(thisPacketLength & 0xff); 101 | packet[4] = (byte)((spid >> 8) & 0xff); 102 | packet[5] = (byte)(spid & 0xff); 103 | packet[6] = packetId++; 104 | packet[7] = 0; 105 | Buffer.BlockCopy(payload, payloadOffset, packet, HeaderLength, thisPayloadLength); 106 | await stream.WriteAsync(packet, 0, thisPacketLength, cancellationToken); 107 | 108 | if (DumpPackets) 109 | log.DebugFormat("Wrote {0} packet. Data:\r\n{1}", messageType, packet.FormatAsHex()); 110 | 111 | payloadOffset += thisPayloadLength; 112 | } 113 | } 114 | 115 | public Task WriteToStreamAsync(Stream stream) 116 | { 117 | var packetData = PacketData; 118 | return stream.WriteAsync(packetData, 0, packetData.Length); 119 | } 120 | 121 | public static Task> ReadAsync(Stream stream) 122 | { 123 | return ReadAsync(stream, CancellationToken.None); 124 | } 125 | 126 | public static async Task> ReadAsync(Stream stream, CancellationToken cancellationToken) 127 | { 128 | byte[] peekBuffer = new byte[1]; 129 | int typeByteRead = await stream.ReadAsync(peekBuffer, 0, 1, cancellationToken).ConfigureAwait(false); 130 | if (typeByteRead <= 0) 131 | throw new EndOfStreamException(); 132 | 133 | return await ReadAsync((TDSMessageType)peekBuffer[0], stream, cancellationToken).ConfigureAwait(false); 134 | } 135 | 136 | public static IEnumerable Read(Stream stream) 137 | { 138 | int typeByte = stream.ReadByte(); 139 | if (typeByte < 0) 140 | throw new EndOfStreamException(); 141 | 142 | return Read((TDSMessageType)typeByte, stream); 143 | } 144 | 145 | public static Task> ReadAsync(TDSMessageType type, Stream stream) 146 | { 147 | return ReadAsync(type, stream, CancellationToken.None); 148 | } 149 | 150 | public static async Task> ReadAsync(TDSMessageType type, Stream stream, CancellationToken cancellationToken) 151 | { 152 | List packets = new List(); 153 | TDSPacket lastPacket; 154 | bool notFirstPacket = false; 155 | do 156 | { 157 | lastPacket = await ReadSinglePacketAsync(type, stream, notFirstPacket, cancellationToken).ConfigureAwait(false); 158 | packets.Add(lastPacket); 159 | notFirstPacket = true; 160 | } 161 | while ((lastPacket.Status & TDSStatus.EndOfMessage) != TDSStatus.EndOfMessage); 162 | 163 | return packets; 164 | } 165 | 166 | public static IEnumerable Read(TDSMessageType type, Stream stream) 167 | { 168 | return ReadAsync(type, stream).Result; 169 | } 170 | 171 | public static Task ReadSinglePacketAsync(TDSMessageType type, Stream stream, bool readPacketTypeFromStream) 172 | { 173 | return ReadSinglePacketAsync(type, stream, readPacketTypeFromStream, CancellationToken.None); 174 | } 175 | 176 | public static async Task ReadSinglePacketAsync( 177 | TDSMessageType type, Stream stream, bool readPacketTypeFromStream, CancellationToken cancellationToken) 178 | { 179 | // Already have the type, read the rest of the TDS packet header 180 | var header = new byte[HeaderLength]; 181 | int packetBytesRead = 0; 182 | if (!readPacketTypeFromStream) 183 | { 184 | header[0] = (byte)type; 185 | packetBytesRead = 1; 186 | } 187 | while (packetBytesRead < header.Length) 188 | { 189 | var thisRead = await stream.ReadAsync(header, packetBytesRead, header.Length - packetBytesRead, cancellationToken).ConfigureAwait(false); 190 | if (thisRead <= 0) 191 | throw new TDSInvalidPacketException("Incomplete TDS packet header received.", header, packetBytesRead); 192 | packetBytesRead += thisRead; 193 | } 194 | 195 | // Break out fields from the packet header 196 | var status = (TDSStatus)header[1]; 197 | var length = (ushort)((header[2] << 8) + header[3]); 198 | if (length < header.Length) 199 | throw new TDSInvalidPacketException("Invalid length (" + length + ") in TDS packet.", header, packetBytesRead); 200 | var spid = (ushort)((header[4] << 8) + header[5]); 201 | var packetId = header[6]; 202 | var window = header[7]; 203 | 204 | var payload = new byte[length - HeaderLength]; 205 | 206 | // Read rest of packet 207 | while (packetBytesRead < length) 208 | { 209 | var thisRead = await stream.ReadAsync(payload, packetBytesRead - HeaderLength, length - packetBytesRead, cancellationToken).ConfigureAwait(false); 210 | if (thisRead <= 0) 211 | { 212 | var packetData = new byte[packetBytesRead]; 213 | Buffer.BlockCopy(header, 0, packetData, 0, HeaderLength); 214 | Buffer.BlockCopy(payload, 0, packetData, HeaderLength, packetBytesRead - HeaderLength); 215 | throw new TDSInvalidPacketException( 216 | "Connection closed before complete TDS packet could be read, got " + packetBytesRead + " bytes, expected " + length + " bytes.", 217 | packetData, 218 | packetBytesRead); 219 | } 220 | packetBytesRead += thisRead; 221 | } 222 | 223 | if ((TDSMessageType)header[0] != type) 224 | { 225 | var packetData = new byte[packetBytesRead]; 226 | Buffer.BlockCopy(header, 0, packetData, 0, HeaderLength); 227 | Buffer.BlockCopy(payload, 0, packetData, HeaderLength, packetBytesRead - HeaderLength); 228 | throw new TDSInvalidPacketException( 229 | $"Unexpected message type, expected {type} got {(TDSMessageType)header[0]}", null, 0); 230 | } 231 | 232 | if (DumpPackets) 233 | log.DebugFormat("Received {0} packet. Data:\r\n{1}", type, header.Concat(payload).FormatAsHex()); 234 | 235 | return new TDSPacket 236 | { 237 | PacketType = type, 238 | Status = status, 239 | Length = length, 240 | SPID = spid, 241 | PacketID = packetId, 242 | Window = window, 243 | Payload = payload 244 | }; 245 | } 246 | 247 | public static TDSPacket ReadSinglePacket(TDSMessageType type, Stream stream, bool readPacketTypeFromStream) 248 | { 249 | return ReadSinglePacketAsync(type, stream, readPacketTypeFromStream).Result; 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSPreLoginMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Linq; 6 | using JetBrains.Annotations; 7 | 8 | namespace TDSProtocol 9 | { 10 | public class TDSPreLoginMessage : TDSMessage 11 | { 12 | #region Log4Net 13 | 14 | static readonly log4net.ILog log = 15 | log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 16 | 17 | #endregion 18 | 19 | public override TDSMessageType MessageType => TDSMessageType.PreLogin; 20 | 21 | #region Version 22 | 23 | public struct VersionInfo : IEquatable 24 | { 25 | public uint Version { get; set; } 26 | public ushort SubBuild { get; set; } 27 | 28 | #region Equality testing 29 | 30 | public bool Equals(VersionInfo other) 31 | { 32 | return Version == other.Version && SubBuild == other.SubBuild; 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | return obj is VersionInfo versionInfo && Equals(versionInfo); 38 | } 39 | 40 | [SuppressMessage("ReSharper", 41 | "NonReadonlyMemberInGetHashCode", 42 | Justification = 43 | "Setters necessary for reading message, values won't change between calls to GetHashCode")] 44 | public override int GetHashCode() 45 | { 46 | return Version.GetHashCode() ^ (13 * SubBuild.GetHashCode()); 47 | } 48 | 49 | public static bool operator ==(VersionInfo x, VersionInfo y) 50 | { 51 | return x.Equals(y); 52 | } 53 | 54 | public static bool operator !=(VersionInfo x, VersionInfo y) 55 | { 56 | return !x.Equals(y); 57 | } 58 | 59 | #endregion 60 | } 61 | 62 | private VersionInfo? _version; 63 | 64 | public VersionInfo? Version 65 | { 66 | get => _version; 67 | set 68 | { 69 | Payload = null; 70 | _version = value; 71 | } 72 | } 73 | 74 | #endregion 75 | 76 | #region Encryption 77 | 78 | public enum EncryptionEnum : byte 79 | { 80 | Off = 0x00, 81 | On = 0x01, 82 | NotSupported = 0x02, 83 | Required = 0x03 84 | } 85 | 86 | private EncryptionEnum? _encryption; 87 | 88 | public EncryptionEnum? Encryption 89 | { 90 | get => _encryption; 91 | set 92 | { 93 | Payload = null; 94 | _encryption = value; 95 | } 96 | } 97 | 98 | #endregion 99 | 100 | #region InstValidity 101 | 102 | // null-terminated multi-byte character string. MULTI-BYTE!!! 103 | private byte[] _instValidity; 104 | 105 | public byte[] InstValidity 106 | { 107 | get => _instValidity; 108 | set 109 | { 110 | Payload = null; 111 | _instValidity = value; 112 | } 113 | } 114 | 115 | #endregion 116 | 117 | #region ThreadId 118 | 119 | private uint? _threadId; 120 | 121 | public uint? ThreadId 122 | { 123 | get => _threadId; 124 | set 125 | { 126 | Payload = null; 127 | _threadId = value; 128 | } 129 | } 130 | 131 | #endregion 132 | 133 | #region Mars 134 | 135 | [PublicAPI] 136 | public enum MarsEnum : byte 137 | { 138 | Off = 0x00, 139 | On = 0x01 140 | } 141 | 142 | private MarsEnum? _mars; 143 | 144 | public MarsEnum? Mars 145 | { 146 | get => _mars; 147 | set 148 | { 149 | Payload = null; 150 | _mars = value; 151 | } 152 | } 153 | 154 | #endregion 155 | 156 | #region TraceId 157 | 158 | public struct TraceIdData : IEquatable 159 | { 160 | public Guid ConnId { get; set; } 161 | 162 | private byte[] _activityId; 163 | 164 | public byte[] ActivityId 165 | { 166 | get => _activityId; 167 | set 168 | { 169 | if (null == value) 170 | throw new ArgumentNullException(); 171 | if (value.Length != 0 && value.Length != 20) 172 | throw new ArgumentException("ActivityId must be 20 bytes"); 173 | _activityId = value; 174 | } 175 | } 176 | 177 | #region Equality testing 178 | 179 | public bool Equals(TraceIdData other) 180 | { 181 | // NB: since TraceIdData is a struct, other *cannot* be null 182 | return ConnId == other.ConnId && 183 | (ReferenceEquals(ActivityId, other.ActivityId) || 184 | (null != ActivityId && null != other.ActivityId && ActivityId.SequenceEqual(other.ActivityId))); 185 | } 186 | 187 | public override bool Equals(object obj) 188 | { 189 | return obj is TraceIdData traceIdData && Equals(traceIdData); 190 | } 191 | 192 | public override int GetHashCode() 193 | { 194 | // ReSharper disable once NonReadonlyMemberInGetHashCode 195 | // Nothing's going to change the property between calls but setter is needed for parsing messages 196 | int hash = ConnId.GetHashCode(); 197 | if (null != ActivityId) 198 | { 199 | foreach (var b in ActivityId) 200 | hash = (hash * 17) ^ b.GetHashCode(); 201 | } 202 | 203 | return hash; 204 | } 205 | 206 | public static bool operator ==(TraceIdData x, TraceIdData y) 207 | { 208 | return x.Equals(y); 209 | } 210 | 211 | public static bool operator !=(TraceIdData x, TraceIdData y) 212 | { 213 | return !x.Equals(y); 214 | } 215 | 216 | #endregion 217 | } 218 | 219 | private TraceIdData? _traceId; 220 | 221 | public TraceIdData? TraceId 222 | { 223 | get => _traceId; 224 | set 225 | { 226 | Payload = null; 227 | _traceId = value; 228 | } 229 | } 230 | 231 | #endregion 232 | 233 | #region FedAuthRequired 234 | 235 | [PublicAPI] 236 | public enum FedAuthRequiredEnum : byte 237 | { 238 | ClientSupportsSspiOrFederation = 0x01, 239 | 240 | ServerSupportsSspiOrFederation = 0x00, 241 | ServerDoesNotSupportSspi = 0x01 242 | } 243 | 244 | private FedAuthRequiredEnum? _fedAuthRequired; 245 | 246 | public FedAuthRequiredEnum? FedAuthRequired 247 | { 248 | get => _fedAuthRequired; 249 | set 250 | { 251 | Payload = null; 252 | _fedAuthRequired = value; 253 | } 254 | } 255 | 256 | #endregion 257 | 258 | #region Nonce 259 | 260 | private byte[] _nonce; 261 | 262 | public byte[] Nonce 263 | { 264 | get => _nonce; 265 | set 266 | { 267 | if (null != value && 32 != value.Length) 268 | throw new ArgumentException("Nonce must be 32 bytes"); 269 | Payload = null; 270 | _nonce = value; 271 | } 272 | } 273 | 274 | #endregion 275 | 276 | #region SslPayload 277 | 278 | public byte[] SslPayload 279 | { 280 | get => Payload; 281 | set => Payload = value; 282 | } 283 | 284 | #endregion 285 | 286 | #region OptionData 287 | 288 | public enum OptionToken : byte 289 | { 290 | Version = 0x00, 291 | Encryption = 0x01, 292 | InstOpt = 0x02, 293 | ThreadId = 0x03, 294 | Mars = 0x04, 295 | TraceId = 0x05, 296 | FedAuthRequired = 0x06, 297 | Nonce = 0x07, 298 | 299 | Terminator = 0xff 300 | } 301 | 302 | private class OptionData 303 | { 304 | public OptionToken Token { get; set; } 305 | public ushort Offset { get; set; } 306 | public ushort Length { get; set; } 307 | } 308 | 309 | #endregion 310 | 311 | #region ReadOption 312 | 313 | private OptionData ReadOption(BinaryReader br) 314 | { 315 | if (null == br) 316 | throw new ArgumentNullException(nameof(br)); 317 | var retVal = new OptionData 318 | { 319 | Token = (OptionToken)br.ReadByte() 320 | }; 321 | if (retVal.Token == OptionToken.Terminator) // Terminator 322 | return retVal; 323 | if (br.BaseStream.Position + 3 > br.BaseStream.Length) 324 | throw new TDSInvalidMessageException("Attempted to read option longer than remainder of payload", 325 | MessageType, 326 | Payload); 327 | retVal.Offset = br.ReadBigEndianUInt16(); 328 | retVal.Length = br.ReadBigEndianUInt16(); 329 | if (retVal.Offset + retVal.Length > br.BaseStream.Length) 330 | throw new TDSInvalidMessageException("Option Data past end of message", MessageType, Payload); 331 | return retVal; 332 | } 333 | 334 | #endregion 335 | 336 | #region WriteOption 337 | 338 | private void WriteOption(BinaryWriter bw, OptionData opt) 339 | { 340 | bw.Write((byte)opt.Token); 341 | if (opt.Token == OptionToken.Terminator) 342 | return; 343 | bw.WriteBigEndian(opt.Offset); 344 | bw.WriteBigEndian(opt.Length); 345 | } 346 | 347 | #endregion 348 | 349 | #region GeneratePayload 350 | 351 | private void AddToken(List list, OptionToken token, ref ushort offset, ushort length) 352 | { 353 | var opt = new OptionData {Token = token, Offset = offset, Length = length}; 354 | list.Add(opt); 355 | offset += length; 356 | } 357 | 358 | protected internal override void GeneratePayload() 359 | { 360 | var options = new List(); 361 | // Initially, option.Offset will be relative to end of option tokens, we'll come back and offset by the length of the token data later 362 | ushort offset = 0; 363 | if (null != Version) 364 | AddToken(options, OptionToken.Version, ref offset, 6); 365 | if (null != Encryption) 366 | AddToken(options, OptionToken.Encryption, ref offset, 1); 367 | if (null != InstValidity) 368 | AddToken(options, OptionToken.InstOpt, ref offset, (ushort)InstValidity.Length); 369 | if (null != ThreadId) 370 | AddToken(options, OptionToken.ThreadId, ref offset, 4); 371 | if (null != Mars) 372 | AddToken(options, OptionToken.Mars, ref offset, 1); 373 | if (null != TraceId) 374 | AddToken(options, OptionToken.TraceId, ref offset, 36); 375 | if (null != FedAuthRequired) 376 | AddToken(options, OptionToken.FedAuthRequired, ref offset, 1); 377 | if (null != Nonce) 378 | AddToken(options, OptionToken.Nonce, ref offset, 1); 379 | 380 | AddToken(options, OptionToken.Terminator, ref offset, 0); 381 | 382 | // Calculate the length of the token data and bump the offsets 383 | var initOffset = 384 | (ushort)((options.Count * 5) - 4); // 5 bytes per token, except the final Terminator is only 1 byte 385 | foreach (var o in options) 386 | o.Offset += initOffset; 387 | 388 | Payload = new byte[initOffset + options.Sum(o => o.Length)]; 389 | using (var ms = new MemoryStream(Payload)) 390 | using (var bw = new BinaryWriter(ms)) 391 | { 392 | foreach (var o in options) 393 | WriteOption(bw, o); 394 | if (null != Version) 395 | { 396 | bw.WriteBigEndian(Version.Value.Version); 397 | bw.Write(Version.Value.SubBuild); 398 | } 399 | 400 | if (null != Encryption) 401 | bw.Write((byte)Encryption.Value); 402 | if (null != InstValidity) 403 | bw.Write(InstValidity); 404 | if (null != ThreadId) 405 | bw.Write(ThreadId.Value); 406 | if (null != Mars) 407 | bw.Write((byte)Mars.Value); 408 | if (null != TraceId) 409 | { 410 | bw.Write(TraceId.Value.ConnId.ToByteArray()); 411 | bw.Write(TraceId.Value.ActivityId); 412 | } 413 | 414 | if (null != FedAuthRequired) 415 | bw.Write((byte)FedAuthRequired.Value); 416 | if (null != Nonce) 417 | bw.Write(Nonce); 418 | } 419 | } 420 | 421 | #endregion 422 | 423 | #region InterpretPayload 424 | 425 | protected internal override void InterpretPayload() 426 | { 427 | if (null == Payload) 428 | throw new InvalidOperationException("Attempted to interpret payload, but no payload to interpret"); 429 | 430 | if (Enum.IsDefined(typeof(SslPacketType), Payload[0])) 431 | // Looks like it's an SSL packet 432 | return; 433 | 434 | using (var ms = new MemoryStream(Payload)) 435 | using (var br = new BinaryReader(ms)) 436 | { 437 | var options = new List(); 438 | OptionData opt; 439 | while ((opt = ReadOption(br)).Token != OptionToken.Terminator) 440 | options.Add(opt); 441 | 442 | // NOTE: setting fields NOT properties here because setting properties wipes out Payload 443 | foreach (var o in options) 444 | { 445 | ms.Position = o.Offset; 446 | switch (o.Token) 447 | { 448 | case OptionToken.Version: 449 | if (o.Length < 6) 450 | throw new TDSInvalidMessageException( 451 | $"version option length ({o.Length}) was less than 6", 452 | MessageType, 453 | Payload); 454 | if (o.Length > 6) 455 | log.InfoFormat("Additional data in PRELOGIN version option, length was {0}, expected 6", 456 | o.Length); 457 | var version = br.ReadBigEndianUInt32(); 458 | var subBuild = br.ReadUInt16(); 459 | _version = new VersionInfo {Version = version, SubBuild = subBuild}; 460 | break; 461 | case OptionToken.Encryption: 462 | if (o.Length < 1) 463 | throw new TDSInvalidMessageException("encryption option contained no data", 464 | MessageType, 465 | Payload); 466 | if (o.Length > 1) 467 | log.InfoFormat("Additional data in PRELOGIN encryption option, length was {0}, expected 1", 468 | o.Length); 469 | _encryption = (EncryptionEnum)br.ReadByte(); 470 | break; 471 | case OptionToken.InstOpt: 472 | _instValidity = br.ReadBytes(o.Length); 473 | break; 474 | case OptionToken.ThreadId: 475 | if (o.Length == 0) 476 | _threadId = null; 477 | else 478 | { 479 | if (o.Length < 4) 480 | throw new TDSInvalidMessageException( 481 | $"ThreadId option length ({o.Length}) was less than 4", 482 | MessageType, 483 | Payload); 484 | if (o.Length > 4) 485 | log.InfoFormat( 486 | "Additional data in PRELOGIN ThreadId option, length was {0}, expected 4", 487 | o.Length); 488 | _threadId = br.ReadUInt32(); 489 | } 490 | 491 | break; 492 | case OptionToken.Mars: 493 | if (o.Length < 1) 494 | throw new TDSInvalidMessageException("MARS option contained no data", MessageType, Payload); 495 | if (o.Length > 1) 496 | log.InfoFormat("Additional data in PRELOGIN MARS option, length was {0}, expected 1", 497 | o.Length); 498 | _mars = (MarsEnum)br.ReadByte(); 499 | break; 500 | case OptionToken.TraceId: 501 | if (o.Length == 0) 502 | break; 503 | if (o.Length < 36) 504 | throw new TDSInvalidMessageException( 505 | $"TraceId option length ({o.Length}) was less than 36", 506 | MessageType, 507 | Payload); 508 | if (o.Length > 36) 509 | log.InfoFormat("Additional data in PRELOGIN TraceId option, length was {0}, expected 36", 510 | o.Length); 511 | var connId = new Guid(br.ReadBytes(16)); 512 | var activityId = br.ReadBytes(20); 513 | _traceId = new TraceIdData {ConnId = connId, ActivityId = activityId}; 514 | break; 515 | case OptionToken.FedAuthRequired: 516 | if (o.Length < 1) 517 | throw new TDSInvalidMessageException("FedAuthRequired option contained no data", 518 | MessageType, 519 | Payload); 520 | if (o.Length > 1) 521 | log.InfoFormat( 522 | "Additional data in PRELOGIN FedAuthRequired option, length was {0}, expected 1", 523 | o.Length); 524 | _fedAuthRequired = (FedAuthRequiredEnum)br.ReadByte(); 525 | break; 526 | case OptionToken.Nonce: 527 | if (o.Length < 32) 528 | throw new TDSInvalidMessageException( 529 | $"Nonce option length ({o.Length}) was less than 32", 530 | MessageType, 531 | Payload); 532 | if (o.Length > 32) 533 | log.InfoFormat("Additional data in PRELOGIN Nonce option, length was {0}, expected 32", 534 | o.Length); 535 | _nonce = br.ReadBytes(32); 536 | break; 537 | default: 538 | log.InfoFormat( 539 | "Ignoring unknown PRELOGIN option {0} with data {1}", 540 | o.Token, 541 | string.Join(" ", br.ReadBytes(o.Length).Select(b => b.ToString("X2")))); 542 | break; 543 | } 544 | } 545 | } 546 | } 547 | 548 | #endregion 549 | } 550 | } 551 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSProtocol.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {99FCFF04-2F8E-4AC9-8136-C2574D1E0F2F} 8 | Library 9 | Properties 10 | TDSProtocol 11 | TDSProtocol 12 | v4.8 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 2.0.13 82 | 83 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [Flags, PublicAPI] 7 | public enum TDSStatus : byte 8 | { 9 | Normal = 0, 10 | EndOfMessage = 0x01, 11 | Ignore = 0x02, 12 | ResetConnection = 0x08, 13 | ResetConnectionSkipTran = 0x10 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSTabularDataMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TDSProtocol 4 | { 5 | public class TDSTabularDataMessage : TDSTokenStreamMessage 6 | { 7 | #region Log4Net 8 | static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 9 | #endregion 10 | 11 | public override TDSMessageType MessageType => TDSMessageType.TabularResult; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Reflection.Emit; 7 | using System.Runtime.Remoting.Messaging; 8 | using JetBrains.Annotations; 9 | 10 | namespace TDSProtocol 11 | { 12 | [PublicAPI] 13 | public abstract class TDSToken 14 | { 15 | #region TdsVersion 16 | 17 | private const string LccKeyForTdsVersion = "com.techsoftware.TDSProtocol.TdsVersion"; 18 | 19 | /// 20 | /// TdsVersion for the current message processing. 21 | /// 22 | public static uint TdsVersion 23 | { 24 | get => (CallContext.LogicalGetData(LccKeyForTdsVersion) as uint?).GetValueOrDefault(); 25 | set => CallContext.LogicalSetData(LccKeyForTdsVersion, value); 26 | } 27 | 28 | #endregion 29 | 30 | protected internal readonly TDSTokenStreamMessage Message; 31 | 32 | public int ReceivedOffset { get; private set; } 33 | 34 | public int ReceivedLength { get; private set; } 35 | 36 | protected TDSToken(TDSTokenStreamMessage message) 37 | { 38 | Message = message; 39 | } 40 | 41 | #region TokenId 42 | 43 | public abstract TDSTokenType TokenId { get; } 44 | 45 | #endregion 46 | 47 | #region WriteToBinaryWriter 48 | 49 | protected internal void WriteToBinaryWriter(BinaryWriter bw) 50 | { 51 | bw.Write((byte)TokenId); 52 | WriteBodyToBinaryWriter(bw); 53 | } 54 | 55 | protected abstract void WriteBodyToBinaryWriter(BinaryWriter bw); 56 | 57 | #endregion 58 | 59 | #region ReadFromBinaryReader 60 | 61 | protected abstract int ReadFromBinaryReader(BinaryReader br); 62 | 63 | protected internal static TDSToken ReadFromBinaryReader(TDSTokenStreamMessage message, BinaryReader br, int initialOffset) 64 | { 65 | var tokenId = (TDSTokenType)br.ReadByte(); 66 | try 67 | { 68 | var token = ConcreteTypeConstructors[tokenId](message); 69 | token.ReceivedOffset = initialOffset; 70 | token.ReceivedLength = 1 + token.ReadFromBinaryReader(br); 71 | return token; 72 | } 73 | catch (Exception ex) 74 | { 75 | throw new TDSInvalidMessageException($"Failed to initialize {tokenId} token", 76 | message?.MessageType ?? unchecked ((TDSMessageType)(-1)), 77 | message?.Payload, 78 | ex); 79 | } 80 | } 81 | 82 | #endregion 83 | 84 | #region Implementation registry 85 | 86 | private static readonly Type[] ImplementationConstructorParameters = 87 | {typeof(TDSTokenStreamMessage)}; 88 | 89 | private static Func MakeConstructor(Type t) 90 | { 91 | // Get default constructor 92 | var ci = t.GetConstructor( 93 | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, 94 | null, 95 | ImplementationConstructorParameters, 96 | null) ?? 97 | throw new InvalidOperationException( 98 | $"Unable to find constructor from TDSTokenStreamMessage for type {t.FullName}"); 99 | 100 | // Generate IL function to invoke the default constructor 101 | var dm = new DynamicMethod("_dynamic_constructor", t, ImplementationConstructorParameters, t); 102 | var ilg = dm.GetILGenerator(); 103 | ilg.Emit(OpCodes.Ldarg_0); 104 | ilg.Emit(OpCodes.Newobj, ci); 105 | ilg.Emit(OpCodes.Ret); 106 | return (Func)dm.CreateDelegate( 107 | typeof(Func)); 108 | } 109 | 110 | private static readonly Dictionary> 111 | ConcreteTypeConstructors = 112 | ( 113 | from cls in Assembly.GetExecutingAssembly().GetTypes() 114 | where !cls.IsAbstract && typeof(TDSToken).IsAssignableFrom(cls) 115 | select cls 116 | ).ToDictionary(t => MakeConstructor(t)(null).TokenId, MakeConstructor); 117 | 118 | #endregion 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSTokenStreamMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using JetBrains.Annotations; 7 | 8 | namespace TDSProtocol 9 | { 10 | [PublicAPI] 11 | public abstract class TDSTokenStreamMessage : TDSMessage 12 | { 13 | #region Log4Net 14 | 15 | static readonly log4net.ILog log = 16 | log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 17 | 18 | #endregion 19 | 20 | #region RawMessage 21 | 22 | public byte[] RawMessage 23 | { 24 | get => Payload; 25 | set => Payload = value; 26 | } 27 | 28 | #endregion 29 | 30 | #region Tokens 31 | 32 | private readonly List _tokens = new List(); 33 | 34 | public IEnumerable Tokens => _tokens; 35 | 36 | public void ClearTokens() 37 | { 38 | _tokens.Clear(); 39 | } 40 | 41 | public void AddToken(TDSToken token) 42 | { 43 | if (token.Message != this) 44 | throw new InvalidOperationException("Token is not associated with this message"); 45 | _tokens.Add(token); 46 | } 47 | 48 | public void AddTokens(params TDSToken[] tokens) => AddTokens((IEnumerable)tokens); 49 | 50 | public void AddTokens(IEnumerable tokens) 51 | { 52 | if (null != tokens) 53 | { 54 | foreach (var token in tokens) 55 | AddToken(token); 56 | } 57 | } 58 | 59 | public TDSToken FindToken(TDSTokenType tokenId) 60 | { 61 | return _tokens.FirstOrDefault(t => t.TokenId == tokenId); 62 | } 63 | 64 | #endregion 65 | 66 | #region BuildMessage 67 | 68 | public void BuildMessage() 69 | { 70 | EnsurePayload(); 71 | } 72 | 73 | #endregion 74 | 75 | #region GeneratePayload 76 | 77 | protected internal override void GeneratePayload() 78 | { 79 | using (var ms = new MemoryStream()) 80 | { 81 | using (var bw = new BinaryWriter(ms, Unicode.Instance, true)) 82 | { 83 | foreach (var t in Tokens) 84 | t.WriteToBinaryWriter(bw); 85 | } 86 | 87 | Payload = ms.ToArray(); 88 | } 89 | } 90 | 91 | #endregion 92 | 93 | #region InterpretPayload 94 | 95 | protected internal override void InterpretPayload() 96 | { 97 | if (null == Payload) 98 | throw new InvalidOperationException("Attempted to interpret payload, but no payload to interpret"); 99 | 100 | using (var ms = new MemoryStream(Payload)) 101 | using (var br = new BinaryReader(ms)) 102 | { 103 | bool isDone = false; 104 | int offs = 0; 105 | int tn = 0; 106 | while (!isDone) 107 | { 108 | tn++; 109 | TDSToken token; 110 | try 111 | { 112 | token = TDSToken.ReadFromBinaryReader(this, br, offs); 113 | } 114 | catch (TDSInvalidMessageException ime) 115 | { 116 | throw new TDSInvalidMessageException($"Error parsing token #{tn} starting at offset {offs}", 117 | MessageType, 118 | ReceivedPayload, 119 | ime); 120 | } 121 | catch (Exception e) 122 | { 123 | throw new Exception($"Error parsing token #{tn} starting at offset {offs}", e); 124 | } 125 | 126 | log.DebugFormat("Read token #{0} at offset {1} of type {2}, length {3}", tn, offs, token.TokenId, token.ReceivedLength); 127 | 128 | AddToken(token); 129 | isDone = token is TDSDoneToken done && !done.Status.HasFlag(TDSDoneToken.StatusEnum.More); 130 | offs += token.ReceivedLength; 131 | } 132 | } 133 | } 134 | 135 | #endregion 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/TDSProtocol/TDSTokenType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace TDSProtocol 5 | { 6 | [PublicAPI] 7 | public enum TDSTokenType : byte 8 | { 9 | AltMetaData = 0x88, 10 | AltRow = 0xD3, 11 | ColMetaData = 0x81, 12 | ColInfo = 0xA5, 13 | Done = 0xFD, 14 | DoneProc = 0xFE, 15 | DoneInProc = 0xFF, 16 | EnvChange = 0xE3, 17 | Error = 0xAA, 18 | FeatureExtAck = 0xAE, // TDS 7.4+ 19 | Info = 0xAB, 20 | LoginAck = 0xAD, 21 | NbcRow = 0xD2, // TDS 7.3+ 22 | Offset = 0x78, 23 | Order = 0xA9, 24 | ReturnStatus = 0x79, 25 | ReturnValue = 0xAC, 26 | Row = 0xD1, 27 | SessionState = 0xE4, // TDS 7.4+ 28 | Sspi = 0xED, 29 | TabName = 0xA4 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/TDSProtocol/Unicode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace TDSProtocol 5 | { 6 | public static class Unicode 7 | { 8 | public static readonly Encoding Instance = new UnicodeEncoding(false, false, false); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/TDSProtocolTests/BinaryReaderExtensionsUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | using TDSProtocol; 6 | 7 | namespace TDSProtocolTests 8 | { 9 | [TestClass] 10 | public class BinaryReaderExtensionsUnitTests 11 | { 12 | [TestMethod] 13 | public void TestInt16Positive() 14 | { 15 | byte[] value = { 0x43, 0x21 }; 16 | short expected = 0x4321; 17 | 18 | short actual; 19 | using (var ms = new MemoryStream(value)) 20 | using (var br = new BinaryReader(ms)) 21 | actual = br.ReadBigEndianInt16(); 22 | 23 | Assert.AreEqual(expected, actual); 24 | } 25 | 26 | [TestMethod] 27 | public void TestInt16Negative() 28 | { 29 | byte[] value = { 0xED, 0xCB }; 30 | short expected = -0x1235; // = 0xEDCB 31 | 32 | short actual; 33 | using (var ms = new MemoryStream(value)) 34 | using (var br = new BinaryReader(ms)) 35 | actual = br.ReadBigEndianInt16(); 36 | 37 | Assert.AreEqual(expected, actual); 38 | } 39 | 40 | [TestMethod] 41 | public void TestUInt16Small() 42 | { 43 | byte[] value = { 0x12, 0x34 }; 44 | ushort expected = 0x1234; 45 | 46 | ushort actual; 47 | using (var ms = new MemoryStream(value)) 48 | using (var br = new BinaryReader(ms)) 49 | actual = br.ReadBigEndianUInt16(); 50 | 51 | Assert.AreEqual(expected, actual); 52 | } 53 | 54 | [TestMethod] 55 | public void TestUInt16Large() 56 | { 57 | byte[] value = { 0xBC, 0xDE }; 58 | ushort expected = 0xBCDE; 59 | 60 | ushort actual; 61 | using (var ms = new MemoryStream(value)) 62 | using (var br = new BinaryReader(ms)) 63 | actual = br.ReadBigEndianUInt16(); 64 | 65 | Assert.AreEqual(expected, actual); 66 | } 67 | 68 | [TestMethod] 69 | public void TestInt32Positive() 70 | { 71 | byte[] value = { 0x44, 0x33, 0x22, 0x11 }; 72 | int expected = 0x44332211; 73 | 74 | int actual; 75 | using (var ms = new MemoryStream(value)) 76 | using (var br = new BinaryReader(ms)) 77 | actual = br.ReadBigEndianInt32(); 78 | 79 | Assert.AreEqual(expected, actual); 80 | } 81 | 82 | [TestMethod] 83 | public void TestInt32Negative() 84 | { 85 | byte[] value = { 0xBB, 0xCC, 0xDD, 0xEE }; 86 | int expected = -0x44332212; // = 0xBBCCDDEE 87 | 88 | int actual; 89 | using (var ms = new MemoryStream(value)) 90 | using (var br = new BinaryReader(ms)) 91 | actual = br.ReadBigEndianInt32(); 92 | 93 | Assert.AreEqual(expected, actual); 94 | } 95 | 96 | [TestMethod] 97 | public void TestUInt32Small() 98 | { 99 | byte[] value = { 0x12, 0x34, 0x23, 0x45 }; 100 | uint expected = 0x12342345; 101 | 102 | uint actual; 103 | using (var ms = new MemoryStream(value)) 104 | using (var br = new BinaryReader(ms)) 105 | actual = br.ReadBigEndianUInt32(); 106 | 107 | Assert.AreEqual(expected, actual); 108 | } 109 | 110 | [TestMethod] 111 | public void TestUInt32Large() 112 | { 113 | byte[] value = { 0xBC, 0xDE, 0xCD, 0xEF }; 114 | uint expected = 0xBCDECDEF; 115 | 116 | uint actual; 117 | using (var ms = new MemoryStream(value)) 118 | using (var br = new BinaryReader(ms)) 119 | actual = br.ReadBigEndianUInt32(); 120 | 121 | Assert.AreEqual(expected, actual); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/TDSProtocolTests/BinaryWriterExtensionsUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | using TDSProtocol; 6 | 7 | namespace TDSProtocolTests 8 | { 9 | [TestClass] 10 | public class BinaryWriterExtensionsUnitTests 11 | { 12 | [TestMethod] 13 | public void TestInt16Positive() 14 | { 15 | short value = 0x1234; 16 | byte[] expected = new byte[] { 0x12, 0x34 }; 17 | 18 | byte[] actual = new byte[2]; 19 | using (var ms = new MemoryStream(actual)) 20 | using (var bw = new BinaryWriter(ms)) 21 | bw.WriteBigEndian(value); 22 | 23 | CollectionAssert.AreEqual(expected, actual); 24 | } 25 | 26 | [TestMethod] 27 | public void TestInt16Negative() 28 | { 29 | short value = -0x5433; // = 0xABCD 30 | byte[] expected = new byte[] { 0xAB, 0xCD }; 31 | 32 | byte[] actual = new byte[2]; 33 | using (var ms = new MemoryStream(actual)) 34 | using (var bw = new BinaryWriter(ms)) 35 | bw.WriteBigEndian(value); 36 | 37 | CollectionAssert.AreEqual(expected, actual); 38 | } 39 | 40 | [TestMethod] 41 | public void TestUInt16Small() 42 | { 43 | ushort value = 0x2345; 44 | byte[] expected = new byte[] { 0x23, 0x45 }; 45 | 46 | byte[] actual = new byte[2]; 47 | using (var ms = new MemoryStream(actual)) 48 | using (var bw = new BinaryWriter(ms)) 49 | bw.WriteBigEndian(value); 50 | 51 | CollectionAssert.AreEqual(expected, actual); 52 | } 53 | 54 | [TestMethod] 55 | public void TestUInt16Large() 56 | { 57 | ushort value = 0xBCDE; 58 | byte[] expected = new byte[] { 0xBC, 0xDE }; 59 | 60 | byte[] actual = new byte[2]; 61 | using (var ms = new MemoryStream(actual)) 62 | using (var bw = new BinaryWriter(ms)) 63 | bw.WriteBigEndian(value); 64 | 65 | CollectionAssert.AreEqual(expected, actual); 66 | } 67 | 68 | [TestMethod] 69 | public void TestInt32Positive() 70 | { 71 | int value = 0x1A253A45; 72 | byte[] expected = new byte[] { 0x1A, 0x25, 0x3A, 0x45 }; 73 | 74 | byte[] actual = new byte[4]; 75 | using (var ms = new MemoryStream(actual)) 76 | using (var bw = new BinaryWriter(ms)) 77 | bw.WriteBigEndian(value); 78 | 79 | CollectionAssert.AreEqual(expected, actual); 80 | } 81 | 82 | [TestMethod] 83 | public void TestInt32Negative() 84 | { 85 | int value = -0x5EAD5CAC; // = 0xA152A354 86 | byte[] expected = new byte[] { 0xA1, 0x52, 0xA3, 0x54 }; 87 | 88 | byte[] actual = new byte[4]; 89 | using (var ms = new MemoryStream(actual)) 90 | using (var bw = new BinaryWriter(ms)) 91 | bw.WriteBigEndian(value); 92 | 93 | CollectionAssert.AreEqual(expected, actual); 94 | } 95 | 96 | [TestMethod] 97 | public void TestUInt32Small() 98 | { 99 | uint value = 0x152A354A; 100 | byte[] expected = new byte[] { 0x15, 0x2A, 0x35, 0x4A }; 101 | 102 | byte[] actual = new byte[4]; 103 | using (var ms = new MemoryStream(actual)) 104 | using (var bw = new BinaryWriter(ms)) 105 | bw.WriteBigEndian(value); 106 | 107 | CollectionAssert.AreEqual(expected, actual); 108 | } 109 | 110 | [TestMethod] 111 | public void TestUInt32Large() 112 | { 113 | uint value = 0xA453A251; 114 | byte[] expected = new byte[] { 0xA4, 0x53, 0xA2, 0x51 }; 115 | 116 | byte[] actual = new byte[4]; 117 | using (var ms = new MemoryStream(actual)) 118 | using (var bw = new BinaryWriter(ms)) 119 | bw.WriteBigEndian(value); 120 | 121 | CollectionAssert.AreEqual(expected, actual); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/TDSProtocolTests/EnumerableAssert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace TDSProtocolTests 6 | { 7 | public static class EnumerableAssert 8 | { 9 | public static void AreEqual(IEnumerable expected, IEnumerable actual) where T : IComparable 10 | { 11 | AreEqual(expected, actual, Comparer.Default); 12 | } 13 | 14 | public static void AreEqual(IEnumerable expected, IEnumerable actual, IComparer comparer) 15 | { 16 | // Both null? That's equal! 17 | if (expected is null && actual is null) 18 | return; 19 | 20 | // Both should be non-null, then 21 | if (expected is null) 22 | Assert.Fail("Expected null, actual non-null"); 23 | if (actual is null) 24 | Assert.Fail("Expected non-null, actual null"); 25 | 26 | // Iterator over each enumerable, comparing each element, until at least one iterator is exhausted 27 | using (var expectedIterator = expected.GetEnumerator()) 28 | using (var actualIterator = actual.GetEnumerator()) 29 | { 30 | bool moreExpected, moreActual; 31 | uint count = 0; 32 | for (uint idx = 1; 33 | // NOTE: use of & not && is deliberate because we DO NOT want to shortcut 34 | (moreExpected = expectedIterator.MoveNext()) & (moreActual = actualIterator.MoveNext()); 35 | idx++, count++) 36 | { 37 | if (comparer.Compare(expectedIterator.Current, actualIterator.Current) != 0) 38 | { 39 | var lastDigit = idx % 10; 40 | Assert.AreEqual( 41 | expectedIterator.Current, 42 | actualIterator.Current, 43 | "The {0}{1} element in the sequences differed", 44 | idx, 45 | (lastDigit > 3 || lastDigit == 0 || ((idx / 10) == 1)) ? "th" : 46 | lastDigit == 1 ? "st" : 47 | lastDigit == 2 ? "nd" : "rd"); 48 | } 49 | } 50 | 51 | // Check neither iterator has more 52 | 53 | if (moreExpected) 54 | { 55 | uint expectedCount = count + 1; 56 | while (expectedIterator.MoveNext()) 57 | expectedCount++; 58 | Assert.AreEqual(expectedCount, count, "Sequences were not of same length"); 59 | } 60 | 61 | if (moreActual) 62 | { 63 | uint actualCount = count + 1; 64 | while (actualIterator.MoveNext()) 65 | actualCount++; 66 | Assert.AreEqual(count, actualCount, "Sequences were not of same length"); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/TDSProtocolTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TDSProtocolTests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Tech Software LLC")] 11 | [assembly: AssemblyProduct("TDSProtocolTests")] 12 | [assembly: AssemblyCopyright("Copyright © Tech Software LLC 2014-2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("1b1ed7df-e680-44cc-bb7c-fb943ddba745")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/TDSProtocolTests/TDSPreLoginMessageTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | using TDSProtocol; 5 | 6 | namespace TDSProtocolTests 7 | { 8 | [TestClass] 9 | public class TDSPreLoginMessageTests 10 | { 11 | [TestMethod] 12 | public void TestInterpretPayloadSimple() 13 | { 14 | TDSPreLoginMessage expected = 15 | new TDSPreLoginMessage 16 | { 17 | Version = new TDSPreLoginMessage.VersionInfo { Version = 0x09000000, SubBuild = 0x0000 }, 18 | Encryption = TDSPreLoginMessage.EncryptionEnum.On, 19 | InstValidity = new byte[] { 0x00 }, 20 | ThreadId = 0x00000DB8, 21 | Mars = TDSPreLoginMessage.MarsEnum.On 22 | }; 23 | 24 | 25 | TDSPreLoginMessage actual = new TDSPreLoginMessage 26 | { 27 | Payload = new byte[] 28 | { 29 | 0x00, 0x00, 0x1A, 0x00, 0x06, 0x01, 0x00, 0x20, 0x00, 0x01, 30 | 0x02, 0x00, 0x21, 0x00, 0x01, 0x03, 0x00, 0x22, 0x00, 0x04, 31 | 0x04, 0x00, 0x26, 0x00, 0x01, 0xFF, 0x09, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x01, 0x00, 0xB8, 0x0D, 0x00, 0x00, 0x01 33 | } 34 | }; 35 | actual.InterpretPayload(); 36 | 37 | Assert.AreEqual(expected.Version, actual.Version); 38 | Assert.AreEqual(expected.Encryption, actual.Encryption); 39 | EnumerableAssert.AreEqual(expected.InstValidity, actual.InstValidity); 40 | Assert.AreEqual(expected.ThreadId, actual.ThreadId); 41 | Assert.AreEqual(expected.Mars, actual.Mars); 42 | Assert.AreEqual(expected.TraceId, actual.TraceId); 43 | Assert.AreEqual(expected.FedAuthRequired, actual.FedAuthRequired); 44 | EnumerableAssert.AreEqual(expected.Nonce, actual.Nonce); 45 | } 46 | 47 | [TestMethod] 48 | public void TestInterpretPayloadEmptyTrace() 49 | { 50 | TDSPreLoginMessage expected = 51 | new TDSPreLoginMessage 52 | { 53 | Version = new TDSPreLoginMessage.VersionInfo { Version = 0x09000000, SubBuild = 0x0000 }, 54 | Encryption = TDSPreLoginMessage.EncryptionEnum.On, 55 | InstValidity = new byte[] { 0x00 }, 56 | ThreadId = 0x00000DB8, 57 | Mars = TDSPreLoginMessage.MarsEnum.On 58 | }; 59 | 60 | 61 | TDSPreLoginMessage actual = new TDSPreLoginMessage 62 | { 63 | Payload = new byte[] 64 | { 65 | 0x00, 0x00, 0x1F, 0x00, 0x06, 0x01, 0x00, 0x25, 0x00, 0x01, 66 | 0x02, 0x00, 0x26, 0x00, 0x01, 0x03, 0x00, 0x27, 0x00, 0x04, 67 | 0x04, 0x00, 0x2B, 0x00, 0x01, 0x05, 0x00, 0x2C, 0x00, 0x00, 68 | 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xB8, 69 | 0x0D, 0x00, 0x00, 0x01 70 | } 71 | }; 72 | actual.InterpretPayload(); 73 | 74 | Assert.AreEqual(expected.Version, actual.Version); 75 | Assert.AreEqual(expected.Encryption, actual.Encryption); 76 | EnumerableAssert.AreEqual(expected.InstValidity, actual.InstValidity); 77 | Assert.AreEqual(expected.ThreadId, actual.ThreadId); 78 | Assert.AreEqual(expected.Mars, actual.Mars); 79 | Assert.AreEqual(expected.TraceId, actual.TraceId); 80 | Assert.AreEqual(expected.FedAuthRequired, actual.FedAuthRequired); 81 | EnumerableAssert.AreEqual(expected.Nonce, actual.Nonce); 82 | } 83 | 84 | [TestMethod] 85 | public void TestGeneratePayloadSimple() 86 | { 87 | TDSPreLoginMessage msg = 88 | new TDSPreLoginMessage 89 | { 90 | Version = new TDSPreLoginMessage.VersionInfo { Version = 0x09000000, SubBuild = 0x0000 }, 91 | Encryption = TDSPreLoginMessage.EncryptionEnum.On, 92 | InstValidity = new byte[] { 0x00 }, 93 | ThreadId = 0x00000DB8, 94 | Mars = TDSPreLoginMessage.MarsEnum.On 95 | }; 96 | msg.GeneratePayload(); 97 | 98 | var expected = new byte[] 99 | { 100 | 0x00, 0x00, 0x1A, 0x00, 0x06, 0x01, 0x00, 0x20, 0x00, 0x01, 0x02, 0x00, 0x21, 0x00, 0x01, 0x03, 101 | 0x00, 0x22, 0x00, 0x04, 0x04, 0x00, 0x26, 0x00, 0x01, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 102 | 0x01, 0x00, 0xB8, 0x0D, 0x00, 0x00, 0x01 103 | }; 104 | var actual = msg.Payload; 105 | 106 | EnumerableAssert.AreEqual(expected, actual); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/TDSProtocolTests/TDSProtocolTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {2ADF0698-4E98-45FA-88EC-413F75BB8FF7} 7 | Library 8 | Properties 9 | TDSProtocolTests 10 | TDSProtocolTests 11 | v4.8 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {99fcff04-2f8e-4ac9-8136-c2574d1e0f2f} 66 | TDSProtocol 67 | 68 | 69 | 70 | 71 | 2.0.13 72 | 73 | 74 | 75 | 76 | 77 | 78 | False 79 | 80 | 81 | False 82 | 83 | 84 | False 85 | 86 | 87 | False 88 | 89 | 90 | 91 | 92 | 93 | 94 | 101 | -------------------------------------------------------------------------------- /src/TDSProxy.Authentication/AuthenticationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TDSProxy.Authentication 4 | { 5 | public class AuthenticationResult 6 | { 7 | public AuthenticationResult() { } 8 | public AuthenticationResult(AuthenticationResult toCopy) 9 | { 10 | AllowConnection = toCopy.AllowConnection; 11 | DisplayUsername = toCopy.DisplayUsername; 12 | ConnectToDatabase = toCopy.ConnectToDatabase; 13 | ConnectAsUser = toCopy.ConnectAsUser; 14 | ConnectUsingPassword = toCopy.ConnectUsingPassword; 15 | } 16 | 17 | public bool AllowConnection { get; set; } 18 | public string DisplayUsername { get; set; } 19 | public string ConnectToDatabase { get; set; } 20 | public string ConnectAsUser { get; set; } 21 | public string ConnectUsingPassword { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TDSProxy.Authentication/IAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using JetBrains.Annotations; 4 | 5 | namespace TDSProxy.Authentication 6 | { 7 | [PublicAPI] 8 | public interface IAuthenticator 9 | { 10 | AuthenticationResult Authenticate(IPAddress clientEP, string username, string password, string database); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/TDSProxy.Authentication/JetBrains.Annotations.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable all 2 | using System; 3 | 4 | namespace JetBrains.Annotations 5 | { 6 | /// 7 | /// Indicates that the value of the marked element could be null sometimes, 8 | /// so checking for null is required before its usage. 9 | /// 10 | /// 11 | /// [CanBeNull] object Test() => null; 12 | /// 13 | /// void UseTest() { 14 | /// var p = Test(); 15 | /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' 16 | /// } 17 | /// 18 | [AttributeUsage( 19 | AttributeTargets.Method | 20 | AttributeTargets.Parameter | 21 | AttributeTargets.Property | 22 | AttributeTargets.Delegate | 23 | AttributeTargets.Field | 24 | AttributeTargets.Event | 25 | AttributeTargets.Class | 26 | AttributeTargets.Interface | 27 | AttributeTargets.GenericParameter)] 28 | internal sealed class CanBeNullAttribute : Attribute 29 | { 30 | } 31 | 32 | /// 33 | /// Indicates that the value of the marked element can never be null. 34 | /// 35 | /// 36 | /// [NotNull] object Foo() { 37 | /// return null; // Warning: Possible 'null' assignment 38 | /// } 39 | /// 40 | [AttributeUsage( 41 | AttributeTargets.Method | 42 | AttributeTargets.Parameter | 43 | AttributeTargets.Property | 44 | AttributeTargets.Delegate | 45 | AttributeTargets.Field | 46 | AttributeTargets.Event | 47 | AttributeTargets.Class | 48 | AttributeTargets.Interface | 49 | AttributeTargets.GenericParameter)] 50 | internal sealed class NotNullAttribute : Attribute 51 | { 52 | } 53 | 54 | /// 55 | /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), 56 | /// so this symbol will not be reported as unused (as well as by other usage inspections). 57 | /// 58 | [AttributeUsage(AttributeTargets.All)] 59 | internal sealed class UsedImplicitlyAttribute : Attribute 60 | { 61 | public UsedImplicitlyAttribute() 62 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) 63 | { 64 | } 65 | 66 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) 67 | : this(useKindFlags, ImplicitUseTargetFlags.Default) 68 | { 69 | } 70 | 71 | public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) 72 | : this(ImplicitUseKindFlags.Default, targetFlags) 73 | { 74 | } 75 | 76 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 77 | { 78 | UseKindFlags = useKindFlags; 79 | TargetFlags = targetFlags; 80 | } 81 | 82 | public ImplicitUseKindFlags UseKindFlags { get; } 83 | 84 | public ImplicitUseTargetFlags TargetFlags { get; } 85 | } 86 | 87 | /// 88 | /// Can be applied to attributes, type parameters, and parameters of a type assignable from . 89 | /// When applied to an attribute, the decorated attribute behaves the same as . 90 | /// When applied to a type parameter or to a parameter of type , indicates that the corresponding type 91 | /// is used implicitly. 92 | /// 93 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter | AttributeTargets.Parameter)] 94 | internal sealed class MeansImplicitUseAttribute : Attribute 95 | { 96 | public MeansImplicitUseAttribute() 97 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) 98 | { 99 | } 100 | 101 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) 102 | : this(useKindFlags, ImplicitUseTargetFlags.Default) 103 | { 104 | } 105 | 106 | public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) 107 | : this(ImplicitUseKindFlags.Default, targetFlags) 108 | { 109 | } 110 | 111 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 112 | { 113 | UseKindFlags = useKindFlags; 114 | TargetFlags = targetFlags; 115 | } 116 | 117 | [UsedImplicitly] 118 | public ImplicitUseKindFlags UseKindFlags { get; } 119 | 120 | [UsedImplicitly] 121 | public ImplicitUseTargetFlags TargetFlags { get; } 122 | } 123 | 124 | /// 125 | /// Specify the details of implicitly used symbol when it is marked 126 | /// with or . 127 | /// 128 | [Flags] 129 | internal enum ImplicitUseKindFlags 130 | { 131 | Default = Access | Assign | InstantiatedWithFixedConstructorSignature, 132 | 133 | /// Only entity marked with attribute considered used. 134 | Access = 1, 135 | 136 | /// Indicates implicit assignment to a member. 137 | Assign = 2, 138 | 139 | /// 140 | /// Indicates implicit instantiation of a type with fixed constructor signature. 141 | /// That means any unused constructor parameters won't be reported as such. 142 | /// 143 | InstantiatedWithFixedConstructorSignature = 4, 144 | 145 | /// Indicates implicit instantiation of a type. 146 | InstantiatedNoFixedConstructorSignature = 8, 147 | } 148 | 149 | /// 150 | /// Specify what is considered to be used implicitly when marked 151 | /// with or . 152 | /// 153 | [Flags] 154 | internal enum ImplicitUseTargetFlags 155 | { 156 | Default = Itself, 157 | Itself = 1, 158 | 159 | /// Members of entity marked with attribute are considered used. 160 | Members = 2, 161 | 162 | /// Entity marked with attribute and all its members considered used. 163 | WithMembers = Itself | Members 164 | } 165 | 166 | /// 167 | /// This attribute is intended to mark publicly available API 168 | /// which should not be removed and so is treated as used. 169 | /// 170 | [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] 171 | internal sealed class PublicAPIAttribute : Attribute 172 | { 173 | public PublicAPIAttribute() 174 | { 175 | } 176 | 177 | public PublicAPIAttribute([NotNull] string comment) 178 | { 179 | Comment = comment; 180 | } 181 | 182 | [CanBeNull] 183 | public string Comment { get; } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/TDSProxy.Authentication/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TDSProxy.Authentication")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Tech Software LLC")] 11 | [assembly: AssemblyProduct("TDSProxy.Authentication")] 12 | [assembly: AssemblyCopyright("Copyright © Tech Software LLC 2014-2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("9c524ce3-67c7-4a5f-a71a-47aa9adcbc56")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/TDSProxy.Authentication/TDSProxy.Authentication.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {87A5B526-EC83-4237-9A08-7AA977DA8EF6} 8 | Library 9 | Properties 10 | TDSProxy.Authentication 11 | TDSProxy.Authentication 12 | v4.8 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | -------------------------------------------------------------------------------- /src/TDSProxy.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32014.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TDSProxy", "TDSProxy\TDSProxy.csproj", "{0AE9E17C-C69B-44E9-A923-6B9B3D62BD40}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TDSProtocol", "TDSProtocol\TDSProtocol.csproj", "{99FCFF04-2F8E-4AC9-8136-C2574D1E0F2F}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TDSProtocolTests", "TDSProtocolTests\TDSProtocolTests.csproj", "{2ADF0698-4E98-45FA-88EC-413F75BB8FF7}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleAuthenticator", "SampleAuthenticator\SampleAuthenticator.csproj", "{47F41C77-1B67-4CB9-B217-27F1A882A857}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TDSProxy.Authentication", "TDSProxy.Authentication\TDSProxy.Authentication.csproj", "{87A5B526-EC83-4237-9A08-7AA977DA8EF6}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConnection", "TestConnection\TestConnection.csproj", "{2B478DDF-E3E5-4C83-B89B-1F1571A72B24}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {0AE9E17C-C69B-44E9-A923-6B9B3D62BD40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {0AE9E17C-C69B-44E9-A923-6B9B3D62BD40}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {0AE9E17C-C69B-44E9-A923-6B9B3D62BD40}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {0AE9E17C-C69B-44E9-A923-6B9B3D62BD40}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {99FCFF04-2F8E-4AC9-8136-C2574D1E0F2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {99FCFF04-2F8E-4AC9-8136-C2574D1E0F2F}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {99FCFF04-2F8E-4AC9-8136-C2574D1E0F2F}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {99FCFF04-2F8E-4AC9-8136-C2574D1E0F2F}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {2ADF0698-4E98-45FA-88EC-413F75BB8FF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {2ADF0698-4E98-45FA-88EC-413F75BB8FF7}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {2ADF0698-4E98-45FA-88EC-413F75BB8FF7}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {2ADF0698-4E98-45FA-88EC-413F75BB8FF7}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {47F41C77-1B67-4CB9-B217-27F1A882A857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {47F41C77-1B67-4CB9-B217-27F1A882A857}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {47F41C77-1B67-4CB9-B217-27F1A882A857}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {47F41C77-1B67-4CB9-B217-27F1A882A857}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {87A5B526-EC83-4237-9A08-7AA977DA8EF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {87A5B526-EC83-4237-9A08-7AA977DA8EF6}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {87A5B526-EC83-4237-9A08-7AA977DA8EF6}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {87A5B526-EC83-4237-9A08-7AA977DA8EF6}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {2B478DDF-E3E5-4C83-B89B-1F1571A72B24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {2B478DDF-E3E5-4C83-B89B-1F1571A72B24}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {2B478DDF-E3E5-4C83-B89B-1F1571A72B24}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {2B478DDF-E3E5-4C83-B89B-1F1571A72B24}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {8BE9362E-EEC7-4973-9B93-B8EF0F2EBBAD} 54 | EndGlobalSection 55 | GlobalSection(SubversionScc) = preSolution 56 | Svn-Managed = True 57 | Manager = AnkhSVN - Subversion Support for Visual Studio 58 | EndGlobalSection 59 | EndGlobal 60 | -------------------------------------------------------------------------------- /src/TDSProxy.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | ASCII 3 | DB 4 | EBCDIC 5 | EP 6 | ID 7 | IEEE 8 | LCID 9 | ND 10 | ODBC 11 | OLEDB 12 | SMP 13 | SPID 14 | SQL 15 | SSL 16 | SSPI 17 | TDS 18 | TSQL 19 | VAX 20 | True 21 | True 22 | True 23 | True 24 | True 25 | True 26 | True -------------------------------------------------------------------------------- /src/TDSProxy/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/TDSProxy/Configuration.xsd: -------------------------------------------------------------------------------- 1 |  2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/TDSProxy/Configuration/AuthenticatorCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using JetBrains.Annotations; 8 | 9 | namespace TDSProxy.Configuration 10 | { 11 | public class AuthenticatorCollection : ConfigurationElementCollection 12 | { 13 | [UsedImplicitly] 14 | public AuthenticatorElement this[int index] 15 | { 16 | get => (AuthenticatorElement)BaseGet(index); 17 | set 18 | { 19 | if (null != BaseGet(index)) BaseRemoveAt(index); 20 | BaseAdd(index, value); 21 | } 22 | } 23 | 24 | protected override ConfigurationElement CreateNewElement() => new AuthenticatorElement(); 25 | 26 | protected override object GetElementKey(ConfigurationElement element) => ((AuthenticatorElement)element).Name; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/TDSProxy/Configuration/AuthenticatorElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using JetBrains.Annotations; 4 | 5 | namespace TDSProxy.Configuration 6 | { 7 | public class AuthenticatorElement : ConfigurationElement 8 | { 9 | [ConfigurationProperty("name", IsKey = true, IsRequired = true), UsedImplicitly] 10 | public string Name 11 | { 12 | get => (string)base["name"]; 13 | set => base["name"] = value; 14 | } 15 | 16 | [ConfigurationProperty("dll", IsRequired = true), UsedImplicitly] 17 | public string Dll 18 | { 19 | get => (string)base["dll"]; 20 | set => base["dll"] = value; 21 | } 22 | 23 | [ConfigurationProperty("class", IsRequired = true), UsedImplicitly] 24 | public string Class 25 | { 26 | get => (string)base["class"]; 27 | set => base["class"] = value; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TDSProxy/Configuration/IPAddressConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Net; 4 | 5 | namespace TDSProxy.Configuration 6 | { 7 | public class IPAddressConverter : TypeConverter 8 | { 9 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 10 | { 11 | return typeof(string) == sourceType|| typeof(IPAddress).IsAssignableFrom(sourceType) || base.CanConvertFrom(context, sourceType); 12 | } 13 | 14 | public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 15 | { 16 | if (value is string strValue) 17 | return IPAddress.Parse(strValue); 18 | if (value is IPAddress ipAddressValue) 19 | return ipAddressValue; 20 | return base.ConvertFrom(context, culture, value); 21 | } 22 | 23 | public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) 24 | { 25 | return typeof(string) == destinationType || destinationType.IsAssignableFrom(typeof(IPAddress)) || base.CanConvertTo(context, destinationType); 26 | } 27 | 28 | public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 29 | { 30 | if (null == destinationType) 31 | throw new ArgumentNullException(nameof(destinationType)); 32 | if (value is IPAddress ipAddress) 33 | { 34 | if (typeof(string) == destinationType) 35 | return ipAddress.ToString(); 36 | if (destinationType.IsAssignableFrom(typeof(IPAddress))) 37 | return ipAddress; 38 | } 39 | return base.ConvertTo(context, culture, value, destinationType); 40 | } 41 | 42 | public override bool IsValid(ITypeDescriptorContext context, object value) 43 | { 44 | var valueStr = value as string; 45 | if (null == valueStr) 46 | return value is IPAddress; 47 | IPAddress dummy; 48 | return IPAddress.TryParse(valueStr, out dummy); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/TDSProxy/Configuration/ListenerCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | 4 | namespace TDSProxy.Configuration 5 | { 6 | public class ListenerCollection : ConfigurationElementCollection 7 | { 8 | // ReSharper disable once UnusedMember.Global 9 | public ListenerElement this[int index] 10 | { 11 | get => (ListenerElement)BaseGet(index); 12 | set 13 | { 14 | if (null != BaseGet(index)) 15 | BaseRemoveAt(index); 16 | base.BaseAdd(index, value); 17 | } 18 | } 19 | 20 | protected override ConfigurationElement CreateNewElement() 21 | { 22 | return new ListenerElement(); 23 | } 24 | 25 | protected override object GetElementKey(ConfigurationElement element) 26 | { 27 | return ((ListenerElement)element).Name; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TDSProxy/Configuration/ListenerElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Configuration; 4 | using System.Net; 5 | using System.Security.Cryptography.X509Certificates; 6 | using JetBrains.Annotations; 7 | 8 | namespace TDSProxy.Configuration 9 | { 10 | public class ListenerElement : ConfigurationElement 11 | { 12 | private const string ByteValueRegex = @"(?:[01]?[0-9]{1,2}|2(?:[0-4][0-9]|5[0-5]))"; 13 | private const string Ipv4Regex = @"(?:" + ByteValueRegex + @"\.){3}" + ByteValueRegex; 14 | private const string QuadRegex = @"(?:[0-9a-fA-F]{1,4})"; 15 | private const string ColonQuadRegex = @"(?:\:[0-9a-fA-F]{1,4})"; 16 | private const string QuadColonRegex = @"(?:[0-9a-fA-F]{1,4}:)"; 17 | private const string Ipv6Regex = 18 | @"(?:" + 19 | // fully fleshed-out IPv6 20 | QuadColonRegex + @"{7}" + QuadRegex + @"|" + 21 | // compressed simple IPv6 with trailing 0s 22 | QuadColonRegex + @"{0,6}" + QuadRegex + @"?\::|" + 23 | // compressed simple IPv6 with 1 final non-0 group 24 | @"(?:" + QuadColonRegex + @"{0,5}" + QuadRegex + @")?\:" + ColonQuadRegex + @"|" + 25 | // compressed simple IPv6 with 2 final non-0 groups 26 | @"(?:" + QuadColonRegex + @"{0,4}" + QuadRegex + @")?\:" + ColonQuadRegex + @"{2}|" + 27 | // compressed simple IPv6 with 3 final non-0 groups 28 | @"(?:" + QuadColonRegex + @"{0,3}" + QuadRegex + @")?\:" + ColonQuadRegex + @"{3}|" + 29 | // compressed simple IPv6 with 4 final non-0 groups 30 | @"(?:" + QuadColonRegex + @"{0,2}" + QuadRegex + @")?\:" + ColonQuadRegex + @"{4}|" + 31 | // compressed simple IPv6 with 5 final non-0 groups 32 | @"(?:" + QuadColonRegex + @"?" + QuadRegex + @")?\:" + ColonQuadRegex + @"{5}|" + 33 | // compressed simple IPv6 with 6 final non-0 groups 34 | QuadRegex + @"?\:" + ColonQuadRegex + @"{6}|" + 35 | // compressed simple IPv6 with 7 final non-0 groups 36 | @":" + ColonQuadRegex + @"{7}|" + 37 | // fully fleshed out IPv6 + IPv4 38 | QuadColonRegex + @"{6}" + Ipv4Regex + @"|" + 39 | // IPv6 + IPv4 compressed immediately before IPv4 part 40 | @"(?:" + QuadColonRegex + @"{0,4}" + QuadRegex + @")?\::" + Ipv4Regex + @"|" + 41 | // IPv6 + IPv4 compressed then 1 group before IPv4 part 42 | @"(?:" + QuadColonRegex + @"{0,3}" + QuadRegex + @")?\::" + QuadColonRegex + Ipv4Regex + @"|" + 43 | // IPv6 + IPv4 compressed then 2 groups before IPv4 part 44 | @"(?:" + QuadColonRegex + @"{0,2}" + QuadRegex + @")?\::" + QuadColonRegex + @"{2}" + Ipv4Regex + @"|" + 45 | // IPv6 + IPv4 compressed then 3 groups before IPv4 part 46 | @"(?:" + QuadColonRegex + @"?" + QuadRegex + @")?\::" + QuadColonRegex + @"{3}" + Ipv4Regex + @"|" + 47 | // IPv6 + IPv4 compressed then 4 groups before IPv4 part 48 | QuadRegex + @"?\::" + QuadColonRegex + @"{4}" + Ipv4Regex + @"|" + 49 | // IPv6 + IPv4 compressed then 5 groups before IPv4 part 50 | @"::" + QuadColonRegex + @"{5}" + Ipv4Regex + 51 | @")"; 52 | private const string IPAddressRegex = @"(?:" + Ipv4Regex + @"|" + Ipv6Regex + ")"; 53 | private const string HostnameLabel = @"(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)"; 54 | private const string HostnameRegex = @"(?:" + HostnameLabel + @"\.)*" + HostnameLabel; 55 | private const string HostRegex = "(?:" + HostnameRegex + "|" + IPAddressRegex + ")"; 56 | private const string AnchoredHostRegex = "^" + HostRegex + "$"; 57 | 58 | [ConfigurationProperty("name", IsKey = true, IsRequired = true)] 59 | public string Name 60 | { 61 | get => (string)base["name"]; 62 | [UsedImplicitly] set => base["name"] = value; 63 | } 64 | 65 | [ConfigurationProperty("bindToAddress", IsRequired = false)] 66 | [TypeConverter(typeof(IPAddressConverter))] 67 | public IPAddress BindToAddress 68 | { 69 | get => (IPAddress)base["bindToAddress"]; 70 | [UsedImplicitly] set => base["bindToAddress"] = value; 71 | } 72 | 73 | [ConfigurationProperty("listenOnPort", IsRequired = true)] 74 | public ushort ListenOnPort 75 | { 76 | get => (ushort?)base["listenOnPort"] ?? 0; 77 | [UsedImplicitly] set => base["listenOnPort"] = value; 78 | } 79 | 80 | [ConfigurationProperty("forwardToHost", IsRequired = true)] 81 | //[RegexStringValidator(AnchoredHostRegex)] 82 | public string ForwardToHost 83 | { 84 | get => (string)base["forwardToHost"]; 85 | [UsedImplicitly] set => base["forwardToHost"] = value; 86 | } 87 | 88 | [ConfigurationProperty("forwardToPort", IsRequired = true)] 89 | public ushort ForwardToPort 90 | { 91 | get => (ushort?)base["forwardToPort"] ?? 0; 92 | [UsedImplicitly] set => base["forwardToPort"] = value; 93 | } 94 | 95 | [ConfigurationProperty("sslCertStoreName", IsRequired = true)] 96 | public StoreName SslCertStoreName 97 | { 98 | get => (StoreName)base["sslCertStoreName"]; 99 | [UsedImplicitly] set => base["sslCertStoreName"] = value; 100 | } 101 | 102 | [ConfigurationProperty("sslCertStoreLocation", IsRequired = true)] 103 | public StoreLocation SslCertStoreLocation 104 | { 105 | get => (StoreLocation)base["sslCertStoreLocation"]; 106 | [UsedImplicitly] set => base["sslCertStoreLocation"] = value; 107 | } 108 | 109 | [ConfigurationProperty("sslCertSubjectThumbprint", IsRequired = true)] 110 | public string SslCertSubjectThumbprint 111 | { 112 | get => (string)base["sslCertSubjectThumbprint"]; 113 | [UsedImplicitly] set => base["sslCertSubjectThumbprint"] = value; 114 | } 115 | 116 | [ConfigurationProperty("authenticators", IsDefaultCollection = true, IsRequired = true)] 117 | [ConfigurationCollection(typeof(AuthenticatorCollection))] 118 | public AuthenticatorCollection Authenticators => (AuthenticatorCollection)base["authenticators"]; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/TDSProxy/Configuration/TdsProxySection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Configuration; 4 | using JetBrains.Annotations; 5 | 6 | namespace TDSProxy.Configuration 7 | { 8 | public class TdsProxySection : ConfigurationSection 9 | { 10 | [Browsable(false)] 11 | [EditorBrowsable(EditorBrowsableState.Never)] 12 | [ConfigurationProperty("xmlns")] 13 | [UsedImplicitly] 14 | public string Xmlns 15 | { 16 | get => (string)base["xmlns"]; 17 | set => base["xmlns"] = value; 18 | } 19 | 20 | [ConfigurationProperty("listeners", IsDefaultCollection = true, IsRequired = true)] 21 | [ConfigurationCollection(typeof(ListenerCollection))] 22 | public ListenerCollection Listeners => (ListenerCollection)base["listeners"]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/TDSProxy/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ServiceProcess; 3 | 4 | namespace TDSProxy 5 | { 6 | static class Program 7 | { 8 | /// 9 | /// The main entry point for the application. 10 | /// 11 | static void Main(string[] args) 12 | { 13 | Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory; 14 | 15 | var service = new TDSProxyService(); 16 | if (Environment.UserInteractive) 17 | { 18 | service.Start(args); 19 | Console.Write("Press ESC to end..."); 20 | while (Console.ReadKey(false).Key != ConsoleKey.Escape) {} 21 | service.Stop(); 22 | } 23 | else 24 | { 25 | ServiceBase.Run(new TDSProxyService()); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/TDSProxy/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TDSProxy")] 8 | [assembly: AssemblyDescription("SQL Server TDS protocol proxy")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Tech Software LLC")] 11 | [assembly: AssemblyProduct("TDSProxy")] 12 | [assembly: AssemblyCopyright("Copyright © Tech Software LLC, 2014-2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("28b760c8-7558-4c28-8bdf-ff78228f1847")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | 37 | [assembly: log4net.Config.XmlConfigurator(ConfigFileExtension = "log4net.config", Watch = true)] 38 | -------------------------------------------------------------------------------- /src/TDSProxy/TDSListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.ComponentModel.Composition; 5 | using System.ComponentModel.Composition.Hosting; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Security.Cryptography.X509Certificates; 10 | using TDSProxy.Authentication; 11 | using TDSProxy.Configuration; 12 | 13 | namespace TDSProxy 14 | { 15 | class TDSListener : IDisposable 16 | { 17 | #region Log4Net 18 | static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 19 | #endregion 20 | 21 | readonly TDSProxyService _service; 22 | readonly TcpListener _tcpListener; 23 | readonly CompositionContainer _mefContainer; 24 | volatile bool _stopped; 25 | 26 | internal readonly X509Certificate Certificate; 27 | 28 | public TDSListener(TDSProxyService service, ListenerElement configuration) 29 | { 30 | var insideAddresses = Dns.GetHostAddresses(configuration.ForwardToHost); 31 | if (0 == insideAddresses.Length) 32 | { 33 | log.ErrorFormat("Unable to resolve forwardToHost=\"{0}\" for listener {1}", configuration.ForwardToHost, configuration.Name); 34 | _stopped = true; 35 | return; 36 | } 37 | ForwardTo = new IPEndPoint(insideAddresses.First(), configuration.ForwardToPort); 38 | 39 | _service = service; 40 | 41 | var bindToEP = new IPEndPoint(configuration.BindToAddress ?? IPAddress.Any, configuration.ListenOnPort); 42 | 43 | try 44 | { 45 | var catalog = new AggregateCatalog(from AuthenticatorElement a in configuration.Authenticators 46 | select new AssemblyCatalog(a.Dll)); 47 | _mefContainer = new CompositionContainer(catalog); 48 | 49 | _authenticators = _mefContainer.GetExports().ToArray(); 50 | if (!_authenticators.Any()) 51 | { 52 | throw new InvalidOperationException("No authenticators"); 53 | } 54 | } 55 | catch (CompositionException ce) 56 | { 57 | log.Error( 58 | "Failed to find an authenticator. Composition errors:\r\n\t" + 59 | string.Join("\r\n\t", ce.Errors.Select(err => "Element: " + err.Element.DisplayName + ", Error: " + err.Description)), 60 | ce); 61 | Dispose(); 62 | return; 63 | } 64 | catch (Exception e) 65 | { 66 | log.Error("Failed to find an authenticator", e); 67 | Dispose(); 68 | return; 69 | } 70 | 71 | try 72 | { 73 | log.DebugFormat("Opening SSL certificate store {0}.{1}", configuration.SslCertStoreLocation, configuration.SslCertStoreName); 74 | var store = new X509Store(configuration.SslCertStoreName, configuration.SslCertStoreLocation); 75 | store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); 76 | var matching = store.Certificates.Find(X509FindType.FindByThumbprint, configuration.SslCertSubjectThumbprint, false); 77 | if (0 == matching.Count) 78 | { 79 | log.ErrorFormat( 80 | "Failed to find SSL certification with thumbprint '{0}' in location {1}, store {2}.", 81 | configuration.SslCertSubjectThumbprint, 82 | configuration.SslCertStoreLocation, 83 | configuration.SslCertStoreName); 84 | Dispose(); 85 | return; 86 | } 87 | Certificate = matching[0]; 88 | } 89 | catch (Exception e) 90 | { 91 | log.Error("Failed to load SSL certificate", e); 92 | Dispose(); 93 | return; 94 | } 95 | 96 | _tcpListener = new TcpListener(bindToEP); 97 | _tcpListener.Start(); 98 | _tcpListener.BeginAcceptTcpClient(AcceptConnection, _tcpListener); 99 | 100 | _service.AddListener(this); 101 | 102 | log.InfoFormat( 103 | "Listening on {0} and forwarding to {1} (SSL cert DN {2}; expires {5} serial {3}; authenticators {4})", 104 | bindToEP, 105 | ForwardTo, 106 | Certificate.Subject, 107 | Certificate.GetSerialNumberString(), 108 | string.Join(", ", from a in Authenticators select a.GetType().FullName), 109 | Certificate.GetExpirationDateString()); 110 | } 111 | 112 | //public IAuthenticator Authenticator { get; private set; } 113 | private Lazy[] _authenticators; 114 | 115 | public IEnumerable> Authenticators => 116 | !_stopped 117 | ? (Lazy[])_authenticators.Clone() 118 | : throw new ObjectDisposedException(nameof(TDSListener)); 119 | 120 | // ReSharper disable once MemberCanBePrivate.Global 121 | public IPEndPoint ForwardTo { get; } 122 | 123 | private void AcceptConnection(IAsyncResult result) 124 | { 125 | try 126 | { 127 | // Get connection 128 | TcpClient readClient = ((TcpListener)result.AsyncState).EndAcceptTcpClient(result); 129 | 130 | //Log as Info so we have the open (and the close elsewhere) 131 | log.InfoFormat("Accepted connection from {0} on {1}, will forward to {2}", readClient.Client.RemoteEndPoint, readClient.Client.LocalEndPoint, ForwardTo); 132 | 133 | // Handle stop requested 134 | if (_service?.StopRequested == true) 135 | { 136 | log.Info("Service was ending, closing connection and returning."); 137 | readClient.Close(); 138 | return; 139 | } 140 | 141 | // Process this connection 142 | // ReSharper disable once ObjectCreationAsStatement -- constructed object registers itself 143 | new TDSConnection(_service, this, readClient, ForwardTo); 144 | } 145 | catch (ObjectDisposedException) { /* We're shutting down, ignore */ } 146 | catch (Exception e) 147 | { 148 | log.Fatal("Error in AcceptConnection.", e); 149 | } 150 | 151 | // Listen for next connection -- Do this here so we accept new connections even if this attempt to accept failed. 152 | _tcpListener.BeginAcceptTcpClient(AcceptConnection, _tcpListener); 153 | } 154 | 155 | public void Dispose() 156 | { 157 | if (!_stopped) 158 | { 159 | _stopped = true; 160 | _service?.RemoveListener(this); 161 | _tcpListener?.Stop(); 162 | if (null != _mefContainer) 163 | { 164 | if (null != _authenticators) 165 | _mefContainer.ReleaseExports(_authenticators); 166 | _mefContainer.Dispose(); 167 | } 168 | _authenticators = null; 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/TDSProxy/TDSProxy.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0AE9E17C-C69B-44E9-A923-6B9B3D62BD40} 8 | Exe 9 | Properties 10 | TDSProxy 11 | TDSProxy 12 | v4.8 13 | 512 14 | 15 | 16 | 17 | x64 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | false 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Component 64 | 65 | 66 | TDSProxyService.cs 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Designer 75 | 76 | 77 | Designer 78 | 79 | 80 | 81 | 82 | {99fcff04-2f8e-4ac9-8136-c2574d1e0f2f} 83 | TDSProtocol 84 | 85 | 86 | {87a5b526-ec83-4237-9a08-7aa977da8ef6} 87 | TDSProxy.Authentication 88 | 89 | 90 | 91 | 92 | TDSProxyService.cs 93 | 94 | 95 | 96 | 97 | 2021.3.0 98 | 99 | 100 | 2.0.13 101 | 102 | 103 | 104 | 105 | copy /y $(ProjectDir)log4net.config $(TargetPath).log4net.config 106 | 107 | 114 | -------------------------------------------------------------------------------- /src/TDSProxy/TDSProxyService.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace TDSProxy 2 | { 3 | sealed partial class TDSProxyService 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | // 32 | // TDSProxyService 33 | // 34 | this.CanPauseAndContinue = true; 35 | this.ServiceName = "TDSProxy"; 36 | 37 | } 38 | 39 | #endregion 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/TDSProxy/TDSProxyService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Net; 6 | using System.ServiceProcess; 7 | 8 | namespace TDSProxy 9 | { 10 | public sealed partial class TDSProxyService : ServiceBase 11 | { 12 | #region Log4Net 13 | static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 14 | #endregion 15 | 16 | public static bool VerboseLogging { get; private set; } 17 | public static bool VerboseLoggingInWrapper { get; private set; } 18 | public static bool SkipLoginProcessing { get; private set; } 19 | public static bool AllowUnencryptedConnections { get; private set; } 20 | 21 | private readonly HashSet _listeners = new HashSet(); 22 | 23 | private bool _stopRequested; 24 | 25 | private static Configuration.TdsProxySection _configuration; 26 | // ReSharper disable once MemberCanBePrivate.Global 27 | public static Configuration.TdsProxySection Configuration 28 | { 29 | get 30 | { 31 | if (null == _configuration) 32 | try 33 | { 34 | _configuration = (Configuration.TdsProxySection)ConfigurationManager.GetSection("tdsProxy"); 35 | } 36 | catch (Exception e) 37 | { 38 | log.Error("Error reading configuration", e); 39 | throw; 40 | } 41 | return _configuration; 42 | } 43 | } 44 | 45 | public TDSProxyService() 46 | { 47 | InitializeComponent(); 48 | } 49 | 50 | protected override void OnStart(string[] args) => Start(args); 51 | 52 | public void Start(string[] args) 53 | { 54 | log.InfoFormat( 55 | "\r\n-----------------\r\nService Starting on {0} with security protocol {1}.\r\n-----------------\r\n", 56 | AppContext.TargetFrameworkName, 57 | ServicePointManager.SecurityProtocol); 58 | 59 | if (args.Any(a => string.Equals(a, "debug", StringComparison.OrdinalIgnoreCase))) 60 | { 61 | log.Info("Calling Debugger.Break()"); 62 | System.Diagnostics.Debugger.Break(); 63 | } 64 | 65 | VerboseLogging = args.Any(a => string.Equals(a, "verbose", StringComparison.OrdinalIgnoreCase)); 66 | if (VerboseLogging) 67 | log.Debug("Verbose logging is on."); 68 | 69 | // ReSharper disable once StringLiteralTypo 70 | VerboseLoggingInWrapper = args.Any(a => string.Equals(a, "wrapperverbose", StringComparison.OrdinalIgnoreCase)); 71 | if (VerboseLoggingInWrapper) 72 | log.Debug("Verbose logging is on in TDS/SSL wrapper."); 73 | 74 | // ReSharper disable once StringLiteralTypo 75 | TDSProtocol.TDSPacket.DumpPackets = args.Any(a => string.Equals(a, "packetdump", StringComparison.OrdinalIgnoreCase)); 76 | if (TDSProtocol.TDSPacket.DumpPackets) 77 | log.Debug("Packet dumping is on."); 78 | 79 | // ReSharper disable once StringLiteralTypo 80 | SkipLoginProcessing = args.Any(a => string.Equals(a, "skiplogin", StringComparison.OrdinalIgnoreCase)); 81 | if (SkipLoginProcessing) 82 | log.Debug("Skipping login processing."); 83 | 84 | // ReSharper disable once StringLiteralTypo 85 | AllowUnencryptedConnections = args.Any(a => string.Equals(a, "allowunencrypted", StringComparison.OrdinalIgnoreCase)); 86 | if (AllowUnencryptedConnections) 87 | log.Debug("Allowing unencrypted connections (but encryption must be supported because we will not allow unencrypted login)."); 88 | 89 | _stopRequested = false; 90 | 91 | StartListeners(); 92 | 93 | log.Info("TDSProxyService initialization complete."); 94 | } 95 | 96 | protected override void OnStop() => Stop(); 97 | 98 | public new void Stop() 99 | { 100 | log.Info("Stopping TDSProxyService"); 101 | LogStats(); 102 | _stopRequested = true; 103 | StopListeners(); 104 | OnStopping(EventArgs.Empty); 105 | log.Info("\r\n----------------\r\nService stopped.\r\n----------------\r\n"); 106 | } 107 | 108 | public bool StopRequested => _stopRequested; 109 | 110 | protected override void OnPause() 111 | { 112 | StopListeners(); 113 | log.Info("Service paused."); 114 | } 115 | 116 | protected override void OnContinue() 117 | { 118 | log.Info("Resuming service."); 119 | RefreshConfiguration(); 120 | StartListeners(); 121 | } 122 | 123 | protected override void OnCustomCommand(int command) 124 | { 125 | if (_stopRequested) 126 | return; 127 | 128 | switch (command) 129 | { 130 | case 200: 131 | LogStats(); 132 | break; 133 | case 201: 134 | StopListeners(); 135 | RefreshConfiguration(); 136 | StartListeners(); 137 | break; 138 | } 139 | } 140 | 141 | public event EventHandler Stopping; 142 | 143 | private void OnStopping(EventArgs e) => Stopping?.Invoke(this, e); 144 | 145 | private void StartListeners() 146 | { 147 | foreach (Configuration.ListenerElement listenerConfig in Configuration.Listeners) 148 | // ReSharper disable once ObjectCreationAsStatement -- constructed object registers itself 149 | new TDSListener(this, listenerConfig); 150 | } 151 | 152 | private void StopListeners() 153 | { 154 | List listeners; 155 | lock (_listeners) 156 | listeners = new List(_listeners); 157 | 158 | // NOTE: listeners de-register themselves 159 | foreach (var listener in listeners) 160 | listener.Dispose(); 161 | } 162 | 163 | private void RefreshConfiguration() 164 | { 165 | ConfigurationManager.RefreshSection("tdsProxy"); 166 | _configuration = null; 167 | } 168 | 169 | private void LogStats() 170 | { 171 | log.InfoFormat( 172 | "{0} active connections ({1} connections started since last restart, {2} connections collected without being closed first)", 173 | TDSConnection.ActiveConnectionCount, 174 | TDSConnection.TotalConnections, 175 | TDSConnection.UnclosedCollections); 176 | } 177 | 178 | internal void AddListener(TDSListener listener) 179 | { 180 | lock(_listeners) 181 | _listeners.Add(listener); 182 | } 183 | 184 | internal void RemoveListener(TDSListener listener) 185 | { 186 | lock (_listeners) 187 | _listeners.Remove(listener); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/TDSProxy/TDSProxyService.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | False 122 | 123 | -------------------------------------------------------------------------------- /src/TDSProxy/log4net.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/TestConnection/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/TestConnection/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.OleDb; 3 | 4 | namespace TestConnection 5 | { 6 | static class Program 7 | { 8 | static void Main() 9 | { 10 | var cn = new OleDbConnection("Provider=SQLNCLI11;Server=pc-mattw.techsoftwareinc.com;Database=IRBM_Dev;Uid=dev\\mattw;Pwd=dev;MARS_Connection=no"); 11 | // ReSharper disable CommentTypo 12 | //var cn = new OdbcConnection("Driver={SQL Server Native Client 11.0};Server=pc-mattw.techsoftwareinc.com;Database=IRBM_Dev;Uid=dev\\mattw;Pwd=dev;MARS_Connection=no"); 13 | //var cn = new SqlConnection("Server=pc-mattw.techsoftwareinc.com;Database=IRBM_Dev;User id=dev\\mattw;Password=dev;MultipleActiveResultSets=false"); 14 | // ReSharper restore CommentTypo 15 | cn.Open(); 16 | while (true) 17 | { 18 | var cmd = cn.CreateCommand(); 19 | cmd.CommandText = "SELECT * FROM Note"; 20 | var dr = cmd.ExecuteReader(); 21 | while (dr.Read()) 22 | { 23 | for (int i = 0; i < dr.FieldCount; i++) 24 | Console.Write("{0}\t", dr.GetValue(i)); 25 | Console.WriteLine(); 26 | } 27 | 28 | if (Console.ReadKey(false).Key == ConsoleKey.Escape) 29 | break; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TestConnection/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TestConnection")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Tech Software LLC")] 11 | [assembly: AssemblyProduct("TestConnection")] 12 | [assembly: AssemblyCopyright("Copyright © Tech Software LLC 2014-2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("177d5235-d152-4d56-a0d6-952bfe92ea5d")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/TestConnection/TestConnection.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2B478DDF-E3E5-4C83-B89B-1F1571A72B24} 8 | Exe 9 | Properties 10 | TestConnection 11 | TestConnection 12 | v4.8 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | --------------------------------------------------------------------------------