Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
XmppS2SEndpoint.cs
1//#define LogToWebHookTester
2
3using System;
5using System.ComponentModel;
6using System.Security.Authentication;
8using System.Security.Cryptography.X509Certificates;
9using System.Text;
10using System.Threading;
11using System.Threading.Tasks;
12using System.Xml;
13using Waher.Content;
15using Waher.Events;
21using Waher.Security;
23
25{
30 {
34 Valid,
35
39 Invalid,
40
44 Error
45 }
46
51 {
52 private const int KeepAliveTimeSeconds = 30;
53 private const int MaxFragmentSize = 40000000;
54
55 private readonly Dictionary<string, bool> compressionMethods = new Dictionary<string, bool>();
56 private readonly X509Certificate localDomainCertificate = null;
57 private readonly string errorType = "cancel";
58 private readonly string errorXml = "<remote-server-not-found xmlns='" + XmppServer.StanzaNamespace + "'/>";
59 private readonly object synchObject = new object();
60 private readonly string localStreamId;
61 private readonly StringBuilder fragment = new StringBuilder();
62 private readonly XmppServer server;
63 private readonly DateTime creationTimeUtc = DateTime.UtcNow;
64 private readonly int port;
65 private readonly bool allowEncryption = true;
66 private readonly bool incomingConnection;
67 private readonly bool temporary = false;
68 private DateTime connectTimeUtc = DateTime.MinValue;
69 private DateTime connectedTimeUtc = DateTime.MaxValue;
70 private DateTime nextPingUtc = DateTime.MinValue;
71 private ChunkedList<QueuedStanza> queue;
72 private CaseInsensitiveString[] remoteDomainCertificateDomains = null;
73 private TextTcpClient client = null;
74 private Timer secondTimer = null;
75 private int fragmentLength = 0;
76 private XmppS2sState state;
77 private XmppS2SEndpoint authConnection = null;
78 private LinkedList<Tuple<string, int, int>> stanzasOnHold = null;
79 private string authKey = null;
80 private string authStreamId = null;
81 private string remoteStreamId;
82 private CaseInsensitiveString host;
83 private string streamHeader;
84 private string streamFooter;
85 private double version;
86 private int keepAliveSeconds = KeepAliveTimeSeconds;
87 private int inputState = 0;
88 private int inputDepth = 0;
89 private int contentStart = 0;
90 private int contentEnd = 0;
91 private string pingId = string.Empty;
92 private bool trustServer = false;
93 private bool supportsPing = true;
94 private bool pingResponse = true;
95 private bool checkConnection = false;
96 private bool authResultRequestSent = false;
97 private bool authVerifyRequestSent = false;
98 private bool bidirectional = false;
99 private bool openBracketReceived = false;
100 private bool verified = false;
101 private bool upgradeToTlsAsClient = false;
102 private bool upgradeToTlsAsServer = false;
103
117 int Port, X509Certificate DomainCertificate, XmppServer Server, bool TrustRemoteCertificate,
118 bool Temporary, QueuedStanza[] QueuedStanzas, params ISniffer[] Sniffers)
120 {
121 this.port = Port;
122 this.host = RemoteDomain;
123 this.state = XmppS2sState.Connecting;
124 this.localDomainCertificate = DomainCertificate;
125 this.server = Server;
126 this.localStreamId = this.server.GetRandomHexString(16);
127 this.remoteStreamId = null;
128 this.trustServer = TrustRemoteCertificate;
129 this.incomingConnection = false;
130 this.temporary = Temporary;
131
132 if ((QueuedStanzas?.Length ?? 0) > 0)
133 {
134 this.queue = new ChunkedList<QueuedStanza>();
135 this.queue.AddRange(QueuedStanzas);
136 }
137 }
138
147 public XmppS2SEndpoint(TextTcpClient Client, X509Certificate DomainCertificate, XmppServer Server, bool TrustRemoteCertificate,
148 params ISniffer[] Sniffers)
150 {
151 this.client = Client;
152 this.server = Server;
153 this.localStreamId = this.server.GetRandomHexString(16);
154 this.remoteStreamId = null;
155 this.localDomainCertificate = DomainCertificate;
156 this.trustServer = TrustRemoteCertificate;
157 this.incomingConnection = true;
158
159 this.state = XmppS2sState.StreamNegotiation;
160
161 this.ResetState();
162
163 this.client.OnDisconnected += this.Client_OnDisconnected;
164 this.client.OnError += this.Client_OnError;
165 this.client.OnReceived += this.Client_OnReceived;
166 this.client.OnSent += this.Client_OnSent;
167 this.client.OnPaused += this.Client_OnPaused;
168 this.client.OnInformation += this.Client_OnInformation;
169 this.client.OnWarning += this.Client_OnWarning;
170 }
171
176 public Task<bool> Connect(bool DisposeCurrent)
177 {
178 return this.Connect(this.host, DisposeCurrent);
179 }
180
184 public override string Type => "XMPP";
185
189 public string RemoteEndPoint => this.client.RemoteEndPoint;
190
196 public async Task<bool> Connect(string Host, bool DisposeCurrent)
197 {
198 try
199 {
200 if (DisposeCurrent)
201 await this.DisposeClient("Making a new connection to " + Host);
202
203 this.connectTimeUtc = DateTime.UtcNow;
204 this.host = Host;
205 this.checkConnection = true;
206 await this.SetState(XmppS2sState.Connecting);
207 this.pingResponse = true;
208 this.upgradeToTlsAsClient = false;
209 this.upgradeToTlsAsServer = false;
210
211 this.client = new TextTcpClient(XmppServer.encoding, true);
212
213 this.client.OnDisconnected += this.Client_OnDisconnected;
214 this.client.OnError += this.Client_OnError;
215 this.client.OnReceived += this.Client_OnReceived;
216 this.client.OnSent += this.Client_OnSent;
217 this.client.OnPaused += this.Client_OnPaused;
218 this.client.OnInformation += this.Client_OnInformation;
219 this.client.OnWarning += this.Client_OnWarning;
220
221 if (!await this.client.ConnectAsync(this.host, this.port))
222 return false;
223
224 await this.SetState(XmppS2sState.StreamNegotiation);
225
226 if (!await this.BeginWrite("<?xml version='1.0' encoding='utf-8'?><stream:stream id='" + this.localStreamId + "' to='" + XML.Encode(this.remoteDomain) +
227 "' from='" + XML.Encode(this.localDomain) + "' version='1.0' xmlns='jabber:server' xmlns:db='" + DialbackNamespace +
228 "' xmlns:stream='" + XmppClientConnection.StreamNamespace + "'>", null, null))
229 {
230 return false;
231 }
232
233 this.ResetState();
234
235 return true;
236 }
237 catch (Exception ex)
238 {
239 await this.ConnectionError(ex);
240 return false;
241 }
242 }
243
247 public Task Close()
248 {
249 return this.DisposeClient("Closing connection.");
250 }
251
252 private void ResetState()
253 {
254 this.inputState = 0;
255 this.inputDepth = 0;
256
257 this.compressionMethods.Clear();
258 }
259
260 private async Task ConnectionError(Exception ex)
261 {
262 await this.OnConnectionError.Raise(this, EventArgs.Empty);
263
264 this.Exception(ex);
265
266 this.inputState = -1;
267 await this.DisposeClient("Connection Error: " + ex.Message);
268 await this.SetState(XmppS2sState.Error, ex.Message);
269 }
270
271 private async Task Error(Exception Exception)
272 {
273 Exception = Log.UnnestException(Exception);
274
275 if (Exception is AggregateException ex)
276 {
277 foreach (Exception ex2 in ex.InnerExceptions)
278 await this.Error(ex2);
279 }
280 else
281 {
282 this.Error(Exception.Message);
283
284 await this.OnError.Raise(this, EventArgs.Empty);
285 }
286 }
287
292
296 public event EventHandlerAsync OnError = null;
297
301 public bool TrustServer
302 {
303 get => this.trustServer;
304 set => this.trustServer = value;
305 }
306
310 public X509Certificate RemoteDomainCertificate
311 {
312 get => this.client?.RemoteCertificate;
313 }
314
319 {
320 get => this.client?.RemoteCertificateValid ?? false;
321 }
322
326 public XmppS2sState State => this.state;
327
332 internal Task SetState(XmppS2sState NewState)
333 {
334 return this.SetState(NewState, null);
335 }
336
342 internal async Task SetState(XmppS2sState NewState, string Reason)
343 {
344 if (this.state != NewState)
345 {
346 this.state = NewState;
347
348 if (NewState == XmppS2sState.Connected)
349 this.connectedTimeUtc = DateTime.UtcNow;
350
351 StringBuilder sb = new StringBuilder();
352
353 sb.Append("State changed to ");
354 sb.Append(NewState.ToString());
355
356 if (!string.IsNullOrEmpty(Reason))
357 {
358 sb.Append(" (");
359 sb.Append(Reason);
360 sb.Append(')');
361 }
362
363 this.Information(sb.ToString());
364
365 await this.OnStateChanged.Raise(this, EventArgs.Empty);
366 }
367 }
368
372 public bool IsStale
373 {
374 get
375 {
376 if (this.state == XmppS2sState.Connected)
377 return false;
378
379 DateTime UtcNow = DateTime.UtcNow;
380
381 if ((UtcNow - this.creationTimeUtc).TotalSeconds < 30)
382 return false;
383
384 if (this.state == XmppS2sState.Offline || this.state == XmppS2sState.Error)
385 return true;
386
387 return (UtcNow - this.connectTimeUtc).TotalSeconds > 90;
388 }
389 }
390
394 public DateTime CreationTimeUtc => this.creationTimeUtc;
395
399 public DateTime ConnectTimeUtc => this.connectTimeUtc;
400
404 public DateTime ConnectedTimeUtc => this.connectedTimeUtc;
405
410
415 internal QueuedStanza[] GetAndClearQueuedStanzas()
416 {
417 lock (this.synchObject)
418 {
419 if (this.queue is null)
420 return null;
421
422 QueuedStanza[] Result = this.queue.ToArray();
423
424 this.queue.Clear();
425 this.queue = null;
426
427 return Result;
428 }
429 }
430
435 public override async Task DisposeAsync(string Reason)
436 {
437 this.checkConnection = false;
438
439 this.secondTimer?.Dispose();
440 this.secondTimer = null;
441
442 if (this.state == XmppS2sState.Connected ||
443 this.state == XmppS2sState.StreamOpened ||
444 this.state == XmppS2sState.StartingEncryptionAsClient ||
445 this.state == XmppS2sState.StartingEncryptionAsServer ||
446 this.state == XmppS2sState.Verifying ||
447 this.state == XmppS2sState.Dialback)
448 {
449 try
450 {
451 await this.BeginWrite(this.streamFooter, async (Sender, e) =>
452 {
453 await this.CleanUp(this, XmppS2sState.Offline, Reason);
454 }, null);
455 }
456 catch (Exception ex)
457 {
458 await this.CleanUp(this, XmppS2sState.Offline, ex.Message);
459 }
460 }
461 else
462 await this.CleanUp(this, EventArgs.Empty);
463 }
464
469 public Task HardOffline()
470 {
471 return this.CleanUp(this, XmppS2sState.Offline, "Goind hard-offline.");
472 }
473
474 private Task CleanUp(object Sender, EventArgs e)
475 {
476 return this.CleanUp(Sender, XmppS2sState.Offline, "Closing connection.");
477 }
478
479 private async Task CleanUp(object _, XmppS2sState State, string Reason)
480 {
481 await this.SetState(State, Reason);
482
483 this.compressionMethods?.Clear();
484
485 this.secondTimer?.Dispose();
486 this.secondTimer = null;
487
488 if (string.IsNullOrEmpty(Reason))
489 await this.DisposeClient("Cleaning up connection.");
490 else
491 await this.DisposeClient("Cleaning up connection: " + Reason);
492 }
493
494 private async Task DisposeClient(string Reason)
495 {
496 TextTcpClient c = this.client;
497 this.client = null;
498
499 if (!(this.authConnection is null))
500 {
501 try
502 {
503 if (string.IsNullOrEmpty(Reason))
504 this.authConnection.KeyAuthenticated(S2sValidationResult.Error, "Disposing connection client.");
505 else
506 this.authConnection.KeyAuthenticated(S2sValidationResult.Error, "Disposing connection client: " + Reason);
507 }
508 catch (Exception ex)
509 {
510 Log.Exception(ex);
511 }
512
513 this.authConnection = null;
514 }
515
516 c?.DisposeWhenDone();
517
518 if (!string.IsNullOrEmpty(this.server.DomainSnifferPath))
519 await this.server.CacheSniffers(this.Sniffers);
520
521 await this.OnDisposed.Raise(this, EventArgs.Empty, false);
522 this.OnDisposed = null;
523
524 this.server.S2sEndpointDisposed(this);
525 }
526
530 public event EventHandlerAsync OnDisposed = null;
531
532 private Task<bool> BeginWrite(string Xml, EventHandlerAsync<DeliveryEventArgs> Callback, object State)
533 {
534 this.nextPingUtc = DateTime.UtcNow.AddMilliseconds(this.keepAliveSeconds * 500);
535 return this.client?.SendAsync(Xml, Callback, State) ?? Task.FromResult(false);
536 }
537
538 private Task<bool> Client_OnSent(object Sender, string Text)
539 {
540 this.server?.DataTransmitted(this.client?.LastTransmittedBytes ?? 0);
541 this.TransmitText(Text);
542 return Task.FromResult(true);
543 }
544
545 private string Client_OnWarning(string Text)
546 {
547 this.Warning(Text);
548 return Text;
549 }
550
551 private string Client_OnInformation(string Text)
552 {
553 this.Information(Text);
554 return Text;
555 }
556
557 private async Task<bool> Client_OnReceived(object Sender, string Text)
558 {
559 try
560 {
561 this.server?.DataReceived(this.client?.LastReceivedBytes ?? 0);
562
563 if (this.openBracketReceived)
564 {
565 this.openBracketReceived = false;
566 this.ReceiveText("<" + Text);
567 }
568 else if (Text == "<")
569 this.openBracketReceived = true;
570 else
571 this.ReceiveText(Text);
572
573 return await this.ParseIncoming(Text);
574 }
575 catch (Exception ex)
576 {
577 this.Exception(ex);
578 await this.DisposeAsync(ex.Message);
579
580 return false;
581 }
582 }
583
584 private async Task Client_OnError(object Sender, Exception Exception)
585 {
586 await this.SetState(XmppS2sState.Error);
587 this.Error(Exception.Message);
588 await this.DisposeAsync(Exception.Message);
589 }
590
591 private async Task Client_OnDisconnected(object Sender, EventArgs e)
592 {
593 await this.SetState(XmppS2sState.Offline);
594 await this.DisposeAsync("Client was disconnected.");
595 }
596
597 private const string FragmentTooBig = "Fragment too big.";
598 private const string IllegalCharacterReceived = "Illegal character received.";
599
600 private async Task<bool> ParseIncoming(string s)
601 {
602 bool Result = true;
603
604 foreach (char ch in s)
605 {
606 switch (this.inputState)
607 {
608 case 0: // Waiting for first <
609 if (ch == '<')
610 {
611 this.fragment.Append(ch);
612 if (++this.fragmentLength > MaxFragmentSize)
613 {
614 await this.ToError(FragmentTooBig);
615 return false;
616 }
617 else
618 this.inputState++;
619 }
620 else if (ch > ' ')
621 {
622 await this.ToError(IllegalCharacterReceived);
623 return false;
624 }
625 break;
626
627 case 1: // Waiting for ? or >
628 this.fragment.Append(ch);
629 if (++this.fragmentLength > MaxFragmentSize)
630 {
631 await this.ToError(FragmentTooBig);
632 return false;
633 }
634 else if (ch == '?')
635 this.inputState++;
636 else if (ch == '>')
637 {
638 this.inputState = 5;
639 this.inputDepth = 1;
640 if (!await this.ProcessStream(this.fragment.ToString()))
641 Result = false;
642 this.fragment.Clear();
643 this.fragmentLength = this.contentStart = this.contentEnd = 0;
644 }
645 break;
646
647 case 2: // In processing instruction. Waiting for ?>
648 if (++this.fragmentLength > MaxFragmentSize)
649 {
650 await this.ToError(FragmentTooBig);
651 return false;
652 }
653 else if (ch == '>')
654 {
655 this.fragment.Clear();
656 this.inputState++;
657 }
658 break;
659
660 case 3: // Waiting for <stream
661 this.fragment.Append(ch);
662 if (++this.fragmentLength > MaxFragmentSize)
663 {
664 await this.ToError(FragmentTooBig);
665 return false;
666 }
667 else if (ch == '<')
668 this.inputState++;
669 else if (ch > ' ')
670 {
671 await this.ToError(IllegalCharacterReceived);
672 return false;
673 }
674 break;
675
676 case 4: // Waiting for >
677 this.fragment.Append(ch);
678 if (++this.fragmentLength > MaxFragmentSize)
679 {
680 await this.ToError(FragmentTooBig);
681 return false;
682 }
683 else if (ch == '>')
684 {
685 this.inputState++;
686 this.inputDepth = 1;
687 if (!await this.ProcessStream(this.fragment.ToString()))
688 Result = false;
689 this.fragment.Clear();
690 this.fragmentLength = this.contentStart = this.contentEnd = 0;
691 }
692 break;
693
694 case 5: // Waiting for start element.
695 if (ch == '<')
696 {
697 this.fragment.Append(ch);
698 if (++this.fragmentLength > MaxFragmentSize)
699 {
700 await this.ToError(FragmentTooBig);
701 return false;
702 }
703 else
704 this.inputState++;
705 }
706 else if (this.inputDepth > 1)
707 {
708 this.fragment.Append(ch);
709 if (++this.fragmentLength > MaxFragmentSize)
710 {
711 await this.ToError(FragmentTooBig);
712 return false;
713 }
714 }
715 else if (ch > ' ')
716 {
717 await this.ToError(IllegalCharacterReceived);
718 return false;
719 }
720 break;
721
722 case 6: // Second character in tag
723 this.fragment.Append(ch);
724 if (++this.fragmentLength > MaxFragmentSize)
725 {
726 await this.ToError(FragmentTooBig);
727 return false;
728 }
729 else if (ch == '/')
730 {
731 if (this.inputDepth == 2)
732 this.contentEnd = this.fragmentLength - 2;
733
734 this.inputState++;
735 }
736 else if (ch == '?') // Unexpected processing instruction. Treat as if sender reinitiates connection on a reused connection without a graceful shutdown.
737 {
738 if (this.State != XmppS2sState.Connected && this.State != XmppS2sState.Verifying && this.State != XmppS2sState.Dialback)
739 {
740 this.Warning("Processing instruction received. Assuming connection reused. Resetting state.");
741 await this.SetState(XmppS2sState.StreamNegotiation);
742 }
743
744 this.inputState = 2;
745 this.inputDepth = 0;
746 }
747 else if (ch == '!')
748 this.inputState = 13;
749 else
750 this.inputState += 2;
751 break;
752
753 case 7: // Waiting for end of closing tag
754 this.fragment.Append(ch);
755 if (++this.fragmentLength > MaxFragmentSize)
756 {
757 await this.ToError(FragmentTooBig);
758 return false;
759 }
760 else if (ch == '>')
761 {
762 this.inputDepth--;
763 if (this.inputDepth < 1)
764 {
765 this.inputState = -1;
766 await this.CleanUp(this, XmppS2sState.Offline, "Closing tag.");
767 return false;
768 }
769 else
770 {
771 if (this.inputDepth == 1)
772 {
773 if (!await this.ProcessFragment(this.fragment.ToString(), this.contentStart, this.contentEnd - this.contentStart))
774 Result = false;
775
776 this.fragment.Clear();
777 this.fragmentLength = this.contentStart = this.contentEnd = 0;
778 }
779
780 if (this.inputState > 0)
781 this.inputState = 5;
782 }
783 }
784 break;
785
786 case 8: // Wait for end of start tag
787 this.fragment.Append(ch);
788 if (++this.fragmentLength > MaxFragmentSize)
789 {
790 await this.ToError(FragmentTooBig);
791 return false;
792 }
793 else if (ch == '>')
794 {
795 if (this.inputDepth == 1)
796 this.contentStart = this.fragmentLength;
797
798 this.inputDepth++;
799 this.inputState = 5;
800 }
801 else if (ch == '/')
802 this.inputState++;
803 else if (ch <= ' ')
804 this.inputState += 2;
805 break;
806
807 case 9: // Check for end of childless tag.
808 this.fragment.Append(ch);
809 if (++this.fragmentLength > MaxFragmentSize)
810 {
811 await this.ToError(FragmentTooBig);
812 return false;
813 }
814 else if (ch == '>')
815 {
816 if (this.inputDepth == 1)
817 {
818 if (!await this.ProcessFragment(this.fragment.ToString(), this.contentStart, this.contentEnd - this.contentStart))
819 Result = false;
820
821 this.fragment.Clear();
822 this.fragmentLength = this.contentStart = this.contentEnd = 0;
823 }
824
825 if (this.inputState != 0)
826 this.inputState = 5;
827 }
828 else
829 this.inputState--;
830 break;
831
832 case 10: // Check for attributes.
833 this.fragment.Append(ch);
834 if (++this.fragmentLength > MaxFragmentSize)
835 {
836 await this.ToError(FragmentTooBig);
837 return false;
838 }
839 else if (ch == '>')
840 {
841 if (this.inputDepth == 1)
842 this.contentStart = this.fragmentLength;
843
844 this.inputDepth++;
845 this.inputState = 5;
846 }
847 else if (ch == '/')
848 this.inputState--;
849 else if (ch == '"')
850 this.inputState++;
851 else if (ch == '\'')
852 this.inputState += 2;
853 break;
854
855 case 11: // Double quote attribute.
856 this.fragment.Append(ch);
857 if (++this.fragmentLength > MaxFragmentSize)
858 {
859 await this.ToError(FragmentTooBig);
860 return false;
861 }
862 else if (ch == '"')
863 this.inputState--;
864 break;
865
866 case 12: // Single quote attribute.
867 this.fragment.Append(ch);
868 if (++this.fragmentLength > MaxFragmentSize)
869 {
870 await this.ToError(FragmentTooBig);
871 return false;
872 }
873 else if (ch == '\'')
874 this.inputState -= 2;
875 break;
876
877 case 13: // Third character in start of comment
878 this.fragment.Append(ch);
879 if (++this.fragmentLength > MaxFragmentSize)
880 {
881 await this.ToError(FragmentTooBig);
882 return false;
883 }
884 else if (ch == '-')
885 this.inputState++;
886 else if (ch == '[')
887 this.inputState = 18;
888 else
889 {
890 await this.ToError(IllegalCharacterReceived);
891 return false;
892 }
893 break;
894
895 case 14: // Fourth character in start of comment
896 this.fragment.Append(ch);
897 if (++this.fragmentLength > MaxFragmentSize)
898 {
899 await this.ToError(FragmentTooBig);
900 return false;
901 }
902 else if (ch == '-')
903 this.inputState++;
904 else
905 {
906 await this.ToError(IllegalCharacterReceived);
907 return false;
908 }
909 break;
910
911 case 15: // In comment
912 this.fragment.Append(ch);
913 if (++this.fragmentLength > MaxFragmentSize)
914 {
915 await this.ToError(FragmentTooBig);
916 return false;
917 }
918 else if (ch == '-')
919 this.inputState++;
920 break;
921
922 case 16: // Second character in end of comment
923 this.fragment.Append(ch);
924 if (++this.fragmentLength > MaxFragmentSize)
925 {
926 await this.ToError(FragmentTooBig);
927 return false;
928 }
929 else if (ch == '-')
930 this.inputState++;
931 else
932 this.inputState--;
933 break;
934
935 case 17: // Third character in end of comment
936 this.fragment.Append(ch);
937 if (++this.fragmentLength > MaxFragmentSize)
938 {
939 await this.ToError(FragmentTooBig);
940 return false;
941 }
942 else if (ch == '>')
943 this.inputState = 5;
944 else
945 this.inputState -= 2;
946 break;
947
948 case 18: // Fourth character in start of CDATA
949 this.fragment.Append(ch);
950 if (++this.fragmentLength > MaxFragmentSize)
951 {
952 await this.ToError(FragmentTooBig);
953 return false;
954 }
955 else if (ch == 'C')
956 this.inputState++;
957 else
958 {
959 await this.ToError(IllegalCharacterReceived);
960 return false;
961 }
962 break;
963
964 case 19: // Fifth character in start of CDATA
965 this.fragment.Append(ch);
966 if (++this.fragmentLength > MaxFragmentSize)
967 {
968 await this.ToError(FragmentTooBig);
969 return false;
970 }
971 else if (ch == 'D')
972 this.inputState++;
973 else
974 {
975 await this.ToError(IllegalCharacterReceived);
976 return false;
977 }
978 break;
979
980 case 20: // Sixth character in start of CDATA
981 this.fragment.Append(ch);
982 if (++this.fragmentLength > MaxFragmentSize)
983 {
984 await this.ToError(FragmentTooBig);
985 return false;
986 }
987 else if (ch == 'A')
988 this.inputState++;
989 else
990 {
991 await this.ToError(IllegalCharacterReceived);
992 return false;
993 }
994 break;
995
996 case 21: // Seventh character in start of CDATA
997 this.fragment.Append(ch);
998 if (++this.fragmentLength > MaxFragmentSize)
999 {
1000 await this.ToError(FragmentTooBig);
1001 return false;
1002 }
1003 else if (ch == 'T')
1004 this.inputState++;
1005 else
1006 {
1007 await this.ToError(IllegalCharacterReceived);
1008 return false;
1009 }
1010 break;
1011
1012 case 22: // Eighth character in start of CDATA
1013 this.fragment.Append(ch);
1014 if (++this.fragmentLength > MaxFragmentSize)
1015 {
1016 await this.ToError(FragmentTooBig);
1017 return false;
1018 }
1019 else if (ch == 'A')
1020 this.inputState++;
1021 else
1022 {
1023 await this.ToError(IllegalCharacterReceived);
1024 return false;
1025 }
1026 break;
1027
1028 case 23: // Ninth character in start of CDATA
1029 this.fragment.Append(ch);
1030 if (++this.fragmentLength > MaxFragmentSize)
1031 {
1032 await this.ToError(FragmentTooBig);
1033 return false;
1034 }
1035 else if (ch == '[')
1036 this.inputState++;
1037 else
1038 {
1039 await this.ToError(IllegalCharacterReceived);
1040 return false;
1041 }
1042 break;
1043
1044 case 24: // In CDATA
1045 this.fragment.Append(ch);
1046 if (++this.fragmentLength > MaxFragmentSize)
1047 {
1048 await this.ToError(FragmentTooBig);
1049 return false;
1050 }
1051 else if (ch == ']')
1052 this.inputState++;
1053 break;
1054
1055 case 25: // Second character in end of CDATA
1056 this.fragment.Append(ch);
1057 if (++this.fragmentLength > MaxFragmentSize)
1058 {
1059 await this.ToError(FragmentTooBig);
1060 return false;
1061 }
1062 else if (ch == ']')
1063 this.inputState++;
1064 else
1065 this.inputState--;
1066 break;
1067
1068 case 26: // Third character in end of CDATA
1069 this.fragment.Append(ch);
1070 if (++this.fragmentLength > MaxFragmentSize)
1071 {
1072 await this.ToError(FragmentTooBig);
1073 return false;
1074 }
1075 else if (ch == '>')
1076 this.inputState = 5;
1077 else if (ch != ']')
1078 this.inputState -= 2;
1079 break;
1080
1081 default:
1082 break;
1083 }
1084 }
1085
1086 return Result;
1087 }
1088
1089 private Task ToError(string Reason)
1090 {
1091 this.inputState = -1;
1092 return this.CleanUp(this, XmppS2sState.Error, Reason);
1093 }
1094
1095 private async Task<bool> ProcessStream(string Xml)
1096 {
1097 try
1098 {
1099 this.streamHeader = Xml;
1100
1101 if (Xml.StartsWith("</"))
1102 {
1103 await this.ConnectionError(new Exception("Connection closed by the remote endpoint."));
1104 return false;
1105 }
1106
1107 int i = Xml.IndexOf(":stream");
1108 if (i < 0)
1109 this.streamFooter = "</stream>";
1110 else
1111 this.streamFooter = "</" + Xml[1..i] + ":stream>";
1112
1113 XmlDocument Doc = XML.ParseXml(Xml + this.streamFooter, true);
1114
1115 if (Doc.DocumentElement.LocalName != "stream")
1116 throw new Exception("Invalid stream.");
1117
1118 XmlElement Stream = Doc.DocumentElement;
1119
1120 this.version = XML.Attribute(Stream, "version", 0.0);
1121 if (this.version < 1.0)
1122 throw new Exception("Version not supported.");
1123
1124 this.remoteStreamId = XML.Attribute(Stream, "id");
1125
1126 if (this.incomingConnection)
1127 {
1128 this.remoteDomain = XML.Attribute(Stream, "from");
1129
1131
1132 foreach (ISniffer Sniffer in this.Sniffers)
1133 {
1134 InMemorySniffer = Sniffer as InMemorySniffer;
1135 if (!(InMemorySniffer is null))
1136 break;
1137 }
1138
1139 if (!(InMemorySniffer is null))
1140 {
1141 this.Remove(InMemorySniffer);
1142 this.server.AddS2SSniffers(this, this.remoteDomain);
1143 InMemorySniffer.Replay(this);
1144 }
1145
1146 string To = XML.Attribute(Stream, "to");
1147 if (!this.server.IsServerDomain(To, true))
1148 {
1149 if (string.IsNullOrEmpty(To))
1150 throw new Exception("No to attribute in S2S connection stream. From: " + XML.Attribute(Stream, "from"));
1151 else
1152 throw new Exception("Unexpected domain: " + To);
1153 }
1154
1155 if (string.IsNullOrEmpty(this.localDomain))
1156 this.localDomain = To;
1157
1158 this.trustServer = XmppServer.TrustCertificate(this.remoteDomain);
1159 }
1160 else if (this.state == XmppS2sState.Connected)
1161 {
1162 string From = XML.Attribute(Stream, "from");
1163 if (this.remoteDomain != From)
1164 throw new Exception("Unexpected domain: " + From + ". Expected: " + this.remoteDomain);
1165 }
1166
1167 if (this.state == XmppS2sState.Connected ||
1168 this.state == XmppS2sState.Verifying ||
1169 this.state == XmppS2sState.Dialback)
1170 {
1171 if (this.incomingConnection)
1172 {
1173 StringBuilder sb = new StringBuilder();
1174
1175 sb.Append("<?xml version='1.0' encoding='utf-8'?><stream:stream id='");
1176 sb.Append(this.localStreamId);
1177 sb.Append("' to='");
1178 sb.Append(XML.Encode(this.remoteDomain));
1179 sb.Append("' from='");
1180 sb.Append(XML.Encode(this.localDomain));
1181 sb.Append("' version='1.0' xmlns='jabber:server' xmlns:db='");
1182 sb.Append(DialbackNamespace);
1183 sb.Append("' xmlns:stream='");
1185 sb.Append("'><stream:features><dialback xmlns='");
1186 sb.Append(DialbackFeaturesNamespace);
1187 sb.Append("'/></stream:features>");
1188
1189 if (!await this.BeginWrite(sb.ToString(), null, null))
1190 return false;
1191 }
1192
1193 if (this.verified)
1194 {
1195 if (!await this.SendQueued())
1196 return false;
1197 }
1198
1199 return true;
1200 }
1201
1202 if (this.incomingConnection)
1203 {
1204 StringBuilder sb = new StringBuilder();
1205
1206 sb.Append("<?xml version='1.0' encoding='utf-8'?><stream:stream id='");
1207 sb.Append(this.localStreamId);
1208 sb.Append("' to='");
1209 sb.Append(XML.Encode(this.remoteDomain));
1210 sb.Append("' from='");
1211 sb.Append(XML.Encode(this.localDomain));
1212 sb.Append("' version='1.0' xmlns='jabber:server' xmlns:db='");
1213 sb.Append(DialbackNamespace);
1214 sb.Append("' xmlns:stream='");
1216 sb.Append("'><stream:features>");
1217
1218 if (this.client.IsEncrypted)
1219 {
1220 if (this.client.RemoteCertificateValid)
1221 {
1222 if (!(this.remoteDomainCertificateDomains is null) &&
1223 Array.IndexOf(this.remoteDomainCertificateDomains, this.remoteDomain) >= 0)
1224 {
1225 sb.Append("<mechanisms xmlns='");
1226 sb.Append(XmppServer.SaslNamespace);
1227 sb.Append("'><mechanism>EXTERNAL</mechanism></mechanisms>");
1228 }
1229 }
1230
1231 sb.Append("<dialback xmlns='");
1232 sb.Append(DialbackFeaturesNamespace);
1233 sb.Append("'><errors/></dialback>");
1234 }
1235 else
1236 {
1237 sb.Append("<starttls xmlns='");
1239 sb.Append("'><required/></starttls>");
1240 }
1241
1242 sb.Append("<bidi xmlns='");
1244 sb.Append("'/></stream:features>");
1245
1246 if (!await this.BeginWrite(sb.ToString(), null, null))
1247 return false;
1248 }
1249
1250 await this.SetState(XmppS2sState.StreamOpened);
1251 }
1252 catch (Exception ex)
1253 {
1254 await this.ConnectionError(ex);
1255 }
1256
1257 return true;
1258 }
1259
1260 private async Task<bool> SendQueued()
1261 {
1262 QueuedStanza Stanza;
1263 Tuple<string, int, int> OnHold;
1264 bool First = true;
1265
1266 do
1267 {
1268 lock (this.synchObject)
1269 {
1270 if (this.queue is null || !this.queue.HasFirstItem)
1271 {
1272 this.queue = null;
1273 break;
1274 }
1275
1276 Stanza = this.queue.RemoveFirst();
1277 if (!this.queue.HasFirstItem)
1278 this.queue = null;
1279 }
1280
1281 if (First)
1282 {
1283 First = false;
1284 this.Information("Sending queued stanzas.");
1285 }
1286
1287 if (!await this.BeginWrite(Stanza.StanzaType, Stanza.Type, Stanza.Id, Stanza.To, Stanza.From, Stanza.Language, Stanza.ContentXml))
1288 {
1289 lock (this.synchObject)
1290 {
1291 this.queue ??= new ChunkedList<QueuedStanza>();
1292 this.queue.Add(Stanza);
1293 }
1294
1295 return false;
1296 }
1297 }
1298 while (!(Stanza is null));
1299
1300 First = true;
1301
1302 do
1303 {
1304 lock (this.synchObject)
1305 {
1306 if (this.stanzasOnHold is null || this.stanzasOnHold.First is null)
1307 {
1308 this.stanzasOnHold = null;
1309 break;
1310 }
1311
1312 OnHold = this.stanzasOnHold.First.Value;
1313 this.stanzasOnHold.RemoveFirst();
1314 if (this.stanzasOnHold.First is null)
1315 this.stanzasOnHold = null;
1316 }
1317
1318 if (First)
1319 {
1320 First = false;
1321 this.Information("Processing incoming stanzas put on hold.");
1322 }
1323
1324 this.Information(OnHold.Item1.Substring(OnHold.Item2, OnHold.Item3));
1325
1326 if (!await this.ProcessFragment(OnHold.Item1, OnHold.Item2, OnHold.Item3))
1327 {
1328 lock (this.synchObject)
1329 {
1330 this.stanzasOnHold ??= new LinkedList<Tuple<string, int, int>>();
1331 this.stanzasOnHold.AddFirst(OnHold);
1332 }
1333
1334 return false;
1335 }
1336 }
1337 while (!(OnHold is null));
1338
1339 return true;
1340 }
1341
1342 private async Task<bool> ProcessFragment(string Xml, int ContentStart, int ContentLen)
1343 {
1344 Stanza Stanza;
1345 XmlDocument Doc;
1346 XmlElement E;
1347 StringBuilder sb;
1348
1349 try
1350 {
1351 this.server.TouchServerConnection(this.remoteDomain);
1352
1353 Doc = XML.ParseXml(this.streamHeader + Xml + this.streamFooter, true);
1354
1355 Stanza = new Stanza(Doc.DocumentElement, Xml, ContentStart, ContentLen);
1357 if (E is null)
1358 return true;
1359
1360 switch (E.LocalName)
1361 {
1362 case "iq":
1363 if (this.state != XmppS2sState.Connected)
1364 {
1365 if (this.state == XmppS2sState.Dialback || this.state == XmppS2sState.Verifying)
1366 {
1367 lock (this.synchObject)
1368 {
1369 this.stanzasOnHold ??= new LinkedList<Tuple<string, int, int>>();
1370 this.stanzasOnHold.AddLast(new Tuple<string, int, int>(Xml, ContentStart, ContentLen));
1371 }
1372
1373 this.Warning("Keeping stanza on hold while performing verification.");
1374 }
1375
1376 break;
1377 }
1378
1379 string Type = XML.Attribute(E, "type");
1380 string Id = XML.Attribute(E, "id");
1381 XmppAddress To = new XmppAddress(XML.Attribute(E, "to"));
1382 XmppAddress From = new XmppAddress(XML.Attribute(E, "from"));
1383 string Language = XML.Attribute(E, "xml:lang");
1384
1385 this.server.IncCounters("iq", Type, From, To, E);
1386
1387 if (!await this.CheckFrom(From))
1388 return true;
1389
1390 if (To.Address == this.localDomain && From.Address == this.remoteDomain && (Type == "result" || Type == "error") && Id == this.pingId)
1391 {
1392 this.pingResponse = true;
1393
1394 if (Type == "error")
1395 this.supportsPing = false;
1396
1397 break;
1398 }
1399
1400 this.ProcessIq(Id, To, From, Type, Language, Stanza);
1401 break;
1402
1403 case "message":
1404 if (this.state != XmppS2sState.Connected)
1405 {
1406 if (this.state == XmppS2sState.Dialback || this.state == XmppS2sState.Verifying)
1407 {
1408 lock (this.synchObject)
1409 {
1410 this.stanzasOnHold ??= new LinkedList<Tuple<string, int, int>>();
1411 this.stanzasOnHold.AddLast(new Tuple<string, int, int>(Xml, ContentStart, ContentLen));
1412 }
1413
1414 this.Warning("Keeping stanza on hold while performing verification");
1415 }
1416
1417 break;
1418 }
1419
1420 Type = XML.Attribute(E, "type");
1421 Id = XML.Attribute(E, "id");
1422 To = new XmppAddress(XML.Attribute(E, "to"));
1423 From = new XmppAddress(XML.Attribute(E, "from"));
1424 Language = XML.Attribute(E, "xml:lang");
1425
1426 this.server.IncCounters("message", Type, From, To, E);
1427
1428 if (!await this.CheckFrom(From))
1429 return true;
1430
1431 this.ProcessMessage(Type, Id, To, From, Language, Stanza);
1432 break;
1433
1434 case "presence":
1435 if (this.state != XmppS2sState.Connected)
1436 {
1437 if (this.state == XmppS2sState.Dialback || this.state == XmppS2sState.Verifying)
1438 {
1439 lock (this.synchObject)
1440 {
1441 this.stanzasOnHold ??= new LinkedList<Tuple<string, int, int>>();
1442 this.stanzasOnHold.AddLast(new Tuple<string, int, int>(Xml, ContentStart, ContentLen));
1443 }
1444
1445 this.Warning("Keeping stanza on hold while performing verification");
1446 }
1447
1448 break;
1449 }
1450
1451 Type = XML.Attribute(E, "type");
1452 Id = XML.Attribute(E, "id");
1453 To = new XmppAddress(XML.Attribute(E, "to"));
1454 From = new XmppAddress(XML.Attribute(E, "from"));
1455 Language = XML.Attribute(E, "xml:lang");
1456
1457 this.server.IncCounters("presence", Type, From, To, E);
1458
1459 if (!await this.CheckFrom(From))
1460 return true;
1461
1462 this.ProcessPresence(Type, Id, To, From, Language, Stanza);
1463 break;
1464
1465 case "features":
1466 if (E.FirstChild is null)
1467 this.DialbackCompleted(false, false, "No features available.");
1468 else
1469 {
1470 bool StartTls = false;
1471 bool Dialback = false;
1472 bool Bidirectional = false;
1473 bool ExternalAuth = false;
1474
1475 foreach (XmlNode N2 in E.ChildNodes)
1476 {
1477 switch (N2.LocalName)
1478 {
1479 case "starttls":
1480 StartTls = true;
1481 break;
1482
1483 case "compression":
1484 foreach (XmlNode N3 in N2.ChildNodes)
1485 {
1486 if (N3.LocalName == "method")
1487 this.compressionMethods[N3.InnerText.Trim().ToUpper()] = true;
1488 }
1489 break;
1490
1491 case "bidi":
1492 Bidirectional = true;
1493 break;
1494
1495 case "dialback":
1496 Dialback = true;
1497 break;
1498
1499 case "mechanisms":
1500 foreach (XmlNode N3 in N2.ChildNodes)
1501 {
1502 if (N3.LocalName == "mechanism")
1503 {
1504 switch (N3.InnerText)
1505 {
1506 case "EXTERNAL":
1507 ExternalAuth = true;
1508 break;
1509 }
1510 }
1511 }
1512 break;
1513
1514 default:
1515 break;
1516 }
1517 }
1518
1519 if (StartTls && this.allowEncryption)
1520 return await this.BeginWrite("<starttls xmlns='" + XmppClientConnection.TlsNamespace + "'/>", null, null);
1521 else if (ExternalAuth)
1522 {
1523 sb = new StringBuilder();
1524
1525 if (Bidirectional)
1526 {
1527 sb.Append("<bidi xmlns='");
1528 sb.Append(BidirectionalNamespace);
1529 sb.Append("'/>");
1530 }
1531
1532 sb.Append("<auth xmlns='");
1533 sb.Append(XmppServer.SaslNamespace);
1534 sb.Append("' mechanism='EXTERNAL'>");
1535 sb.Append(Convert.ToBase64String(Encoding.UTF8.GetBytes(this.localDomain)));
1536 sb.Append("</auth>");
1537
1538 return await this.BeginWrite(sb.ToString(), null, null);
1539 }
1540 else if (Dialback && !this.verified)
1541 {
1542 await this.SetState(XmppS2sState.Verifying);
1543
1544 sb = new StringBuilder();
1545
1546 if (!string.IsNullOrEmpty(this.authKey))
1547 {
1548 sb.Append("<db:verify from='");
1549 sb.Append(XML.Encode(this.localDomain));
1550 sb.Append("' to='");
1551 sb.Append(XML.Encode(this.remoteDomain));
1552 sb.Append("' id='");
1553 sb.Append(XML.Encode(this.authStreamId));
1554 sb.Append("'>");
1555 sb.Append(XML.Encode(this.authKey));
1556 sb.Append("</db:verify>");
1557#if LogToWebHookTester
1558 _ = Task.Run(async () =>
1559 {
1560 try
1561 {
1562 await InternetContent.PostAsync(new Uri("https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
1563 new Dictionary<string, object>()
1564 {
1565 { "id", this.Id.ToString() },
1566 { "event", "Requesting verification" },
1567 { "local", this.localDomain.Value },
1568 { "remote", this.remoteDomain.Value },
1569 { "key", this.authKey },
1570 { "authStreamId", this.authStreamId },
1571 { "xml", sb.ToString() }
1572 },
1573 new KeyValuePair<string, string>("Accept", "application/json"));
1574 }
1575 catch (Exception ex)
1576 {
1577 Log.Exception(ex);
1578 }
1579 });
1580#endif
1581 this.authVerifyRequestSent = true;
1582 }
1583 else
1584 {
1585 this.authKey = this.server.GetDialbackKey(this.remoteDomain, this.localDomain, this.remoteStreamId);
1586 this.authResultRequestSent = true;
1587
1588 if (Bidirectional)
1589 {
1590 sb.Append("<bidi xmlns='");
1591 sb.Append(BidirectionalNamespace);
1592 sb.Append("'/>");
1593 }
1594
1595 sb.Append("<db:result from='");
1596 sb.Append(XML.Encode(this.localDomain));
1597 sb.Append("' to='");
1598 sb.Append(XML.Encode(this.remoteDomain));
1599 sb.Append("'>");
1600 sb.Append(this.authKey);
1601 sb.Append("</db:result>");
1602
1603#if LogToWebHookTester
1604 // TODO: Remove
1605 _ = Task.Run(async () =>
1606 {
1607 try
1608 {
1609 await InternetContent.PostAsync(new Uri("https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
1610 new Dictionary<string, object>()
1611 {
1612 { "id", this.Id.ToString() },
1613 { "event", "Verification result" },
1614 { "local", this.localDomain.Value },
1615 { "remote", this.remoteDomain.Value },
1616 { "key", this.authKey },
1617 { "bidirectional", Bidirectional },
1618 { "xml", sb.ToString() }
1619 },
1620 new KeyValuePair<string, string>("Accept", "application/json"));
1621 }
1622 catch (Exception ex)
1623 {
1624 Log.Exception(ex);
1625 }
1626 });
1627#endif
1628 }
1629
1630 return await this.BeginWrite(sb.ToString(), null, null);
1631 }
1632 }
1633 break;
1634
1635 case "proceed":
1636 this.upgradeToTlsAsClient = true;
1637 return false;
1638
1639 case "starttls":
1640 if (await this.BeginWrite("<proceed xmlns='" + XmppClientConnection.TlsNamespace + "'/>", null, null))
1641 this.upgradeToTlsAsServer = true;
1642 return false;
1643
1644 case "bidi":
1645 if (E.NamespaceURI == BidirectionalNamespace)
1646 this.bidirectional = true;
1647 break;
1648
1649 case "success":
1650 sb = new StringBuilder();
1651
1652 sb.Append("<?xml version='1.0' encoding='utf-8'?><stream:stream id='");
1653 sb.Append(this.localStreamId);
1654 sb.Append("' to='");
1655 sb.Append(XML.Encode(this.remoteDomain));
1656 sb.Append("' from='");
1657 sb.Append(XML.Encode(this.localDomain));
1658 sb.Append("' version='1.0' xmlns='jabber:server' xmlns:db='");
1659 sb.Append(DialbackNamespace);
1660 sb.Append("' xmlns:stream='");
1662 sb.Append("'>");
1663
1664 if (!await this.BeginWrite(sb.ToString(), null, null))
1665 return false;
1666
1667 this.verified = true;
1668 this.DialbackCompleted(true, false, null);
1669 break;
1670
1671 case "failure":
1672 this.DialbackCompleted(false, false, "Authentication failed.");
1673 break;
1674
1675 case "auth":
1676 if (E.NamespaceURI == XmppServer.SaslNamespace && XML.Attribute(E, "mechanism") == "EXTERNAL")
1677 {
1678 string s = E.InnerText;
1679 CaseInsensitiveString Domain;
1680
1681 if (s == "=")
1682 Domain = this.remoteDomain;
1683 else
1684 {
1685 byte[] Bin = Convert.FromBase64String(s);
1686 Domain = Encoding.UTF8.GetString(Bin);
1687 }
1688
1689 bool RemoteCertificateValid = this.client.RemoteCertificateValid;
1690 bool ValidDomain = (string.IsNullOrEmpty(Domain) || Domain == this.remoteDomain);
1691 bool RemoteDomainsInCertificate = !(this.remoteDomainCertificateDomains is null);
1692 bool DomainInCertificate = Array.IndexOf(this.remoteDomainCertificateDomains, this.remoteDomain) >= 0;
1693
1694 if (RemoteCertificateValid &&
1695 ValidDomain &&
1696 RemoteDomainsInCertificate &&
1697 DomainInCertificate)
1698 {
1699 if (!await this.BeginWrite("<success xmlns='" + XmppServer.SaslNamespace + "'/>", null, null))
1700 return false;
1701
1702 this.verified = true;
1703 this.DialbackCompleted(true, false, null);
1704 }
1705 else
1706 {
1707 if (!await this.BeginWrite("<failure xmlns='" + XmppServer.SaslNamespace + "'><not-authorized/></failure>", null, null))
1708 return false;
1709
1710 if (!RemoteCertificateValid)
1711 this.DialbackCompleted(false, false, "Remote certificate is not valid.");
1712 else if (!ValidDomain)
1713 this.DialbackCompleted(false, false, "Presented domain not valid.");
1714 else if (!RemoteDomainsInCertificate)
1715 this.DialbackCompleted(false, false, "Remote domains not available in certificate.");
1716 else if (!DomainInCertificate)
1717 this.DialbackCompleted(false, false, "Presented domain not in certificate.");
1718 else
1719 this.DialbackCompleted(false, false, "Something went wrong.");
1720 }
1721 }
1722 break;
1723
1724 case "result":
1725 if (E.NamespaceURI == DialbackNamespace)
1726 {
1727 Type = XML.Attribute(E, "type");
1728 if (string.IsNullOrEmpty(Type))
1729 {
1730#if LogToWebHookTester
1731 // TODO: Remove
1732 _ = Task.Run(async () =>
1733 {
1734 try
1735 {
1736 await InternetContent.PostAsync(new Uri("https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
1737 new Dictionary<string, object>()
1738 {
1739 { "id", this.Id.ToString() },
1740 { "event", "Verification result received" },
1741 { "local", this.localDomain.Value },
1742 { "remote", this.remoteDomain.Value },
1743 { "key", this.authKey },
1744 { "verified", this.verified }
1745 },
1746 new KeyValuePair<string, string>("Accept", "application/json"));
1747 }
1748 catch (Exception ex)
1749 {
1750 Log.Exception(ex);
1751 }
1752 });
1753#endif
1754 if (this.verified)
1755 {
1756 sb = new StringBuilder();
1757
1758 sb.Append("<db:result to='");
1759 sb.Append(XML.Encode(this.remoteDomain));
1760 sb.Append("' from='");
1761 sb.Append(XML.Encode(this.localDomain));
1762 sb.Append("' valid='true'/>");
1763 }
1764 else
1765 {
1766 this.Information("Performing dialback to validate domain name claim.");
1767 await this.SetState(XmppS2sState.Dialback);
1768
1769 try
1770 {
1771#if LogToWebHookTester
1772 // TODO: Remove
1773 _ = Task.Run(async () =>
1774 {
1775 try
1776 {
1777 await InternetContent.PostAsync(new Uri("https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
1778 new Dictionary<string, object>()
1779 {
1780 { "id", this.Id.ToString() },
1781 { "event", "Performing dialback" },
1782 { "local", this.localDomain.Value },
1783 { "remote", this.remoteDomain.Value },
1784 { "key", E.InnerText },
1785 { "authStreamId", this.localStreamId }
1786 },
1787 new KeyValuePair<string, string>("Accept", "application/json"));
1788 }
1789 catch (Exception ex)
1790 {
1791 Log.Exception(ex);
1792 }
1793 });
1794#endif
1795 IS2SEndpoint AuthEndpoint = await this.server.GetS2sEndpoint(this.localDomain, this.remoteDomain,
1796 false, "Performing dialback to validate domain name claim.");
1797
1798 if (AuthEndpoint is XmppS2SEndpoint XmppS2SEndpoint)
1799 {
1800 XmppS2SEndpoint.authKey = E.InnerText;
1801 XmppS2SEndpoint.authStreamId = this.localStreamId;
1802 XmppS2SEndpoint.authConnection = this;
1803 }
1804 }
1805 catch (Exception ex)
1806 {
1807 Log.Exception(ex);
1808
1809 sb = new StringBuilder();
1810
1811 XmppServer.GetErrorInformation(ex, out string ErrorType, out string ErrorXml);
1812
1813#if LogToWebHookTester
1814 // TODO: Remove
1815 _ = Task.Run(async () =>
1816 {
1817 try
1818 {
1819 await InternetContent.PostAsync(new Uri("https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
1820 new Dictionary<string, object>()
1821 {
1822 { "id", this.Id.ToString() },
1823 { "event", "Verification error" },
1824 { "local", this.localDomain.Value },
1825 { "remote", this.remoteDomain.Value },
1826 { "key", this.authKey },
1827 { "errorType", ErrorType },
1828 { "errorXml", ErrorXml },
1829 { "exceptionMessage", ex.Message }
1830 },
1831 new KeyValuePair<string, string>("Accept", "application/json"));
1832 }
1833 catch (Exception ex2)
1834 {
1835 Log.Exception(ex2);
1836 }
1837 });
1838#endif
1839 sb.Append("<db:result to='");
1840 sb.Append(XML.Encode(this.remoteDomain));
1841 sb.Append("' from='");
1842 sb.Append(XML.Encode(this.localDomain));
1843 sb.Append("' type='error'><error type='");
1844 sb.Append(ErrorType);
1845 sb.Append("'>");
1846 sb.Append(ErrorXml);
1847 sb.Append("<text xmlns='");
1848 sb.Append(XmppServer.StanzaNamespace);
1849 sb.Append("'>");
1850 sb.Append(XML.Encode(ex.Message));
1851 sb.Append("</text></error></db:result>");
1852
1853 if (!await this.BeginWrite(sb.ToString(), null, null))
1854 return false;
1855 }
1856 }
1857 }
1858 else if (this.authResultRequestSent)
1859 {
1860 XmppS2SEndpoint Endpoint = this.authConnection ?? this;
1861
1862 if (Type == "valid")
1863 {
1864 Endpoint.verified = true;
1865 Endpoint.DialbackCompleted(true, true, null);
1866 }
1867 else
1868 Endpoint.DialbackCompleted(false, true, "Remote end rejects authentication result.");
1869
1870 if (!(this.authConnection is null))
1871 {
1872 this.authConnection = null;
1873 await this.CleanUp(this, XmppS2sState.Offline, "Dialback check completed.");
1874 }
1875 }
1876 }
1877 break;
1878
1879 case "verify":
1880 if (E.NamespaceURI == DialbackNamespace)
1881 {
1882 From = new XmppAddress(XML.Attribute(E, "from"));
1883 To = new XmppAddress(XML.Attribute(E, "to"));
1884 Id = XML.Attribute(E, "id");
1885 Type = XML.Attribute(E, "type");
1886
1887 if (string.IsNullOrEmpty(Type))
1888 {
1889 string Key = this.server.GetDialbackKey(From.Address, To.Address, Id);
1890 sb = new StringBuilder();
1891
1892 this.verified = Key == E.InnerText;
1893
1894 sb.Append("<db:verify to='");
1895 sb.Append(XML.Encode(From.Address));
1896 sb.Append("' from='");
1897 sb.Append(XML.Encode(To.Address));
1898 sb.Append("' id='");
1899 sb.Append(XML.Encode(Id));
1900 sb.Append("' type='");
1901 sb.Append(this.verified ? "valid" : "invalid");
1902 sb.Append("'/>");
1903
1904#if LogToWebHookTester
1905 // TODO: Remove
1906 _ = Task.Run(async () =>
1907 {
1908 try
1909 {
1910 await InternetContent.PostAsync(new Uri("https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
1911 new Dictionary<string, object>()
1912 {
1913 { "id", this.Id.ToString() },
1914 { "event", "verify stanza" },
1915 { "local", this.localDomain.Value },
1916 { "remote", this.remoteDomain.Value },
1917 { "verified", this.verified },
1918 { "key", Key },
1919 { "receivedKey", E.InnerText },
1920 { "xml", sb.ToString() }
1921 },
1922 new KeyValuePair<string, string>("Accept", "application/json"));
1923 }
1924 catch (Exception ex)
1925 {
1926 Log.Exception(ex);
1927 }
1928 });
1929#endif
1930 if (this.temporary)
1931 {
1932 await this.BeginWrite(sb.ToString(), (Sender, e) => this.DisposeAsync("Verification failed."), null);
1933 return false;
1934 }
1935 else
1936 {
1937 if (!await this.BeginWrite(sb.ToString(), null, null))
1938 return false;
1939
1940 if (this.verified && this.state == XmppS2sState.Connected)
1941 {
1942 if (!await this.SendQueued())
1943 return false;
1944 }
1945 }
1946 }
1947 else
1948 {
1949 this.verified = Type == "valid";
1950
1951 if (!(this.authConnection is null))
1952 {
1953 try
1954 {
1955 if (this.verified)
1956 {
1957 this.authConnection.KeyAuthenticated(
1958 S2sValidationResult.Valid, null);
1959 }
1960 else
1961 {
1962 this.authConnection.KeyAuthenticated(
1963 S2sValidationResult.Invalid,
1964 "Remote validation failed.");
1965
1966 }
1967 }
1968 catch (Exception ex)
1969 {
1970 Log.Exception(ex);
1971 }
1972
1973 this.authConnection = null;
1974 }
1975
1976 if (this.authVerifyRequestSent)
1977 {
1978 await this.DisposeAsync("Verification request already sent.");
1979 return false;
1980 }
1981 else if (this.verified)
1982 {
1983 if (!await this.SendQueued())
1984 return false;
1985 }
1986 }
1987 }
1988 break;
1989
1990 default:
1991 break;
1992 }
1993 }
1994 catch (Exception ex)
1995 {
1996 await this.ConnectionError(ex);
1997 return false;
1998 }
1999
2000 return true;
2001 }
2002
2003 private async void ProcessIq(string Id, XmppAddress To, XmppAddress From, string Type, string Language, Stanza Stanza)
2004 {
2005 try
2006 {
2007 await this.server.ProcessIq(Id, To, From, Type, Language, Stanza, this);
2008 }
2009 catch (Exception ex)
2010 {
2011 this.Exception(ex);
2012 }
2013 }
2014
2015 private async void ProcessMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza)
2016 {
2017 try
2018 {
2019 await this.server.Message(Type, Id, To, From, Language, Stanza, this);
2020 }
2021 catch (Exception ex)
2022 {
2023 this.Exception(ex);
2024 }
2025 }
2026
2027 private async void ProcessPresence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza)
2028 {
2029 try
2030 {
2031 await this.server.Presence(Type, Id, To, From, Language, Stanza, this);
2032 }
2033 catch (Exception ex)
2034 {
2035 this.Exception(ex);
2036 }
2037 }
2038
2039 private async Task Client_OnPaused(object Sender, EventArgs e)
2040 {
2041 if (this.upgradeToTlsAsClient || this.upgradeToTlsAsServer)
2042 {
2043 bool AsClient = this.upgradeToTlsAsClient;
2044
2045 this.upgradeToTlsAsClient = false;
2046 this.upgradeToTlsAsServer = false;
2047
2048 string RemoteEndPoint = this.client.RemoteEndPoint.RemovePortNumber();
2049
2050 if (AsClient || LoginAuditor.CanStartTls(RemoteEndPoint))
2051 {
2052 try
2053 {
2054 if (AsClient)
2055 {
2056 await this.SetState(XmppS2sState.StartingEncryptionAsClient);
2057 await this.client.UpgradeToTlsAsClient(this.localDomainCertificate, Crypto.SecureTls, null, this.trustServer, this.remoteDomain, "xmpp-server");
2058 }
2059 else
2060 {
2061 await this.SetState(XmppS2sState.StartingEncryptionAsServer);
2062 await this.client.UpgradeToTlsAsServer(this.localDomainCertificate, Crypto.SecureTls, ClientCertificates.Optional, null, this.trustServer, "xmpp-server");
2063 }
2064
2065 if (!(this.client.RemoteCertificate is null))
2066 {
2067 this.remoteDomainCertificateDomains = this.GetIdentities(this.client.RemoteCertificate);
2068
2069 if (this.HasSniffers)
2070 {
2071 StringBuilder sb = new StringBuilder();
2072 string Subject = this.client.RemoteCertificate.Subject;
2073
2074 sb.Append("Remote Certificate received. Valid: ");
2075 sb.Append(this.client.RemoteCertificateValid.ToString());
2076 sb.Append(", SslPolicyErrors: ");
2077 sb.Append(this.client.RemoteSslPolicyErrors.ToString());
2078 sb.Append(", Subject: ");
2079 sb.Append(Subject);
2080
2081 if (Subject.StartsWith("CN="))
2082 Subject = Subject[3..];
2083
2084 bool First = true;
2085
2086 foreach (CaseInsensitiveString Name in this.remoteDomainCertificateDomains)
2087 {
2088 if (Name != Subject)
2089 {
2090 if (First)
2091 {
2092 sb.Append(", Alternative Names: ");
2093 First = false;
2094 }
2095 else
2096 sb.Append("; ");
2097
2098 sb.Append(Name);
2099 }
2100 }
2101
2102 sb.Append(", Issuer: ");
2103 sb.Append(this.client.RemoteCertificate.Issuer);
2104 sb.Append(", S/N: ");
2105 sb.Append(this.client.RemoteCertificate.GetSerialNumberString());
2106 sb.Append(", Hash: ");
2107 sb.Append(this.client.RemoteCertificate.GetCertHashString());
2108
2109 if (!this.client.RemoteCertificateValid)
2110 {
2111 try
2112 {
2113 byte[] Bin = this.client.RemoteCertificate.GetRawCertData();
2114 sb.AppendLine();
2115 sb.AppendLine();
2116 sb.Append(Convert.ToBase64String(Bin, Base64FormattingOptions.InsertLineBreaks));
2117 }
2118 catch (Exception)
2119 {
2120 sb.Append(" No raw data in certificate.");
2121 }
2122 }
2123
2124 string Msg = sb.ToString();
2125
2126 this.Information(Msg);
2127
2128 if (!this.client.RemoteCertificateValid)
2129 Log.Warning(sb.ToString());
2130 }
2131 }
2132
2133 this.ResetState();
2134 this.client.Continue();
2135
2136 if (AsClient)
2137 {
2138 await this.BeginWrite("<?xml version='1.0' encoding='utf-8'?><stream:stream id='" + this.localStreamId +
2139 "' from='" + XML.Encode(this.localDomain) + "' to='" + XML.Encode(this.remoteDomain) +
2140 "' version='1.0' xmlns='jabber:server' xmlns:db='" + DialbackNamespace + "' xmlns:stream='" +
2141 XmppClientConnection.StreamNamespace + "'>", null, null);
2142 }
2143 }
2144 catch (AuthenticationException ex)
2145 {
2146 await this.LoginFailure(ex, AsClient, RemoteEndPoint);
2147 }
2148 catch (Win32Exception ex)
2149 {
2150 await this.LoginFailure(ex, AsClient, RemoteEndPoint);
2151 }
2152 catch (Exception ex)
2153 {
2154 await this.ConnectionError(ex);
2155 }
2156 }
2157 else
2158 await this.ConnectionError(new Exception("Remote endpoint rejected due to suspected TLS hacking."));
2159 }
2160 }
2161
2162 private async Task LoginFailure(Exception ex, bool AsClient, string RemoteIpEndpoint)
2163 {
2164 if (!AsClient)
2165 {
2166 Exception ex2 = Log.UnnestException(ex);
2167 LoginAuditor.ReportTlsHackAttempt(RemoteIpEndpoint, "TLS handshake failed: " + ex2.Message, "XMPP S2S");
2168
2169 await this.ConnectionError(ex);
2170 }
2171 }
2172
2173 private async Task<bool> CheckFrom(XmppAddress From)
2174 {
2175 if (this.remoteDomain is null)
2176 return true;
2177
2178 CaseInsensitiveString FromDomain = From.Domain;
2179
2180 if (FromDomain == this.remoteDomain)
2181 return true;
2182
2183 if (FromDomain.EndsWith("." + this.remoteDomain, StringComparison.CurrentCultureIgnoreCase))
2184 return true;
2185
2186 await this.BeginWrite("<stream:error><invalid-from xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error></stream:stream>", async (Sender, e) =>
2187 {
2188 await this.CleanUp(this, XmppS2sState.Error, "Invalid from attribute value.");
2189 }, null);
2190
2191 return false;
2192 }
2193
2194 private async void KeyAuthenticated(S2sValidationResult ValidationResult, string Reason)
2195 {
2196 try
2197 {
2198 if (this.client?.Connected ?? false)
2199 {
2200 if (string.IsNullOrEmpty(this.authKey))
2201 {
2202 StringBuilder Xml = new StringBuilder();
2203
2204 Xml.Append("<db:result from='");
2205 Xml.Append(XML.Encode(this.localDomain));
2206 Xml.Append("' to='");
2207 Xml.Append(XML.Encode(this.remoteDomain));
2208 Xml.Append("' type='");
2209
2210 switch (ValidationResult)
2211 {
2212 case S2sValidationResult.Valid:
2213 Xml.Append("valid'/>");
2214 break;
2215
2216 case S2sValidationResult.Invalid:
2217 Xml.Append("invalid'/>");
2218 break;
2219
2220 case S2sValidationResult.Error:
2221 default:
2222 Xml.Append("error'><error type='wait'>");
2223 Xml.Append("<internal-server-error xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>");
2224
2225 if (!string.IsNullOrEmpty(Reason))
2226 {
2227 Xml.Append("<text xmlns='");
2228 Xml.Append(XmppServer.StanzaNamespace);
2229 Xml.Append("'>");
2230 Xml.Append(XML.Encode(Reason));
2231 Xml.Append("</text>");
2232 }
2233
2234 Xml.Append("</error></db:result>");
2235 break;
2236 }
2237
2238#if LogToWebHookTester
2239 // TODO: Remove
2240 _ = Task.Run(async () =>
2241 {
2242 try
2243 {
2244 await InternetContent.PostAsync(new Uri("https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
2245 new Dictionary<string, object>()
2246 {
2247 { "id", this.Id.ToString() },
2248 { "event", "KeyAuthenticated" },
2249 { "local", this.localDomain.Value },
2250 { "remote", this.remoteDomain.Value },
2251 { "type", ValidationResult },
2252 { "reason", Reason },
2253 { "xml", Xml.ToString() }
2254 },
2255 new KeyValuePair<string, string>("Accept", "application/json"));
2256 }
2257 catch (Exception ex)
2258 {
2259 Log.Exception(ex);
2260 }
2261 });
2262#endif
2263 await this.BeginWrite(Xml.ToString(), null, null);
2264 }
2265
2266 if (ValidationResult == S2sValidationResult.Valid)
2267 {
2268 await this.SetState(XmppS2sState.Connected);
2269
2270 Log.Informational("XMPP S2S connection accepted.", this.remoteDomain, this.localDomain, "XmppIncomingS2SSuccess", EventLevel.Minor,
2271 new KeyValuePair<string, object>("RemoteEndPoint", this.client.RemoteEndPoint));
2272
2273 if (this.bidirectional && !this.temporary)
2274 this.server.RegisterS2SEndpoint(this);
2275
2276 await this.SendQueued();
2277 }
2278 else
2279 {
2280 Log.Warning("XMPP S2S connection failed.", this.remoteDomain, this.localDomain,
2281 "XmppIncomingS2SFailure", EventLevel.Minor,
2282 new KeyValuePair<string, object>("RemoteEndPoint", this.client.RemoteEndPoint),
2283 new KeyValuePair<string, object>("Reason", Reason));
2284 }
2285 }
2286 }
2287 catch (Exception ex)
2288 {
2289 this.Exception(ex);
2290 }
2291 }
2292
2293 internal static Exception GetStreamExceptionObject(XmlElement E)
2294 {
2295 return GetExceptionObject(E, XmppServer.StreamsNamespace);
2296 }
2297
2298 internal static Exception GetExceptionObject(XmlElement E, string Namespace)
2299 {
2300 string Msg = string.Empty;
2301
2302 foreach (XmlNode N2 in E.ChildNodes)
2303 {
2304 if (N2.LocalName == "text")
2305 Msg = N2.InnerText.Trim();
2306 }
2307
2308 foreach (XmlNode N2 in E.ChildNodes)
2309 {
2310 if (N2.NamespaceURI == Namespace)
2311 {
2312 if (string.IsNullOrEmpty(Msg))
2313 Msg = N2.LocalName;
2314
2315 return new Exception(Msg);
2316 }
2317 }
2318
2319 return new Exception(string.IsNullOrEmpty(Msg) ? "Unspecified error returned." : Msg);
2320 }
2321
2322 internal static Exception GetStanzaExceptionObject(XmlElement E)
2323 {
2324 return GetExceptionObject(E, XmppServer.StanzaNamespace);
2325 }
2326
2327 internal static Exception GetSaslExceptionObject(XmlElement E)
2328 {
2329 return GetExceptionObject(E, XmppServer.SaslNamespace);
2330 }
2331
2332 internal CaseInsensitiveString[] GetIdentities(X509Certificate certificate)
2333 {
2334 List<CaseInsensitiveString> Domains = new List<CaseInsensitiveString>();
2335 bool HasAlternativeNames = false;
2336
2337 try
2338 {
2339 foreach (string Part in certificate.Subject.Split(certificateSubjectSeparator, StringSplitOptions.None))
2340 {
2341 if (Part.StartsWith("CN="))
2342 Domains.Add(Part[3..]);
2343 else if (Part.StartsWith("SAN="))
2344 {
2345 Domains.Add(Part[4..]);
2346 HasAlternativeNames = true;
2347 }
2348 }
2349
2350 if (!HasAlternativeNames)
2351 {
2352 if (!(certificate is X509Certificate2 Cert2))
2353 {
2354 byte[] Bin = certificate.GetRawCertData();
2355 Cert2 = new X509Certificate2(Bin);
2356 }
2357
2358 foreach (X509Extension Extension in Cert2.Extensions)
2359 {
2360 if (Extension.Oid.Value == "2.5.29.17") // Subject Alternative Name
2361 {
2362 AsnEncodedData Parsed = new AsnEncodedData(Extension.Oid, Extension.RawData);
2363 string[] SAN = Parsed.Format(true).Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
2364
2365 foreach (string Name in SAN)
2366 {
2367 int i = Name.LastIndexOf('=');
2368 if (i > 0)
2369 Domains.Add(Name[(i + 1)..]);
2370 }
2371 }
2372 }
2373 }
2374 }
2375 catch (Exception ex)
2376 {
2377 StringBuilder sb = new StringBuilder();
2378
2379 sb.Append("Unable to extract domain names from certificate subject (");
2380 sb.Append(ex.Message);
2381 sb.Append(").");
2382
2383 try
2384 {
2385 byte[] Bin = certificate.GetRawCertData();
2386 sb.AppendLine();
2387 sb.AppendLine();
2388 sb.Append(Convert.ToBase64String(Bin, Base64FormattingOptions.InsertLineBreaks));
2389 }
2390 catch (Exception)
2391 {
2392 sb.Append(" No raw data in certificate.");
2393 }
2394
2395 this.Error(sb.ToString());
2396 }
2397
2398 return Domains.ToArray();
2399 }
2400
2401 private readonly static string[] certificateSubjectSeparator = new string[] { ", " };
2402
2403 private async void DialbackCompleted(bool Successful, bool SendQueued, string Reason)
2404 {
2405 try
2406 {
2407 if (Successful)
2408 {
2409 await this.SetState(XmppS2sState.Connected);
2410 this.supportsPing = true;
2411 this.secondTimer = new Timer(this.SecondTimerCallback, null, 1000, 1000);
2412
2413 this.Information("XMPP S2S connection successful.");
2414 Log.Informational("XMPP S2S connection successful.", this.remoteDomain, this.localDomain, "XmppOutgoingS2SSuccess", EventLevel.Minor,
2415 new KeyValuePair<string, object>("RemoteEndPoint", this.client.RemoteEndPoint));
2416
2417 if (SendQueued)
2418 await this.SendQueued();
2419 }
2420 else
2421 {
2422 await this.SetState(XmppS2sState.Error, "Dialback failed.");
2423
2424 this.Information("XMPP S2S connection failed: " + Reason);
2425 Log.Warning("XMPP S2S connection failed.", this.remoteDomain, this.localDomain, "XmppOutgoingS2SFailure", EventLevel.Minor,
2426 new KeyValuePair<string, object>("RemoteEndPoint", this.client.RemoteEndPoint),
2427 new KeyValuePair<string, object>("Reason", Reason));
2428
2429 if (SendQueued)
2430 {
2431 lock (this.synchObject)
2432 {
2433 this.queue = null;
2434 this.stanzasOnHold = null;
2435 }
2436 }
2437 }
2438 }
2439 catch (Exception ex)
2440 {
2441 this.Exception(ex);
2442 }
2443 }
2444
2449 public int KeepAliveSeconds
2450 {
2451 get => this.keepAliveSeconds;
2452 set
2453 {
2454 if (value <= 0)
2455 throw new ArgumentException("Value must be positive.", nameof(this.KeepAliveSeconds));
2456
2457 this.keepAliveSeconds = value;
2458 }
2459 }
2460
2461 private async void SecondTimerCallback(object State)
2462 {
2463 try
2464 {
2465 if (!this.checkConnection)
2466 return;
2467
2468 DateTime UtcNow = DateTime.UtcNow;
2469
2470 if (UtcNow >= this.nextPingUtc && this.state == XmppS2sState.Connected)
2471 {
2472 this.server.TouchServerConnection(this.remoteDomain);
2473
2474 this.nextPingUtc = DateTime.UtcNow.AddMilliseconds(this.keepAliveSeconds * 500);
2475 try
2476 {
2477 if (this.supportsPing)
2478 {
2479 if (this.pingResponse)
2480 {
2481 this.pingResponse = false;
2482 await this.SendPing();
2483 }
2484 else
2485 await this.DisposeAsync("No ping response.");
2486 }
2487 else
2488 await this.BeginWrite(" ", null, null);
2489 }
2490 catch (Exception ex)
2491 {
2492 Log.Exception(ex);
2493 this.Exception(ex);
2494 await this.DisposeAsync(ex.Message);
2495 }
2496 }
2497 }
2498 catch (Exception ex)
2499 {
2500 Log.Exception(ex);
2501 }
2502 }
2503
2507 public Task<bool> SendPing()
2508 {
2509 StringBuilder Xml = new StringBuilder();
2510
2511 this.pingId = this.server.NewId(16);
2512
2513 Xml.Append("<iq type='get' id='");
2514 Xml.Append(this.pingId);
2515 Xml.Append("' from='");
2516 Xml.Append(XML.Encode(this.localDomain));
2517 Xml.Append("' to='");
2518 Xml.Append(XML.Encode(this.remoteDomain));
2519 Xml.Append("'><ping xmlns='");
2520 Xml.Append(XmppServer.PingNamespace);
2521 Xml.Append("'/></iq>");
2522
2523 return this.BeginWrite(Xml.ToString(), null, null);
2524 }
2525
2526 #region IRecipient
2527
2529 public override Task<bool> IQ(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
2530 {
2531 return this.SendStanza("iq", Type, Id, To, From, Language, Stanza?.Content, Sender);
2532 }
2533
2535 public override Task<bool> IQ(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
2536 {
2537 return this.SendStanza("iq", Type, Id, To, From, Language, ContentXml, Sender);
2538 }
2539
2540 internal async Task<bool> SendStanza(string StanzaType, string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
2541 {
2542 bool Error;
2543
2544 lock (this.synchObject)
2545 {
2546 if (this.state == XmppS2sState.Error)
2547 Error = true;
2548 else if (this.state == XmppS2sState.Connected)
2549 Error = false;
2550 else
2551 {
2552 this.queue ??= new ChunkedList<QueuedStanza>();
2553 this.queue.Add(new QueuedStanza()
2554 {
2555 StanzaType = StanzaType,
2556 Type = Type,
2557 Id = Id,
2558 To = To,
2559 From = From,
2560 Language = Language,
2561 ContentXml = ContentXml,
2562 Sender = Sender
2563 });
2564
2565 return true;
2566 }
2567 }
2568
2569 if (Error)
2570 {
2571 if (!(Sender is null))
2572 {
2573 switch (StanzaType.ToLower())
2574 {
2575 case "iq":
2576 if (Sender is null)
2577 return true;
2578 else
2579 return !(await Sender.IqError(Id, From, To, this.errorType, this.errorXml, "S2S connection failed.", "en") is null);
2580
2581 case "message":
2582 if (!await (Sender?.MessageError(Id, From, To, this.errorType, this.errorXml, "S2S connection failed.", "en") ?? Task.FromResult(true)))
2583 return false;
2584 break;
2585
2586 case "presence":
2587 if (!await (Sender?.PresenceError(Id, From, To, this.errorType, this.errorXml, "S2S connection failed.", "en") ?? Task.FromResult(true)))
2588 return false;
2589 break;
2590 }
2591 }
2592 }
2593 else
2594 {
2595 try
2596 {
2597 if (!await this.BeginWrite(StanzaType, Type, Id, To, From, Language, ContentXml))
2598 return false;
2599 }
2600 catch (Exception ex)
2601 {
2602 if (!(Sender is null))
2603 {
2604 try
2605 {
2606 switch (StanzaType.ToLower())
2607 {
2608 case "iq":
2609 if (Sender is null)
2610 return true;
2611 else
2612 return !(await Sender.IqError(Id, From, To, this.errorType, this.errorXml, "S2S connection failed: " + ex.Message, "en") is null);
2613
2614 case "message":
2615 if (!await (Sender?.MessageError(Id, From, To, this.errorType, this.errorXml, "S2S connection failed: " + ex.Message, "en") ?? Task.FromResult(true)))
2616 return false;
2617 break;
2618
2619 case "presence":
2620 if (!await (Sender?.PresenceError(Id, From, To, this.errorType, this.errorXml, "S2S connection failed: " + ex.Message, "en") ?? Task.FromResult(true)))
2621 return false;
2622 break;
2623 }
2624 }
2625 catch (Exception ex2)
2626 {
2627 Log.Exception(ex2);
2628 }
2629 }
2630 }
2631 }
2632
2633 return true;
2634 }
2635
2636 private Task<bool> BeginWrite(string StanzaType, string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
2637 {
2638 StringBuilder Xml = new StringBuilder();
2639
2640 Xml.Append('<');
2641 Xml.Append(StanzaType);
2642
2643 if (!From.IsEmpty)
2644 {
2645 Xml.Append(" from='");
2646 Xml.Append(XML.Encode(From.Address));
2647 }
2648
2649 if (!To.IsEmpty)
2650 {
2651 Xml.Append("' to='");
2652 Xml.Append(XML.Encode(To.Address));
2653 }
2654
2655 if (!string.IsNullOrEmpty(Type))
2656 {
2657 Xml.Append("' type='");
2658 Xml.Append(Type);
2659 }
2660
2661 if (!string.IsNullOrEmpty(Id))
2662 {
2663 Xml.Append("' id='");
2664 Xml.Append(XML.Encode(Id));
2665 }
2666
2667 if (!string.IsNullOrEmpty(Language))
2668 {
2669 Xml.Append("' xml:lang='");
2670 Xml.Append(XML.Encode(Language));
2671 }
2672
2673 if (string.IsNullOrEmpty(ContentXml))
2674 Xml.Append("'/>");
2675 else
2676 {
2677 Xml.Append("'>");
2678 Xml.Append(ContentXml);
2679 Xml.Append("</");
2680 Xml.Append(StanzaType);
2681 Xml.Append('>');
2682 }
2683
2684 return this.BeginWrite(Xml.ToString(), null, null);
2685 }
2686
2688 public override Task<bool> Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
2689 {
2690 return this.SendStanza("message", Type, Id, To, From, Language, Stanza?.Content, Sender);
2691 }
2692
2694 public override Task<bool> Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
2695 {
2696 return this.SendStanza("message", Type, Id, To, From, Language, ContentXml, Sender);
2697 }
2698
2700 public override Task<bool> Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
2701 {
2702 return this.SendStanza("presence", Type, Id, To, From, Language, Stanza?.Content, Sender);
2703 }
2704
2706 public override Task<bool> Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
2707 {
2708 return this.SendStanza("presence", Type, Id, To, From, Language, ContentXml, Sender);
2709 }
2710
2711 #endregion
2712
2713 #region ISender
2714
2716 public override Task<bool> IqError(string Id, XmppAddress To, XmppAddress From, string ErrorXml)
2717 {
2718 StringBuilder Xml = new StringBuilder();
2719
2720 Xml.Append("<iq");
2721 XmppClientConnection.AppendParameters(Xml, "error", Id, To, From, string.Empty, (Stanza)null);
2722 Xml.Append('>');
2723 Xml.Append(ErrorXml);
2724 Xml.Append("</iq>");
2725
2726 return this.BeginWrite(Xml.ToString(), null, null);
2727 }
2728
2730 public override Task<string> IqError(string Id, XmppAddress To, XmppAddress From, Exception ex)
2731 {
2732 XmppServer.GetErrorInformation(ex, out string Type, out string Xml);
2733 return this.IqError(Id, To, From, Type, Xml, ex.Message + "\r\n\r\n" + Log.CleanStackTrace(ex.StackTrace), string.Empty);
2734 }
2735
2737 public override Task<bool> IqResult(string Id, XmppAddress To, XmppAddress From, string ResultXml)
2738 {
2739 StringBuilder Xml = new StringBuilder();
2740
2741 Xml.Append("<iq");
2742 XmppClientConnection.AppendParameters(Xml, "result", Id, To, From, string.Empty, (Stanza)null);
2743 Xml.Append('>');
2744 Xml.Append(ResultXml);
2745 Xml.Append("</iq>");
2746
2747 return this.BeginWrite(Xml.ToString(), null, null);
2748 }
2749
2751 public override Task<bool> Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
2752 {
2753 return this.SendStanza("presence", Type, Id, To, From, Language, ContentXml, null);
2754 // TODO: Errors in SendStanza should be returned, which does not happen since last parameter is null.
2755 }
2756
2758 public override Task<bool> PresenceError(string Id, XmppAddress To, XmppAddress From, string ErrorXml)
2759 {
2760 StringBuilder Xml = new StringBuilder();
2761
2762 Xml.Append("<presence id='");
2763 Xml.Append(Id);
2764 Xml.Append("' from='");
2765 Xml.Append(From);
2766 Xml.Append("' to='");
2767 Xml.Append(To);
2768 Xml.Append("' type='error'>");
2769 Xml.Append(ErrorXml);
2770 Xml.Append("</presence>");
2771
2772 return this.BeginWrite(Xml.ToString(), null, null);
2773 }
2774
2776 public override Task<bool> PresenceError(string Id, XmppAddress To, XmppAddress From, Exception ex)
2777 {
2778 XmppServer.GetErrorInformation(ex, out string Type, out string Xml);
2779 return this.PresenceError(Id, To, From, Type, Xml, ex.Message + "\r\n\r\n" + Log.CleanStackTrace(ex.StackTrace), string.Empty);
2780 }
2781
2783 public override Task<bool> Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
2784 {
2785 return this.Message(Type, Id, To, From, Language, ContentXml, null);
2786 }
2787
2789 public override Task<bool> MessageError(string Id, XmppAddress To, XmppAddress From, Exception ex)
2790 {
2791 XmppServer.GetErrorInformation(ex, out string Type, out string Xml);
2792 return this.MessageError(Id, To, From, Type, Xml, ex.Message + "\r\n\r\n" + Log.CleanStackTrace(ex.StackTrace), string.Empty);
2793 }
2794
2795 #endregion
2796
2797 }
2798}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static string CleanStackTrace(string StackTrace)
Cleans a Stack Trace string, removing entries from the asynchronous execution model,...
Definition: Log.cs:194
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
Definition: Log.cs:576
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
Task UpgradeToTlsAsClient(SslProtocols Protocols)
Upgrades a client connection to TLS.
bool RemoteCertificateValid
If the remote certificate is valid.
SslPolicyErrors RemoteSslPolicyErrors
SSL/TLS policy errors encountered by the remote endpoint.
string RemoteEndPoint
Remote End-point of connection. This corresponds to the IP Endpoint of the remote party in normal cas...
void DisposeWhenDone()
Disposes the client when done sending all data.
void Continue()
Continues reading from the socket, if paused in an event handler.
X509Certificate RemoteCertificate
Certificate used by the remote endpoint.
Task< bool > ConnectAsync(string Host, int Port)
Connects to a host using TCP.
Task UpgradeToTlsAsServer(X509Certificate ServerCertificate)
Upgrades a server connection to TLS.
bool IsEncrypted
If connection is encrypted or not.
void TransmitText(string Text)
Called when text has been transmitted.
virtual bool Remove(ISniffer Sniffer)
ICommunicationLayer.Remove
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
void ReceiveText(string Text)
Called when text has been received.
ISniffer[] Sniffers
Registered sniffers.
void Warning(string Warning)
Called to inform the viewer of a warning state.
void Information(string Comment)
Called to inform the viewer of something.
Sniffer that stores events in memory.
Implements a text-based TCP Client, by using the thread-safe full-duplex BinaryTcpClient.
virtual Task< bool > SendAsync(string Text)
Sends a text packet.
const string TlsNamespace
urn:ietf:params:xml:ns:xmpp-tls
const string StreamNamespace
http://etherx.jabber.org/streams
Abstract base class for server connections.
const string BidirectionalNamespace
urn:xmpp:bidi
const string DialbackFeaturesNamespace
urn:xmpp:features:dialback
CaseInsensitiveString remoteDomain
Remote domain
CaseInsensitiveString LocalDomain
Local domain name.
const string BidirectionalFeatureNamespaces
urn:xmpp:features:bidi
CaseInsensitiveString RemoteDomain
Connection to domain.
const string DialbackNamespace
urn:xmpp:dialback
CaseInsensitiveString localDomain
Local domain
Contains information about a stanza.
Definition: Stanza.cs:9
XmlElement StanzaElement
Stanza element.
Definition: Stanza.cs:113
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
bool IsEmpty
If the address is empty.
Definition: XmppAddress.cs:183
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
Manages an XMPP server-to-server connection.
DateTime CreationTimeUtc
When connection object was created (in UTC).
Task< bool > Connect(bool DisposeCurrent)
Connects to the server.
override Task< string > IqError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends an IQ error stanza. If stanza was sent.
override Task< bool > IQ(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Sends an IQ stanza. If stanza was sent.
EventHandlerAsync OnError
Event raised when an error was encountered.
XmppS2sState State
Current state of connection.
override Task< bool > PresenceError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends a presence error stanza. If stanza was sent.
EventHandlerAsync OnStateChanged
Event raised whenever the internal state of the connection changes.
Task< bool > SendPing()
Sends an XMPP ping request.
override Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
Sends a message stanza. If stanza was sent.
override Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Sends a presence stanza. If stanza was sent.
Task HardOffline()
Closes the connection the hard way. This might disrupt stream processing, but can simulate a lost con...
EventHandlerAsync OnDisposed
Event raised when object is disposed.
EventHandlerAsync OnConnectionError
Event raised when a connection to a broker could not be made.
async Task< bool > Connect(string Host, bool DisposeCurrent)
Connects to the server.
override Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
Sends a presence stanza. If stanza was sent.
override string Type
Type of endpoint
XmppS2SEndpoint(TextTcpClient Client, X509Certificate DomainCertificate, XmppServer Server, bool TrustRemoteCertificate, params ISniffer[] Sniffers)
Manages an XMPP server-to-server connection.
DateTime ConnectedTimeUtc
When connection was established (in UTC).
override Task< bool > IQ(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
Sends an IQ stanza. If stanza was sent.
override async Task DisposeAsync(string Reason)
Closes the connection and disposes of all resources.
override Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Sends a message stanza. If stanza was sent.
override Task< bool > IqResult(string Id, XmppAddress To, XmppAddress From, string ResultXml)
Sends an IQ result stanza. If stanza was sent.
bool ServerCertificateValid
If the server certificate is valid.
override Task< bool > PresenceError(string Id, XmppAddress To, XmppAddress From, string ErrorXml)
Sends a presence error stanza. If stanza was sent.
override Task< bool > IqError(string Id, XmppAddress To, XmppAddress From, string ErrorXml)
Sends an IQ error stanza. If stanza was sent.
DateTime ConnectTimeUtc
When connection attempt was started (in UTC).
override Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
Sends a presence stanza. If stanza was sent.
override Task< bool > MessageError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends a message error stanza. If stanza was sent.
override Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
Sends a message stanza. If stanza was sent.
bool TrustServer
If server should be trusted, regardless if the operating system could validate its certificate or not...
X509Certificate RemoteDomainCertificate
Certificate used by the remote server.
string GetRandomHexString(int NrBytes)
Generates a random hexadecimal string.
Definition: XmppServer.cs:696
static void GetErrorInformation(Exception ex, out string Type, out string Xml)
Converts an Exception to an XMPP error message.
Definition: XmppServer.cs:3426
const string SaslNamespace
urn:ietf:params:xml:ns:xmpp-sasl
Definition: XmppServer.cs:113
const string PingNamespace
urn:xmpp:ping (XEP-0199)
Definition: XmppServer.cs:183
const string StanzaNamespace
urn:ietf:params:xml:ns:xmpp-stanzas (RFC 6120)
Definition: XmppServer.cs:103
const string StreamsNamespace
urn:ietf:params:xml:ns:xmpp-streams
Definition: XmppServer.cs:108
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
bool EndsWith(CaseInsensitiveString value, StringComparison comparisonType)
Determines whether the end of this string instance matches the specified string when compared using t...
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void Clear()
Clears the collection.
Definition: ChunkedList.cs:306
void AddRange(IEnumerable< T > Collection)
Adds a range of elements (last) to the list.
bool HasFirstItem
If there is a first item in the collection
Definition: ChunkedList.cs:778
T RemoveFirst()
Removes the first item in the collection.
Definition: ChunkedList.cs:876
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
T[] ToArray()
Returns an array containing all elements of the collection.
Helper methods for encrypting and decrypting streams of data.
Definition: Crypto.cs:14
const SslProtocols SecureTls
TLS 1.2 & 1.3
Definition: Crypto.cs:18
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
static bool CanStartTls(string RemoteEndPoint)
Checks if TLS negotiation can start, for a given endpoint. If the endpoint has tries a TLS hack attem...
static void ReportTlsHackAttempt(string RemoteEndPoint, string Message, string Protocol)
Reports a TLS hacking attempt from an endpoint. Can be used to deny TLS negotiation to proceed,...
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
Interface for XMPP S2S endpoints
Definition: IS2sEndpoint.cs:11
Interface for senders of stanzas.
Definition: ISender.cs:10
Task< string > IqError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends an IQ Error stanza.
Definition: ImplTypes.g.cs:58
delegate Task EventHandlerAsync(object Sender, EventArgs e)
Asynchronous version of EventArgs.
EventLevel
Event level.
Definition: EventLevel.cs:7
XmppS2sState
State of XMPP connection.
Definition: XmppS2sState.cs:7
S2sValidationResult
S2S validation result.
ClientCertificates
Client Certificate Options