├── .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 |
--------------------------------------------------------------------------------