-
Notifications
You must be signed in to change notification settings - Fork 329
Expand file tree
/
Copy pathGenericTdsServer.cs
More file actions
1084 lines (878 loc) · 45 KB
/
GenericTdsServer.cs
File metadata and controls
1084 lines (878 loc) · 45 KB
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
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Threading;
using Microsoft.SqlServer.TDS.Authentication;
using Microsoft.SqlServer.TDS.Done;
using Microsoft.SqlServer.TDS.EndPoint;
using Microsoft.SqlServer.TDS.EndPoint.FederatedAuthentication;
using Microsoft.SqlServer.TDS.EndPoint.SSPI;
using Microsoft.SqlServer.TDS.EnvChange;
using Microsoft.SqlServer.TDS.Error;
using Microsoft.SqlServer.TDS.FeatureExtAck;
using Microsoft.SqlServer.TDS.Info;
using Microsoft.SqlServer.TDS.Login7;
using Microsoft.SqlServer.TDS.LoginAck;
using Microsoft.SqlServer.TDS.PreLogin;
using Microsoft.SqlServer.TDS.SSPI;
namespace Microsoft.SqlServer.TDS.Servers
{
/// <summary>
/// Generic TDS server without specialization
/// </summary>
public abstract class GenericTdsServer<T> : ITDSServer, IDisposable
where T : TdsServerArguments
{
/// <summary>
/// Delegate to be called when a LOGIN7 request has been received and is
/// validated. This is called before any authentication work is done,
/// and before any response is sent.
/// </summary>
public delegate void OnLogin7ValidatedDelegate(
TDSLogin7Token login7Token);
/// <summary>
/// Delegate to be called when authentication is completed and TDSResponse
/// message is sent to the client.
/// </summary>
public delegate void OnAuthenticationCompletedDelegate(
TDSMessage response);
/// <summary>
/// Default feature extension version supported on the server for vector support.
/// </summary>
public const byte DefaultSupportedVectorFeatureExtVersion = 0x01;
/// <summary>
/// Property for setting server version for vector feature extension.
/// </summary>
public bool EnableVectorFeatureExt { get; set; } = false;
/// <summary>
/// Property for setting server flag for user agent feature extension.
/// </summary>
public bool EnableUserAgentFeatureExt { get; set; } = false;
/// <summary>
/// Property for setting server enhanced routing enablement state.
/// </summary>
public FeatureExtensionBehavior EnhancedRoutingBehavior { get; set; } = FeatureExtensionBehavior.Disabled;
/// <summary>
/// Property for setting server version for vector feature extension.
/// </summary>
public byte ServerSupportedVectorFeatureExtVersion { get; set; } = DefaultSupportedVectorFeatureExtVersion;
/// <summary>
/// Property for setting server version for user agent feature extension.
/// </summary>
public byte ServerSupportedUserAgentFeatureExtVersion { get; set; } = DefaultSupportedUserAgentFeatureExtVersion;
/// <summary>
/// Client version for vector FeatureExtension.
/// </summary>
private byte _clientSupportedVectorFeatureExtVersion = 0;
/// <summary>
/// Default feature extension version supported on the server for user agent.
/// </summary>
public const byte DefaultSupportedUserAgentFeatureExtVersion = 0x01;
/// <summary>
/// Session counter
/// </summary>
private int _sessionCount = 0;
/// <summary>
/// Counts pre-login requests to the server.
/// </summary>
private int _preLoginCount = 0;
/// <summary>
/// Counts Login7 requests to the server.
/// </summary>
protected int _login7Count = 0;
private TDSServerEndPoint _endpoint;
/// <summary>
/// Initialization constructor
/// </summary>
public GenericTdsServer(T arguments) :
this(arguments, new QueryEngine(arguments))
{
}
/// <summary>
/// Initialization constructor
/// </summary>
public GenericTdsServer(T arguments, QueryEngine queryEngine)
{
// Save arguments
Arguments = arguments;
// Save "relational" query engine
Engine = queryEngine;
// Configure log for the query engine
Engine.Log = Arguments.Log;
}
public IPEndPoint EndPoint => _endpoint.ServerEndPoint;
/// <summary>
/// Server configuration
/// </summary>
protected T Arguments { get; set; }
/// <summary>
/// Query engine instance
/// </summary>
protected QueryEngine Engine { get; set; }
/// <summary>
/// Counts pre-login requests to the server.
/// </summary>
public int PreLoginCount => _preLoginCount;
/// <summary>
/// Counts Login7 requests to the server.
/// </summary>
public int Login7Count => _login7Count;
/// <summary>
/// Counts pre-login requests that did not result in a Login7 request,
/// which indicates the client abandoned the connection (e.g. interval
/// timer timeout during TNIR or failover).
/// </summary>
public int AbandonedPreLoginCount => _preLoginCount - _login7Count;
public OnAuthenticationCompletedDelegate OnAuthenticationResponseCompleted { private get; set; }
public OnLogin7ValidatedDelegate OnLogin7Validated { private get; set; }
public void Start([CallerMemberName] string methodName = "")
{
if (_endpoint != null)
{
throw new InvalidOperationException("Server is already started");
}
_endpoint = new TDSServerEndPoint(this) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) };
_endpoint.EndpointName = methodName;
_endpoint.EventLog = Arguments.Log;
_endpoint.Start();
}
/// <summary>
/// Create a new session on the server
/// </summary>
/// <returns>A new instance of the session</returns>
public virtual ITDSServerSession OpenSession()
{
// Atomically increment the session counter
Interlocked.Increment(ref _sessionCount);
// Create a new session
GenericTdsServerSession session = new GenericTdsServerSession(this, (uint)_sessionCount);
// Use configured encryption certificate and protocols
session.EncryptionCertificate = Arguments.EncryptionCertificate;
session.EncryptionProtocols = Arguments.EncryptionProtocols;
return session;
}
/// <summary>
/// Notify server of the session termination
/// </summary>
public virtual void CloseSession(ITDSServerSession session)
{
// Do nothing
}
/// <summary>
/// Handler for pre-login request
/// </summary>
public virtual TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, TDSMessage request)
{
Interlocked.Increment(ref _preLoginCount);
// Inflate pre-login request from the message
TDSPreLoginToken preLoginRequest = request[0] as TDSPreLoginToken;
GenericTdsServerSession genericTdsServerSession = session as GenericTdsServerSession;
// Log request
TDSUtilities.Log(Arguments.Log, "Request", preLoginRequest);
// Generate server response for encryption
TDSPreLoginTokenEncryptionType serverResponse = TDSUtilities.GetEncryptionResponse(preLoginRequest.Encryption, Arguments.Encryption);
// Update client state with encryption resolution
session.Encryption = TDSUtilities.ResolveEncryption(preLoginRequest.Encryption, serverResponse);
// Create TDS prelogin packet
TDSPreLoginToken preLoginToken = new TDSPreLoginToken(Arguments.ServerVersion, serverResponse, false); // TDS server doesn't support MARS
// Cache the received Nonce into the session
genericTdsServerSession.ClientNonce = preLoginRequest.Nonce;
// Check if the server has been started up as requiring FedAuth when choosing between SSPI and FedAuth
if (Arguments.FedAuthRequiredPreLoginOption == TdsPreLoginFedAuthRequiredOption.FedAuthRequired)
{
if (preLoginRequest.FedAuthRequired == TdsPreLoginFedAuthRequiredOption.FedAuthRequired)
{
// Set the FedAuthRequired option
preLoginToken.FedAuthRequired = TdsPreLoginFedAuthRequiredOption.FedAuthRequired;
}
// Keep the federated authentication required flag in the server session
genericTdsServerSession.FedAuthRequiredPreLoginServerResponse = preLoginToken.FedAuthRequired;
if (preLoginRequest.Nonce != null)
{
// Generate Server Nonce
preLoginToken.Nonce = _GenerateRandomBytes(32);
}
}
// Cache the server Nonce in a session
genericTdsServerSession.ServerNonce = preLoginToken.Nonce;
// Log response
TDSUtilities.Log(Arguments.Log, "Response", preLoginToken);
// Reset authentication information
session.SQLUserID = null;
session.NTUserAuthenticationContext = null;
// Respond with a single message that contains only one token
return new TDSMessageCollection(new TDSMessage(TDSMessageType.Response, preLoginToken));
}
/// <summary>
/// Handler for login request
/// </summary>
public virtual TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request)
{
Interlocked.Increment(ref _login7Count);
// Inflate login7 request from the message
TDSLogin7Token loginRequest = request[0] as TDSLogin7Token;
// Log request
TDSUtilities.Log(Arguments.Log, "Request", loginRequest);
// Update server context
session.Database = string.IsNullOrEmpty(loginRequest.Database) ? "master" : loginRequest.Database;
// Resolve TDS version
session.TDSVersion = TDSVersion.Resolve(TDSVersion.GetTDSVersion(Arguments.ServerVersion), loginRequest.TDSVersion);
// Check for the TDS version
TDSMessageCollection collection = CheckTDSVersion(session);
// Check if any errors are posted
if (collection != null)
{
// Version check needs to send own message hence we can't proceed
return collection;
}
// Indicates federated authentication
bool bIsFedAuthConnection = false;
// Federated authentication option to be used later
TDSLogin7FedAuthOptionToken federatedAuthenticationOption = null;
// Check if feature extension block is available
if (loginRequest.FeatureExt != null)
{
// Go over the feature extension data
foreach (TDSLogin7FeatureOptionToken option in loginRequest.FeatureExt)
{
// Check option type
switch (option.FeatureID)
{
case TDSFeatureID.SessionRecovery:
{
// Enable session recovery
session.IsSessionRecoveryEnabled = true;
// Cast to session state options
TDSLogin7SessionRecoveryOptionToken sessionStateOption = option as TDSLogin7SessionRecoveryOptionToken;
// Inflate session state
(session as GenericTdsServerSession).Inflate(sessionStateOption.Initial, sessionStateOption.Current);
break;
}
case TDSFeatureID.FederatedAuthentication:
{
// Cast to federated authentication option
federatedAuthenticationOption = option as TDSLogin7FedAuthOptionToken;
// Mark authentication as federated
bIsFedAuthConnection = true;
// Validate federated authentication option
collection = CheckFederatedAuthenticationOption(session, option as TDSLogin7FedAuthOptionToken);
if (collection != null)
{
// Version error happened.
return collection;
}
// Save the fed auth library to be used
(session as GenericTdsServerSession).FederatedAuthenticationLibrary = federatedAuthenticationOption.Library;
break;
}
case TDSFeatureID.JsonSupport:
{
// Enable Json Support
session.IsJsonSupportEnabled = true;
break;
}
case TDSFeatureID.VectorSupport:
{
if (EnableVectorFeatureExt)
{
// Enable Vector Support
session.IsVectorSupportEnabled = true;
_clientSupportedVectorFeatureExtVersion = ((TDSLogin7GenericOptionToken)option).Data[0];
}
break;
}
case TDSFeatureID.UserAgentSupport:
{
if (EnableUserAgentFeatureExt)
{
// Enable User Agent Support
session.IsUserAgentSupportEnabled = true;
}
break;
}
case TDSFeatureID.EnhancedRoutingSupport:
{
session.IsEnhancedRoutingSupportRequested = true;
break;
}
default:
{
// Do nothing
break;
}
}
}
}
// Pass the packet to our delegate if we have one.
OnLogin7Validated?.Invoke(loginRequest);
// Check if SSPI authentication is requested
if (loginRequest.OptionalFlags2.IntegratedSecurity == TDSLogin7OptionalFlags2IntSecurity.On)
{
// Delegate to SSPI authentication
return ContinueSSPIAuthentication(session, loginRequest.SSPI);
}
// If it is not a FedAuth connection or the server has been started up as not supporting FedAuth, just ignore the FeatureExtension
// Yes unfortunately for the fake server, supporting FedAuth = Requiring FedAuth
if (!bIsFedAuthConnection
|| Arguments.FedAuthRequiredPreLoginOption == TdsPreLoginFedAuthRequiredOption.FedAuthNotRequired)
{
// We use SQL authentication
session.SQLUserID = loginRequest.UserID;
// Process with the SQL login.
return OnSqlAuthenticationCompleted(session);
}
else
{
// Fedauth feature extension is present and server has been started up as Requiring (or Supporting) FedAuth
if (federatedAuthenticationOption.IsRequestingAuthenticationInfo)
{
// Must provide client with more info before completing authentication
return OnFederatedAuthenticationInfoRequest(session);
}
else
{
return OnFederatedAuthenticationCompleted(session, federatedAuthenticationOption.Token);
}
}
}
public virtual TDSMessageCollection OnFederatedAuthenticationTokenMessage(ITDSServerSession session, TDSMessage message)
{
// Get the FedAuthToken
TDSFedAuthToken fedauthToken = message[0] as TDSFedAuthToken;
// Log
TDSUtilities.Log(Arguments.Log, "Request", fedauthToken);
return OnFederatedAuthenticationCompleted(session, fedauthToken.Token);
}
/// <summary>
/// Handler for SSPI request
/// </summary>
public virtual TDSMessageCollection OnSSPIRequest(ITDSServerSession session, TDSMessage request)
{
// Get the SSPI token
TDSSSPIClientToken sspiRequest = request[0] as TDSSSPIClientToken;
// Log request
TDSUtilities.Log(Arguments.Log, "Request", sspiRequest.Payload);
// Delegate to SSPI routine
return ContinueSSPIAuthentication(session, sspiRequest.Payload);
}
/// <summary>
/// It is called when SQL batch request arrives
/// </summary>
public virtual TDSMessageCollection OnSQLBatchRequest(ITDSServerSession session, TDSMessage message)
{
// Delegate to the query engine
TDSMessageCollection responseMessage = Engine.ExecuteBatch(session, message);
// Check if session packet size is different than the engine packet size
if (session.PacketSize != Arguments.PacketSize)
{
// Get the first message
TDSMessage firstMessage = responseMessage[0];
// Find DONE token in it
int indexOfDone = firstMessage.IndexOf(firstMessage.Where(t => t is TDSDoneToken).First());
// Create new packet size environment change token
TDSEnvChangeToken envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.PacketSize, Arguments.PacketSize.ToString(), session.PacketSize.ToString());
// Log response
TDSUtilities.Log(Arguments.Log, "Response", envChange);
// Insert env change before done token
firstMessage.Insert(indexOfDone, envChange);
// Update session with the new packet size
session.PacketSize = (uint)Arguments.PacketSize;
}
return responseMessage;
}
/// <summary>
/// It is called when attention arrives
/// </summary>
public virtual TDSMessageCollection OnAttention(ITDSServerSession session, TDSMessage message)
{
// Delegate into the query engine
return Engine.ExecuteAttention(session, message);
}
/// <summary>
/// Advances one step in SSPI authentication sequence
/// </summary>
protected virtual TDSMessageCollection ContinueSSPIAuthentication(ITDSServerSession session, byte[] payload)
{
// Response to send to the client
SSPIResponse response;
try
{
// Check if we have an SSPI context
if (session.NTUserAuthenticationContext == null)
{
// This is the first step so we need to create a server context and initialize it
session.NTUserAuthenticationContext = SSPIContext.CreateServer();
// Run the first step of authentication
response = session.NTUserAuthenticationContext.StartServerAuthentication(payload);
}
else
{
// Process SSPI request from the client
response = session.NTUserAuthenticationContext.ContinueServerAuthentication(payload);
}
}
catch (Exception e)
{
// Prepare ERROR token with the reason
TDSErrorToken errorToken = new TDSErrorToken(12345, 1, 15, "Failed to accept security SSPI context", Arguments.ServerName);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", errorToken);
// Serialize the error token into the response packet
TDSMessage responseErrorMessage = new TDSMessage(TDSMessageType.Response, errorToken);
// Prepare ERROR token with the final errorToken
errorToken = new TDSErrorToken(12345, 1, 15, e.Message, Arguments.ServerName);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", errorToken);
// Serialize the error token into the response packet
responseErrorMessage.Add(errorToken);
// Create DONE token
TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Error);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", doneToken);
// Serialize DONE token into the response packet
responseErrorMessage.Add(doneToken);
// Respond with a single message
return new TDSMessageCollection(responseErrorMessage);
}
// Message collection to respond with
TDSMessageCollection responseMessages = new TDSMessageCollection();
// Check if there's a response
if (response != null)
{
// Check if there's a payload
if (response.Payload != null)
{
// Create SSPI token
TDSSSPIToken sspiToken = new TDSSSPIToken(response.Payload);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", sspiToken);
// Prepare response message with a single response token
responseMessages.Add(new TDSMessage(TDSMessageType.Response, sspiToken));
// Check if we can complete login
if (!response.IsFinal)
{
// Send the message to the client
return responseMessages;
}
}
}
// Reset SQL user identifier since we're using NT authentication
session.SQLUserID = null;
// Append successfully authentication response
responseMessages.AddRange(OnAuthenticationCompleted(session));
// Return the message with SSPI token
return responseMessages;
}
protected virtual TDSMessageCollection OnFederatedAuthenticationInfoRequest(ITDSServerSession session)
{
TDSFedAuthInfoToken infoToken = new TDSFedAuthInfoToken();
// Make fake info options
TDSFedAuthInfoOptionSPN spn = new TDSFedAuthInfoOptionSPN(Arguments.ServerPrincipalName);
TDSFedAuthInfoOptionSTSURL stsurl = new TDSFedAuthInfoOptionSTSURL(Arguments.StsUrl);
infoToken.Options.Add(0, spn);
infoToken.Options.Add(1, stsurl);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", spn);
TDSUtilities.Log(Arguments.Log, "Response", stsurl);
TDSMessage infoMessage = new TDSMessage(TDSMessageType.FederatedAuthenticationInfo, infoToken);
return new TDSMessageCollection(infoMessage);
}
/// <summary>
/// Called by OnLogin7Request when client is using SQL auth. Overridden by subclasses to easily detect when SQL auth is used.
/// </summary>
protected virtual TDSMessageCollection OnSqlAuthenticationCompleted(ITDSServerSession session)
{
return OnAuthenticationCompleted(session);
}
protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSession session)
{
// Create new database environment change token
TDSEnvChangeToken envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.Database, session.Database, "master");
// Log response
TDSUtilities.Log(Arguments.Log, "Response", envChange);
// Serialize the login token into the response packet
TDSMessage responseMessage = new TDSMessage(TDSMessageType.Response, envChange);
// Create information token on the change
TDSInfoToken infoToken = new TDSInfoToken(5701, 2, 0, string.Format("Changed database context to '{0}'", envChange.NewValue), Arguments.ServerName);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", infoToken);
// Serialize the login token into the response packet
responseMessage.Add(infoToken);
// Create new collation change token
envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.SQLCollation, (session as GenericTdsServerSession).Collation);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", envChange);
// Serialize the login token into the response packet
responseMessage.Add(envChange);
// Create new language change token
envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.Language, LanguageString.ToString((session as GenericTdsServerSession).Language));
// Log response
TDSUtilities.Log(Arguments.Log, "Response", envChange);
// Serialize the login token into the response packet
responseMessage.Add(envChange);
// Create information token on the change
infoToken = new TDSInfoToken(5703, 1, 0, string.Format("Changed language setting to {0}", envChange.NewValue), Arguments.ServerName);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", infoToken);
// Serialize the login token into the response packet
responseMessage.Add(infoToken);
// Create new packet size environment change token
envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.PacketSize, Arguments.PacketSize.ToString(), Arguments.PacketSize.ToString());
// Log response
TDSUtilities.Log(Arguments.Log, "Response", envChange);
// Serialize the login token into the response packet
responseMessage.Add(envChange);
// Update session packet size
session.PacketSize = (uint)Arguments.PacketSize;
// Create login acknowledgement packet
TDSLoginAckToken loginResponseToken = new TDSLoginAckToken(Arguments.ServerVersion, session.TDSVersion, TDSLogin7TypeFlagsSQL.SQL, "Microsoft SQL Server"); // Otherwise SNAC yields E_FAIL
// Log response
TDSUtilities.Log(Arguments.Log, "Response", loginResponseToken);
// Serialize the login token into the response packet
responseMessage.Add(loginResponseToken);
CheckSessionRecovery(session, responseMessage);
CheckJsonSupported(session, responseMessage);
CheckVectorSupport(session, responseMessage);
CheckUserAgentSupport(session, responseMessage);
CheckEnhancedRoutingSupport(session, responseMessage);
if (!string.IsNullOrEmpty(Arguments.FailoverPartner))
{
envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.RealTimeLogShipping, Arguments.FailoverPartner);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", envChange);
responseMessage.Add(envChange);
}
// Create DONE token
TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", doneToken);
// Serialize DONE token into the response packet
responseMessage.Add(doneToken);
// Invoke delegate for response validation
OnAuthenticationResponseCompleted?.Invoke(responseMessage);
// Wrap a single message in a collection
return new TDSMessageCollection(responseMessage);
}
/// <summary>
/// Check if session recovery is enabled
/// </summary>
/// <param name="session">Server session</param>
/// <param name="responseMessage">Response message</param>
protected void CheckSessionRecovery(ITDSServerSession session, TDSMessage responseMessage)
{
// Check if session recovery is enabled
if (session.IsSessionRecoveryEnabled)
{
// Create Feature extension Ack token
TDSFeatureExtAckToken featureExtActToken = new TDSFeatureExtAckToken(new TDSFeatureExtAckSessionStateOption((session as GenericTdsServerSession).Deflate()));
// Log response
TDSUtilities.Log(Arguments.Log, "Response", featureExtActToken);
// Serialize feature extension token into the response
responseMessage.Add(featureExtActToken);
}
}
/// <summary>
/// Check if Json is supported
/// </summary>
/// <param name="session">Server session</param>
/// <param name="responseMessage">Response message</param>
protected void CheckJsonSupported(ITDSServerSession session, TDSMessage responseMessage)
{
// Check if Json is supported
if (session.IsJsonSupportEnabled)
{
// Create ack data (1 byte: Version number)
byte[] data = new byte[1];
data[0] = (byte)1;
// Create Json support as a generic feature extension option
TDSFeatureExtAckGenericOption jsonSupportOption = new TDSFeatureExtAckGenericOption(TDSFeatureID.JsonSupport, (uint)data.Length, data);
// Look for feature extension token
TDSFeatureExtAckToken featureExtAckToken = (TDSFeatureExtAckToken)responseMessage.Where(t => t is TDSFeatureExtAckToken).FirstOrDefault();
if (featureExtAckToken == null)
{
// Create feature extension ack token
featureExtAckToken = new TDSFeatureExtAckToken(jsonSupportOption);
responseMessage.Add(featureExtAckToken);
}
else
{
// Update the existing token
featureExtAckToken.Options.Add(jsonSupportOption);
}
}
}
/// <summary>
/// Check if Vector is supported
/// </summary>
/// <param name="session">Server session</param>
/// <param name="responseMessage">Response message</param>
protected void CheckVectorSupport(ITDSServerSession session, TDSMessage responseMessage)
{
// Check if Vector is supported
if (session.IsVectorSupportEnabled)
{
// Create ack data (1 byte: Version number)
byte[] data = new byte[1];
data[0] = ServerSupportedVectorFeatureExtVersion > _clientSupportedVectorFeatureExtVersion ? _clientSupportedVectorFeatureExtVersion : ServerSupportedVectorFeatureExtVersion;
// Create vector support as a generic feature extension option
TDSFeatureExtAckGenericOption vectorSupportOption = new TDSFeatureExtAckGenericOption(TDSFeatureID.VectorSupport, (uint)data.Length, data);
// Look for feature extension token
TDSFeatureExtAckToken featureExtAckToken = (TDSFeatureExtAckToken)responseMessage.Where(t => t is TDSFeatureExtAckToken).FirstOrDefault();
if (featureExtAckToken == null)
{
// Create feature extension ack token
featureExtAckToken = new TDSFeatureExtAckToken(vectorSupportOption);
responseMessage.Add(featureExtAckToken);
}
else
{
// Update the existing token
featureExtAckToken.Options.Add(vectorSupportOption);
}
}
}
/// <summary>
/// Check if UserAgent support is enabled
/// </summary>
/// <param name="session">Server session</param>
/// <param name="responseMessage">Response message</param>
protected void CheckUserAgentSupport(ITDSServerSession session, TDSMessage responseMessage)
{
// If tests request it, force an ACK for UserAgentSupport with no negotiation
if (session.IsUserAgentSupportEnabled)
{
// Create ack data (1 byte: Version number)
byte[] data = new byte[1];
data[0] = ServerSupportedUserAgentFeatureExtVersion;
// Create user agent support as a generic feature extension option
TDSFeatureExtAckGenericOption userAgentSupportOption = new TDSFeatureExtAckGenericOption(TDSFeatureID.UserAgentSupport, (uint)data.Length, data);
// Look for feature extension token
TDSFeatureExtAckToken featureExtAckToken = (TDSFeatureExtAckToken)responseMessage.Where(t => t is TDSFeatureExtAckToken).FirstOrDefault();
if (featureExtAckToken == null)
{
// Create feature extension ack token
featureExtAckToken = new TDSFeatureExtAckToken(userAgentSupportOption);
responseMessage.Add(featureExtAckToken);
}
else
{
// Update the existing token
featureExtAckToken.Options.Add(userAgentSupportOption);
}
}
}
/// <summary>
/// Check if Enhanced Routing support is enabled
/// </summary>
/// <param name="session">Server session</param>
/// <param name="responseMessage">Response message</param>
protected void CheckEnhancedRoutingSupport(ITDSServerSession session, TDSMessage responseMessage)
{
if (session.IsEnhancedRoutingSupportRequested &&
EnhancedRoutingBehavior != FeatureExtensionBehavior.DoNotAcknowledge)
{
// Create ack data (1 byte: IsEnabled)
byte[] data = EnhancedRoutingBehavior == FeatureExtensionBehavior.Enabled
? [1]
: [0];
// Create enhanced routing support as a generic feature extension option
TDSFeatureExtAckGenericOption enhancedRoutingSupportOption = new TDSFeatureExtAckGenericOption(TDSFeatureID.EnhancedRoutingSupport, (uint)data.Length, data);
// Look for feature extension token
TDSFeatureExtAckToken featureExtAckToken = (TDSFeatureExtAckToken)responseMessage.Where(t => t is TDSFeatureExtAckToken).FirstOrDefault();
if (featureExtAckToken == null)
{
// Create feature extension ack token
featureExtAckToken = new TDSFeatureExtAckToken(enhancedRoutingSupportOption);
responseMessage.Add(featureExtAckToken);
}
else
{
// Update the existing token
featureExtAckToken.Options.Add(enhancedRoutingSupportOption);
}
}
}
/// <summary>
/// Complete the Federated Login
/// </summary>
/// <param name="session">Server session</param>
/// <returns>Federated Login message collection</returns>
protected virtual TDSMessageCollection OnFederatedAuthenticationCompleted(ITDSServerSession session, byte[] ticket)
{
// Delegate to successful authentication routine
TDSMessageCollection responseMessageCollection = OnAuthenticationCompleted(session);
// Get the last message
TDSMessage targetMessage = responseMessageCollection.Last();
IFederatedAuthenticationTicket decryptedTicket = null;
try
{
// Get the Federated Authentication ticket using RPS
decryptedTicket = FederatedAuthenticationTicketService.DecryptTicket((session as GenericTdsServerSession).FederatedAuthenticationLibrary, ticket);
if (decryptedTicket is RpsTicket)
{
TDSUtilities.Log(Arguments.Log, "RPS ticket session key: ", (decryptedTicket as RpsTicket).sessionKey);
}
else if (decryptedTicket is JwtTicket)
{
TDSUtilities.Log(Arguments.Log, "JWT Ticket Received", null);
}
}
catch (Exception ex)
{
// Prepare ERROR token
TDSErrorToken errorToken = new TDSErrorToken(54879, 1, 20, "Authentication error in Federated Authentication Ticket Service: " + ex.Message, Arguments.ServerName);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", errorToken);
// Create DONE token
TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Error);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", doneToken);
// Return the message and stop processing request
return new TDSMessageCollection(new TDSMessage(TDSMessageType.Response, errorToken, doneToken));
}
// Create federated authentication extension option
TDSFeatureExtAckFederatedAuthenticationOption federatedAuthenticationOption;
if ((session as GenericTdsServerSession).FederatedAuthenticationLibrary == TDSFedAuthLibraryType.MSAL)
{
// For the time being, fake fedauth tokens are used for ADAL, so decryptedTicket is null.
federatedAuthenticationOption =
new TDSFeatureExtAckFederatedAuthenticationOption((session as GenericTdsServerSession).ClientNonce, null);
}
else
{
federatedAuthenticationOption =
new TDSFeatureExtAckFederatedAuthenticationOption((session as GenericTdsServerSession).ClientNonce,
decryptedTicket.GetSignature((session as GenericTdsServerSession).ClientNonce));
}
// Look for feature extension token
TDSFeatureExtAckToken featureExtActToken = (TDSFeatureExtAckToken)targetMessage.Where(t => t is TDSFeatureExtAckToken).FirstOrDefault();
// Check if response already contains federated authentication
if (featureExtActToken == null)
{
// Create Feature extension Ack token
featureExtActToken = new TDSFeatureExtAckToken(federatedAuthenticationOption);
// Serialize feature extension token into the response
// The last token is Done token, so we should put feautureextack token before done token
targetMessage.Insert(targetMessage.Count - 1, featureExtActToken);
}
else
{
// Update
featureExtActToken.Options.Add(federatedAuthenticationOption);
}
// Log response
TDSUtilities.Log(Arguments.Log, "Response", federatedAuthenticationOption);
// Wrap a message with a collection
return responseMessageCollection;
}
/// <summary>
/// Ensure that federated authentication option is valid
/// </summary>
protected virtual TDSMessageCollection CheckFederatedAuthenticationOption(ITDSServerSession session, TDSLogin7FedAuthOptionToken federatedAuthenticationOption)
{
// Check if server's prelogin response for FedAuthRequired prelogin option is echoed back correctly in FedAuth Feature Extenion Echo
if (federatedAuthenticationOption.Echo != (session as GenericTdsServerSession).FedAuthRequiredPreLoginServerResponse)
{
// Create Error message
string message =
string.Format("FEDAUTHREQUIRED option in the prelogin response is not echoed back correctly: in prelogin response, it is {0} and in login, it is {1}: ",
(session as GenericTdsServerSession).FedAuthRequiredPreLoginServerResponse,
federatedAuthenticationOption.Echo);
// Create errorToken token
TDSErrorToken errorToken = new TDSErrorToken(3456, 34, 23, message);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", errorToken);
// Create DONE token
TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Error);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", doneToken);
// Build a collection with a single message of two tokens
return new TDSMessageCollection(new TDSMessage(TDSMessageType.Response, errorToken, doneToken));
}
// Check if the nonce exists
if ((federatedAuthenticationOption.Nonce == null && federatedAuthenticationOption.Library == TDSFedAuthLibraryType.IDCRL)
|| !AreEqual((session as GenericTdsServerSession).ServerNonce, federatedAuthenticationOption.Nonce))
{
// Error message
string message = string.Format("Unexpected NONCEOPT specified in the Federated authentication feature extension");
// Create errorToken token
TDSErrorToken errorToken = new TDSErrorToken(5672, 32, 87, message);
// Log response
TDSUtilities.Log(Arguments.Log, "Response", errorToken);
// Create DONE token
TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Error);