Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
XmppClientConnection.cs
1using System;
3using System.ComponentModel;
4using System.Net;
7using System.Security.Authentication;
8using System.Security.Cryptography.X509Certificates;
9using System.Text;
10using System.Threading.Tasks;
11using System.Xml;
13using Waher.Events;
18using Waher.Security;
20
22{
27 {
28 private const int MaxFragmentSize = 40000000;
29
30 private TextTcpClient client;
31 private readonly StringBuilder fragment = new StringBuilder();
32 private int fragmentLength = 0;
33 private int inputState = 0;
34 private int inputDepth = 0;
35 private int contentStart = 0;
36 private int contentEnd = 0;
37 private string streamId;
38 private string streamHeader;
39 private string streamFooter;
40 private string language;
41 private double version;
42 private bool openBracketReceived = false;
43 private bool upgradeToTls = false;
44 private string qlMechanism = null;
45 private string qlChallenge = null;
46 private string qlResource = null;
47
48
56 : base(Server, XmppConnectionState.Offline, Sniffers)
57 {
58 this.client = Client;
59
60 this.client.OnDisconnected += this.Client_OnDisconnected;
61 this.client.OnError += this.Client_OnError;
62 this.client.OnReceived += this.Client_OnReceived;
63 this.client.OnPaused += this.Client_OnPaused;
64 this.client.OnSent += this.Client_OnSent;
65 this.client.OnInformation += this.Client_OnInformation;
66 this.client.OnWarning += this.Client_OnWarning;
67 }
68
72 public override string Binding => "Socket";
73
77 public override string RemoteEndPoint => this.client?.RemoteEndPoint;
78
82 public override string Protocol => "XMPP";
83
87 internal X509Certificate ClientCertificate => this.client.RemoteCertificate;
88
92 internal bool ClientCertificateValid => this.client.RemoteCertificateValid;
93
97 public async override Task DisposeAsync()
98 {
99 try
100 {
101 if (!this.disposed)
102 {
103 if (this.State != XmppConnectionState.Error)
104 await this.SetState(XmppConnectionState.Offline);
105
106 if (this.isBound && !(this.server is null))
107 {
108 this.isBound = false;
109 await this.ProcessFragment("<presence type=\"unavailable\"/>", 0, 0);
110 this.server?.ConnectionClosed(this);
111 }
112
113 ISniffer[] Sniffers = this.Sniffers;
114 if (!(Sniffers is null) && !(this.server is null))
115 await this.server.CacheSniffers(Sniffers);
116
117 if (!(this.client is null))
118 {
119 await this.client.DisposeAsync();
120 this.client = null;
121 }
122
123 await base.DisposeAsync();
124 }
125 }
126 catch (Exception ex)
127 {
128 Log.Exception(ex);
129 }
130 }
131
132 private Task<bool> Client_OnSent(object Sender, string Text)
133 {
134 this.server?.DataTransmitted(this.client?.LastTransmittedBytes ?? 0);
135 this.TransmitText(Text);
136 return Task.FromResult(true);
137 }
138
139 private string Client_OnWarning(string Text)
140 {
141 this.Warning(Text);
142 return Text;
143 }
144
145 private string Client_OnInformation(string Text)
146 {
147 this.Information(Text);
148 return Text;
149 }
150
151 private async Task<bool> Client_OnReceived(object Sender, string Text)
152 {
153 try
154 {
155 this.server?.DataReceived(this.client?.LastReceivedBytes ?? 0);
156
157 if (this.openBracketReceived)
158 {
159 this.openBracketReceived = false;
160 this.ReceiveText("<" + Text);
161 }
162 else if (Text == "<")
163 this.openBracketReceived = true;
164 else
165 this.ReceiveText(Text);
166
167 return await this.ParseIncoming(Text);
168 }
169 catch (Exception ex)
170 {
171 if (!this.disposed)
172 await this.Client_OnError(this, ex);
173
174 return false;
175 }
176 }
177
178 private async Task Client_OnError(object Sender, Exception Exception)
179 {
180 await this.SetState(XmppConnectionState.Error);
181 this.Error(Exception.Message);
182 await this.DisposeAsync();
183 }
184
185 private async Task Client_OnDisconnected(object Sender, EventArgs e)
186 {
187 await this.SetState(XmppConnectionState.Offline);
188 await this.DisposeAsync();
189 }
190
191 private async Task<bool> ParseIncoming(string s)
192 {
193 bool Result = true;
194
195 foreach (char ch in s)
196 {
197 switch (this.inputState)
198 {
199 case 0: // Waiting for first <
200 if (ch == '<')
201 {
202 this.fragment.Append(ch);
203 if (++this.fragmentLength > MaxFragmentSize)
204 {
206 return false;
207 }
208 else
209 this.inputState++;
210 }
211 else if (ch > ' ')
212 {
213 await this.StreamErrorNotWellFormed();
214 return false;
215 }
216 break;
217
218 case 1: // Waiting for ? or >
219 this.fragment.Append(ch);
220 if (++this.fragmentLength > MaxFragmentSize)
221 {
223 return false;
224 }
225 else if (ch == '?')
226 this.inputState++;
227 else if (ch == '>')
228 {
229 this.inputState = 5;
230 this.inputDepth = 1;
231
232 if (!await this.ProcessStream(this.fragment.ToString()))
233 return false;
234
235 this.fragment.Clear();
236 this.fragmentLength = this.contentStart = this.contentEnd = 0;
237 }
238 break;
239
240 case 2: // In processing instruction. Waiting for ?>
241 this.fragment.Append(ch);
242 if (++this.fragmentLength > MaxFragmentSize)
243 {
245 return false;
246 }
247 else if (ch == '>')
248 this.inputState++;
249 break;
250
251 case 3: // Waiting for <stream
252 this.fragment.Append(ch);
253 if (++this.fragmentLength > MaxFragmentSize)
254 {
256 return false;
257 }
258 else if (ch == '<')
259 this.inputState++;
260 else if (ch > ' ')
261 {
262 await this.StreamErrorNotWellFormed();
263 return false;
264 }
265 break;
266
267 case 4: // Waiting for >
268 this.fragment.Append(ch);
269 if (++this.fragmentLength > MaxFragmentSize)
270 {
272 return false;
273 }
274 else if (ch == '>')
275 {
276 this.inputState++;
277 this.inputDepth = 1;
278 if (!await this.ProcessStream(this.fragment.ToString()))
279 return false;
280
281 this.fragment.Clear();
282 this.fragmentLength = this.contentStart = this.contentEnd = 0;
283 }
284 break;
285
286 case 5: // Waiting for start element.
287 if (ch == '<')
288 {
289 this.fragment.Append(ch);
290 if (++this.fragmentLength > MaxFragmentSize)
291 {
293 return false;
294 }
295 else
296 this.inputState++;
297 }
298 else if (this.inputDepth > 1)
299 {
300 this.fragment.Append(ch);
301 if (++this.fragmentLength > MaxFragmentSize)
302 {
304 return false;
305 }
306 }
307 else if (ch > ' ')
308 {
309 await this.StreamErrorNotWellFormed();
310 return false;
311 }
312 break;
313
314 case 6: // Second character in tag
315 this.fragment.Append(ch);
316 if (++this.fragmentLength > MaxFragmentSize)
317 {
319 return false;
320 }
321 else if (ch == '/')
322 {
323 if (this.inputDepth == 2)
324 this.contentEnd = this.fragmentLength - 2;
325
326 this.inputState++;
327 }
328 else if (ch == '!')
329 this.inputState = 13;
330 else
331 this.inputState += 2;
332 break;
333
334 case 7: // Waiting for end of closing tag
335 this.fragment.Append(ch);
336 if (++this.fragmentLength > MaxFragmentSize)
337 {
339 return false;
340 }
341 else if (ch == '>')
342 {
343 this.inputDepth--;
344 if (this.inputDepth < 1)
345 {
346 await this.DisposeAsync();
347 return false;
348 }
349 else
350 {
351 if (this.inputDepth == 1)
352 {
353 if (!await this.ProcessFragment(this.fragment.ToString(), this.contentStart, this.contentEnd - this.contentStart))
354 Result = false;
355
356 this.fragment.Clear();
357 this.fragmentLength = this.contentStart = this.contentEnd = 0;
358 }
359
360 if (this.inputState > 0)
361 this.inputState = 5;
362 }
363 }
364 break;
365
366 case 8: // Wait for end of start tag
367 this.fragment.Append(ch);
368 if (++this.fragmentLength > MaxFragmentSize)
369 {
371 return false;
372 }
373 else if (ch == '>')
374 {
375 if (this.inputDepth == 1)
376 this.contentStart = this.fragmentLength;
377
378 this.inputDepth++;
379 this.inputState = 5;
380 }
381 else if (ch == '/')
382 this.inputState++;
383 else if (ch <= ' ')
384 this.inputState += 2;
385 break;
386
387 case 9: // Check for end of childless tag.
388 this.fragment.Append(ch);
389 if (++this.fragmentLength > MaxFragmentSize)
390 {
392 return false;
393 }
394 else if (ch == '>')
395 {
396 if (this.inputDepth == 1)
397 {
398 if (!await this.ProcessFragment(this.fragment.ToString(), 0, 0))
399 Result = false;
400
401 this.fragment.Clear();
402 this.fragmentLength = this.contentStart = this.contentEnd = 0;
403 }
404
405 if (this.inputState != 0)
406 this.inputState = 5;
407 }
408 else
409 this.inputState--;
410 break;
411
412 case 10: // Check for attributes.
413 this.fragment.Append(ch);
414 if (++this.fragmentLength > MaxFragmentSize)
415 {
417 return false;
418 }
419 else if (ch == '>')
420 {
421 if (this.inputDepth == 1)
422 this.contentStart = this.fragmentLength;
423
424 this.inputDepth++;
425 this.inputState = 5;
426 }
427 else if (ch == '/')
428 this.inputState--;
429 else if (ch == '"')
430 this.inputState++;
431 else if (ch == '\'')
432 this.inputState += 2;
433 break;
434
435 case 11: // Double quote attribute.
436 this.fragment.Append(ch);
437 if (++this.fragmentLength > MaxFragmentSize)
438 {
440 return false;
441 }
442 else if (ch == '"')
443 this.inputState--;
444 break;
445
446 case 12: // Single quote attribute.
447 this.fragment.Append(ch);
448 if (++this.fragmentLength > MaxFragmentSize)
449 {
451 return false;
452 }
453 else if (ch == '\'')
454 this.inputState -= 2;
455 break;
456
457 case 13: // Third character in start of comment
458 this.fragment.Append(ch);
459 if (++this.fragmentLength > MaxFragmentSize)
460 {
462 return false;
463 }
464 else if (ch == '-')
465 this.inputState++;
466 else if (ch == '[')
467 this.inputState = 18;
468 else
469 {
470 await this.StreamErrorInvalidXml();
471 return false;
472 }
473 break;
474
475 case 14: // Fourth character in start of comment
476 this.fragment.Append(ch);
477 if (++this.fragmentLength > MaxFragmentSize)
478 {
480 return false;
481 }
482 else if (ch == '-')
483 this.inputState++;
484 else
485 {
486 await this.StreamErrorInvalidXml();
487 return false;
488 }
489 break;
490
491 case 15: // In comment
492 this.fragment.Append(ch);
493 if (++this.fragmentLength > MaxFragmentSize)
494 {
496 return false;
497 }
498 else if (ch == '-')
499 this.inputState++;
500 break;
501
502 case 16: // Second character in end of comment
503 this.fragment.Append(ch);
504 if (++this.fragmentLength > MaxFragmentSize)
505 {
507 return false;
508 }
509 else if (ch == '-')
510 this.inputState++;
511 else
512 this.inputState--;
513 break;
514
515 case 17: // Third character in end of comment
516 this.fragment.Append(ch);
517 if (++this.fragmentLength > MaxFragmentSize)
518 {
520 return false;
521 }
522 else if (ch == '>')
523 this.inputState = 5;
524 else
525 this.inputState -= 2;
526 break;
527
528 case 18: // Fourth character in start of CDATA
529 this.fragment.Append(ch);
530 if (++this.fragmentLength > MaxFragmentSize)
531 {
533 return false;
534 }
535 else if (ch == 'C')
536 this.inputState++;
537 else
538 {
539 await this.StreamErrorInvalidXml();
540 return false;
541 }
542 break;
543
544 case 19: // Fifth character in start of CDATA
545 this.fragment.Append(ch);
546 if (++this.fragmentLength > MaxFragmentSize)
547 {
549 return false;
550 }
551 else if (ch == 'D')
552 this.inputState++;
553 else
554 {
555 await this.StreamErrorInvalidXml();
556 return false;
557 }
558 break;
559
560 case 20: // Sixth character in start of CDATA
561 this.fragment.Append(ch);
562 if (++this.fragmentLength > MaxFragmentSize)
563 {
565 return false;
566 }
567 else if (ch == 'A')
568 this.inputState++;
569 else
570 {
571 await this.StreamErrorInvalidXml();
572 return false;
573 }
574 break;
575
576 case 21: // Seventh character in start of CDATA
577 this.fragment.Append(ch);
578 if (++this.fragmentLength > MaxFragmentSize)
579 {
581 return false;
582 }
583 else if (ch == 'T')
584 this.inputState++;
585 else
586 {
587 await this.StreamErrorInvalidXml();
588 return false;
589 }
590 break;
591
592 case 22: // Eighth character in start of CDATA
593 this.fragment.Append(ch);
594 if (++this.fragmentLength > MaxFragmentSize)
595 {
597 return false;
598 }
599 else if (ch == 'A')
600 this.inputState++;
601 else
602 {
603 await this.StreamErrorInvalidXml();
604 return false;
605 }
606 break;
607
608 case 23: // Ninth character in start of CDATA
609 this.fragment.Append(ch);
610 if (++this.fragmentLength > MaxFragmentSize)
611 {
613 return false;
614 }
615 else if (ch == '[')
616 this.inputState++;
617 else
618 {
619 await this.StreamErrorInvalidXml();
620 return false;
621 }
622 break;
623
624 case 24: // In CDATA
625 this.fragment.Append(ch);
626 if (++this.fragmentLength > MaxFragmentSize)
627 {
629 return false;
630 }
631 else if (ch == ']')
632 this.inputState++;
633 break;
634
635 case 25: // Second character in end of CDATA
636 this.fragment.Append(ch);
637 if (++this.fragmentLength > MaxFragmentSize)
638 {
640 return false;
641 }
642 else if (ch == ']')
643 this.inputState++;
644 else
645 this.inputState--;
646 break;
647
648 case 26: // Third character in end of CDATA
649 this.fragment.Append(ch);
650 if (++this.fragmentLength > MaxFragmentSize)
651 {
653 return false;
654 }
655 else if (ch == '>')
656 this.inputState = 5;
657 else if (ch != ']')
658 this.inputState -= 2;
659 break;
660
661 default:
662 break;
663 }
664
665 }
666
667 return Result;
668 }
669
676 public override Task<bool> StreamError(string ErrorXml, string Reason)
677 {
678 return this.ToError("<stream:error>" + ErrorXml + "</stream:error>", Reason);
679 }
680
681 private async Task<bool> ToError(string ErrorXml, string Reason)
682 {
683 if (string.IsNullOrEmpty(ErrorXml))
684 {
685 this.inputState = -1;
686
687 await this.Client_OnError(this, new Exception(Reason));
688
689 return false;
690 }
691 else
692 {
693 return await this.BeginWrite(ErrorXml + this.streamFooter, async (Sender, e) =>
694 {
695 this.inputState = -1;
696
697 await this.SetState(XmppConnectionState.Error);
698 await this.DisposeAsync();
699 }, null);
700 }
701 }
702
703 private async Task<bool> ProcessStream(string Xml)
704 {
705 StringBuilder ToSend = new StringBuilder();
706
707 try
708 {
709 int i = Xml.IndexOf("?>");
710 if (i >= 0)
711 Xml = Xml[(i + 2)..].TrimStart();
712
713 this.streamHeader = Xml;
714
715 i = Xml.IndexOf(":stream");
716 if (i < 0)
717 this.streamFooter = "</stream>";
718 else
719 this.streamFooter = "</" + Xml[1..i] + ":stream>";
720
721 XmlDocument Doc = XML.ParseXml(Xml + this.streamFooter, true);
722
723 XmlElement Stream = Doc.DocumentElement;
724
725 this.bareJid = XML.Attribute(Stream, "from", this.bareJid);
726 string TentativeDomain = XML.Attribute(Stream, "to");
727 this.version = XML.Attribute(Stream, "version", 0.0);
728 this.language = XML.Attribute(Stream, "xml:lang", "en");
729
730 this.bareAddress = new XmppAddress(this.bareJid);
731
732 if (string.IsNullOrEmpty(this.streamId))
733 this.streamId = this.server.GetRandomHexString(16);
734
735 bool IsServerDomain = this.server.IsServerDomain(TentativeDomain, true);
736
737 this.domain = IsServerDomain ? (CaseInsensitiveString)TentativeDomain : this.server.Domain;
738
739 ToSend.Append("<?xml version='1.0' encoding='utf-8'?>");
740 ToSend.Append("<stream:stream from='");
741 ToSend.Append(XML.Encode(this.domain));
742 ToSend.Append("' version='1.0' xml:lang='");
743 ToSend.Append(XML.Encode(this.language));
744 ToSend.Append("' id='");
745 ToSend.Append(this.streamId);
746 ToSend.Append("' xmlns='jabber:client' xmlns:stream='");
747 ToSend.Append(StreamNamespace);
748 ToSend.Append("'>");
749
750 if (Doc.DocumentElement.NamespaceURI != StreamNamespace)
751 {
752 await this.BeginWrite(ToSend);
753 await this.StreamErrorInvalidNamespace();
754 return false;
755 }
756
757 if (Doc.DocumentElement.Prefix != "stream")
758 {
759 await this.BeginWrite(ToSend);
760 await this.StreamError("<bad-namespace-prefix xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", "Bad namespace prefix.");
761 return false;
762 }
763
764 if (Doc.DocumentElement.LocalName != "stream")
765 {
766 await this.BeginWrite(ToSend);
767 await this.StreamError("<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", "Bad format.");
768 return false;
769 }
770
771 if (!this.server.IsServerDomain(this.domain, true) && (!string.IsNullOrEmpty(this.server.Domain) || !IPAddress.TryParse(this.domain, out IPAddress _)))
772 {
773 await this.BeginWrite(ToSend);
774 await this.StreamError("<host-unknown xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", "Domain unknown.");
775 return false;
776 }
777
778 if (this.version != 1.0)
779 {
780 await this.BeginWrite(ToSend);
781 await this.ToError("<unsupported-version xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", "Unsupported version.");
782 return false;
783 }
784
785 bool IsEncrypted = this.client.IsEncrypted;
786 if (!IsEncrypted)
787 await this.SetState(XmppConnectionState.StreamNegotiation);
788
789 ToSend.Append("<stream:features>");
790
791 this.qlMechanism = null;
792 this.qlChallenge = null;
793 this.qlResource = null;
794
795 if (!IsEncrypted && !this.isAuthenticated && !(this.server.ServerCertificate is null))
796 {
797 ToSend.Append("<ql xmlns='");
798 ToSend.Append(QuickLoginNamespace);
799 ToSend.Append("'/>");
800 }
801
802 if (!IsEncrypted && !(this.server.ServerCertificate is null))
803 {
804 ToSend.Append("<starttls xmlns='");
805 ToSend.Append(TlsNamespace);
806 ToSend.Append('\'');
807
808 if (this.server.EncryptionRequired)
809 ToSend.Append("><required/></starttls>");
810 else
811 ToSend.Append("/>");
812 }
813 else if (!this.isAuthenticated)
814 {
815 await this.SetState(XmppConnectionState.Authenticating);
816
817 ToSend.Append("<mechanisms xmlns='" + XmppServer.SaslNamespace + "'>");
818
819 SslStream SslStream = this.client.Stream as SslStream;
820 foreach (IAuthenticationMechanism Mechanism in XmppServer.mechanisms)
821 {
822 if (Mechanism.Allowed(SslStream))
823 {
824 ToSend.Append("<mechanism>");
825 ToSend.Append(Mechanism.Name);
826 ToSend.Append("</mechanism>");
827 }
828 }
829
830 ToSend.Append("</mechanisms>");
831
832 if (await this.server.CanRegister(this))
833 ToSend.Append("<register xmlns='http://jabber.org/features/iq-register'/>");
834 }
835 else if (!this.isBound)
836 {
837 await this.SetState(XmppConnectionState.Binding);
838
839 ToSend.Append("<bind xmlns='");
840 ToSend.Append(BindNamespace);
841 ToSend.Append("'/>");
842 ToSend.Append("<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
843 }
844
845 ToSend.Append("</stream:features>");
846 await this.BeginWrite(ToSend);
847
848 return true;
849 }
850 catch (Exception ex)
851 {
852 StringBuilder Msg = new StringBuilder();
853
854 Msg.Append("Incoming XMPP stream rejected: ");
855 Msg.AppendLine(ex.Message);
856
857 if (!string.IsNullOrEmpty(Xml))
858 {
859 Xml = XML.PrettyXml(Xml);
860 if (Xml.Length > 1000)
861 Xml = Xml[..1000] + "...";
862
863 Msg.AppendLine();
864 Msg.AppendLine("```xml");
865 Msg.AppendLine(Xml);
866 Msg.AppendLine("```");
867 }
868
869 string s = Msg.ToString();
870
871 Log.Warning(s, new KeyValuePair<string, object>("RemoteEP", this.RemoteEndPoint));
872
873 this.Warning(s);
874
875 await this.BeginWrite(ToSend);
876 await this.StreamError("<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", "Bad format.");
877 return false;
878 }
879 }
880
887 public override Task<bool> BeginWrite(string Xml, EventHandlerAsync<DeliveryEventArgs> Callback, object State)
888 {
889 return this.client?.SendAsync(Xml, Callback, State) ?? Task.FromResult(false);
890 }
891
892 private Task<bool> BeginWrite(StringBuilder ToSend)
893 {
894 string Xml = ToSend.ToString();
895 if (string.IsNullOrEmpty(Xml))
896 return Task.FromResult(true);
897
898 ToSend.Clear();
899
900 return this.BeginWrite(Xml, null, null);
901 }
902
903 private async Task<bool> ProcessFragment(string Xml, int ContentStart, int ContentLen)
904 {
905 Stanza Stanza;
906 XmlDocument Doc;
907
908 try
909 {
910 if (this.disposed)
911 return false;
912
913 if (!string.IsNullOrEmpty(this.fullJid))
914 this.server.TouchClientConnection(this.fullJid);
915
916 Doc = XML.ParseXml(this.streamHeader + Xml + this.streamFooter, true);
917
918 Stanza = new Stanza(Doc.DocumentElement, Xml, ContentStart, ContentLen);
919
920 return await this.ProcessStanza(Stanza);
921 }
922 catch (Exception ex)
923 {
924 string Content;
925
926 if (Xml.Length < 100)
927 Content = Xml;
928 else
929 Content = Xml[..100] + "...";
930
931 Log.Exception(ex, this.bareJid,
932 new KeyValuePair<string, object>("ContentLength", Xml.Length),
933 new KeyValuePair<string, object>("Content", Content));
934
935 this.Exception(ex);
936 await this.StreamError("<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", ex.Message);
937 return false;
938 }
939 }
940
941 private async Task Client_OnPaused(object Sender, EventArgs e)
942 {
943 if (this.upgradeToTls)
944 {
945 this.upgradeToTls = false;
946
947 string RemoteEndPoint = this.RemoteEndPoint.RemovePortNumber();
948
950 {
951 try
952 {
953 await this.SetState(XmppConnectionState.StartingEncryption);
954
955 if (this.disposed || this.client is null || this.server is null)
956 return;
957
958 await this.client.UpgradeToTlsAsServer(this.server.ServerCertificate, Crypto.SecureTls, ClientCertificates.Optional, "xmpp-client");
959
960 bool QuickLogin = !string.IsNullOrEmpty(this.qlMechanism);
961
962 this.ResetState(false, !QuickLogin);
963 this.client.Continue();
964
965 if (QuickLogin)
966 {
967 DateTime? Next = await this.server.GetEarliestLoginOpportunity(this);
968
969 if (Next.HasValue)
970 {
971 StringBuilder sb = new StringBuilder();
972 DateTime TP = Next.Value;
973 DateTime Today = DateTime.Today;
974
975 if (Next.Value == DateTime.MaxValue)
976 {
977 sb.Append("This endpoint (");
978 sb.Append(this.RemoteEndPoint);
979 sb.Append(") has been blocked from the system.");
980 }
981 else
982 {
983 sb.Append("Too many failed login attempts in a row registered. Try again after ");
984 sb.Append(TP.ToLongTimeString());
985
986 if (TP.Date != Today)
987 {
988 if (TP.Date == Today.AddDays(1))
989 sb.Append(" tomorrow");
990 else
991 {
992 sb.Append(", ");
993 sb.Append(TP.ToShortDateString());
994 }
995 }
996
997 sb.Append(". Remote Endpoint: ");
998 sb.Append(this.RemoteEndPoint);
999 }
1000
1001 await this.SaslErrorTemporaryAuthFailure(sb.ToString(), "en");
1002
1003 await this.DisposeAsync();
1004 return;
1005 }
1006
1007 bool Found = false;
1008
1009 foreach (IAuthenticationMechanism M in XmppServer.mechanisms)
1010 {
1011 if (M.Name == this.qlMechanism)
1012 {
1013 if (!M.Allowed(this.GetSslStream()))
1014 {
1015 await this.SaslErrorMechanismTooWeak();
1016 await this.DisposeAsync();
1017 return;
1018 }
1019
1020 this.SetMechanism(M);
1021 Found = true;
1022
1023 try
1024 {
1025 bool? AuthResult = await M.AuthenticationRequest(this.qlChallenge, this, this.server.PersistenceLayer);
1026 if (AuthResult.HasValue)
1027 {
1028 if (AuthResult.Value)
1029 {
1030 await this.SaslSuccess(string.Empty);
1031 this.ResetState(true, false);
1032 return;
1033 }
1034 }
1035 }
1036 catch (Exception ex)
1037 {
1038 this.Exception(ex);
1039 await this.StreamErrorInvalidXml();
1040 await this.DisposeAsync();
1041 return;
1042 }
1043 break;
1044 }
1045 }
1046
1047 if (!Found)
1048 {
1049 await this.SaslErrorInvalidMechanism();
1050 await this.DisposeAsync();
1051 return;
1052 }
1053 }
1054 }
1055 catch (AuthenticationException ex)
1056 {
1057 await this.LoginFailure(ex, RemoteEndPoint);
1058 }
1059 catch (Win32Exception ex)
1060 {
1061 await this.LoginFailure(ex, RemoteEndPoint);
1062 }
1063 catch (Exception ex)
1064 {
1065 this.Exception(ex);
1066 await this.DisposeAsync();
1067 }
1068 }
1069 else
1070 await this.DisposeAsync();
1071 }
1072 }
1073
1078 public override async Task<bool> SaslSuccess(string ProofBase64)
1079 {
1080 if (!string.IsNullOrEmpty(this.qlMechanism))
1081 {
1082 string FullJid = null;
1083
1084 if (!string.IsNullOrEmpty(this.qlResource))
1085 {
1086 FullJid = this.bareJid + "/" + this.qlResource;
1087 if (!await this.server.RegisterFullJid(FullJid, this))
1088 FullJid = null;
1089 }
1090
1091 if (FullJid is null)
1092 this.fullJid = await this.server.RegisterBareJid(this.bareJid, this);
1093 else
1094 this.fullJid = FullJid;
1095
1096 this.address = new XmppAddress(this.fullJid);
1097 this.isBound = true;
1098
1099 await this.SetState(XmppConnectionState.RequestingSession);
1100 this.hasSession = true;
1101
1102 await this.SetState(XmppConnectionState.AwaitingPresence);
1103 }
1104
1105 return await base.SaslSuccess(ProofBase64);
1106 }
1107
1108 private async Task LoginFailure(Exception ex, string RemoteIpEndpoint)
1109 {
1110 Exception ex2 = Log.UnnestException(ex);
1111 LoginAuditor.ReportTlsHackAttempt(RemoteIpEndpoint, "TLS handshake failed: " + ex2.Message, "XMPP");
1112
1113 await this.DisposeAsync();
1114 }
1115
1121 {
1122 base.SetUserIdentity(UserName);
1123
1125
1126 foreach (ISniffer Sniffer in this.Sniffers)
1127 {
1128 InMemorySniffer = Sniffer as InMemorySniffer;
1129 if (!(InMemorySniffer is null))
1130 break;
1131 }
1132
1133 if (!(InMemorySniffer is null))
1134 {
1135 this.Remove(InMemorySniffer);
1136 this.Add(this.server.GetSniffer(UserName, false));
1137 InMemorySniffer.Replay(this);
1138 }
1139 }
1140
1145 public override void ResetState(bool Authenticated)
1146 {
1147 this.ResetState(Authenticated, string.IsNullOrEmpty(this.qlMechanism));
1148 }
1149
1155 public void ResetState(bool Authenticated, bool ExpectStream)
1156 {
1157 this.isAuthenticated = Authenticated;
1158
1159 if (ExpectStream)
1160 {
1161 this.inputState = 0;
1162 this.inputDepth = 0;
1163 }
1164 else
1165 {
1166 this.inputState = 5;
1167 this.inputDepth = 1;
1168 }
1169 }
1170
1175 public override bool CheckLive()
1176 {
1177 try
1178 {
1179 if (this.disposed || this.State == XmppConnectionState.Error || this.State == XmppConnectionState.Offline)
1180 return false;
1181
1182 if (!this.client.Connected)
1183 return false;
1184
1185 // https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx
1186
1187 bool BlockingBak = this.client.Client.Client.Blocking;
1188 try
1189 {
1190 byte[] Temp = new byte[1];
1191
1192 this.client.Client.Client.Blocking = false;
1193 this.client.Client.Client.Send(Temp, 0, 0);
1194
1195 return true;
1196 }
1197 catch (SocketException e)
1198 {
1199 if (e.NativeErrorCode.Equals(10035)) // WSAEWOULDBLOCK
1200 return true;
1201 else
1202 return false;
1203 }
1204 finally
1205 {
1206 this.client.Client.Client.Blocking = BlockingBak;
1207 }
1208 }
1209 catch (Exception)
1210 {
1211 return false;
1212 }
1213 }
1214
1219 protected override SslStream GetSslStream()
1220 {
1221 return this.client.Stream as SslStream;
1222 }
1223
1230 protected override async Task<bool> ProcessBindingSpecificStanza(Stanza Stanza, XmlElement StanzaElement)
1231 {
1232 switch (StanzaElement.LocalName)
1233 {
1234 case "starttls":
1235 if (StanzaElement.NamespaceURI != TlsNamespace)
1236 {
1237 await this.StreamErrorInvalidNamespace();
1238 return false;
1239 }
1240
1241 if (this.server.ServerCertificate is null)
1242 {
1243 await this.StreamError("<failure xmlns='" + TlsNamespace + "'/>", "Encryption not enabled.");
1244 return false;
1245 }
1246
1247 if (await this.BeginWrite("<proceed xmlns='" + TlsNamespace + "'/>", null, null))
1248 this.upgradeToTls = true;
1249
1250 return false;
1251
1252 case "ql":
1253 if (StanzaElement.NamespaceURI != QuickLoginNamespace)
1254 {
1255 await this.StreamErrorInvalidNamespace();
1256 return false;
1257 }
1258
1259 if (this.server.ServerCertificate is null)
1260 {
1261 await this.StreamError("<failure xmlns='" + TlsNamespace + "'/>", "Encryption not enabled.");
1262 return false;
1263 }
1264
1265 this.qlMechanism = XML.Attribute(StanzaElement, "m");
1266 this.qlChallenge = XML.Attribute(StanzaElement, "c");
1267 this.qlResource = XML.Attribute(StanzaElement, "r");
1268
1269 this.upgradeToTls = true;
1270 return false;
1271
1272 default:
1273 if (!await this.StreamError("<unsupported-stanza-type xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", "Unsupported stanza: " + StanzaElement.LocalName))
1274 return false;
1275 break;
1276 }
1277
1278 return true;
1279 }
1280
1281 }
1282}
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 string PrettyXml(string Xml)
Reformats XML to make it easier to read.
Definition: XML.cs:1734
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
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 Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
bool Connected
If the connection is open.
bool RemoteCertificateValid
If the remote certificate is valid.
TcpClient Client
Underlying TcpClient object.
string RemoteEndPoint
Remote End-point of connection. This corresponds to the IP Endpoint of the remote party in normal cas...
void Continue()
Continues reading from the socket, if paused in an event handler.
virtual Task DisposeAsync()
Disposes of the object asynchronously. The underlying TcpClient is either disposed directly,...
X509Certificate RemoteCertificate
Certificate used by the remote endpoint.
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 Error(string Error)
Called to inform the viewer of an error state.
void Warning(string Warning)
Called to inform the viewer of a warning state.
virtual void Add(ISniffer Sniffer)
ICommunicationLayer.Add
void Information(string Comment)
Called to inform the viewer of something.
Sniffer that stores events in memory.
void Replay(CommunicationLayer ComLayer)
Replays sniffer events.
Implements a text-based TCP Client, by using the thread-safe full-duplex BinaryTcpClient.
virtual Task< bool > SendAsync(string Text)
Sends a text packet.
Abstract base class for XMPP client connections
const string TlsNamespace
urn:ietf:params:xml:ns:xmpp-tls
const string QuickLoginNamespace
http://waher.se/Schema/QL.xsd
virtual void SetMechanism(IAuthenticationMechanism Mechanism)
Sets the authentication mechanism for the connection.
bool isAuthenticated
If user is authenticated
XmppConnectionState State
Current state of connection.
Task< bool > SaslErrorMechanismTooWeak()
Sends SASL Error that mechanism is too waek.
async Task< bool > ProcessStanza(Stanza Stanza)
Processes an XMPP Stanza.
const string StreamNamespace
http://etherx.jabber.org/streams
Task< bool > StreamErrorInvalidXml()
Sends Stream Error that XML is invalid.
Task< bool > SaslErrorTemporaryAuthFailure(string Message, string Language)
Sends SASL Error that a temporary authentication error has occurred.
Task< bool > SaslErrorInvalidMechanism()
Sends SASL Error that machanism is invalid.
Task< bool > StreamErrorNotWellFormed()
Sends Stream Error that element is not well-formed.
XmppServer Server
XMPP Server serving the client.
Task< bool > StreamErrorResourceConstraint()
Sends Stream Error that there's a resource constraint.
Task< bool > StreamErrorInvalidNamespace()
Sends Stream Error that namespace is invalid.
const string BindNamespace
urn:ietf:params:xml:ns:xmpp-bind
Contains information about a stanza.
Definition: Stanza.cs:9
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
override async Task< bool > ProcessBindingSpecificStanza(Stanza Stanza, XmlElement StanzaElement)
Processes a binding-specific stanza.
override void SetUserIdentity(CaseInsensitiveString UserName)
Sets the authenticate user's identity.
XmppClientConnection(TextTcpClient Client, XmppServer Server, params ISniffer[] Sniffers)
Class managing a connection.
override Task< bool > StreamError(string ErrorXml, string Reason)
Sends a Stream Error.
override SslStream GetSslStream()
Gets underlying SSL-stream
override async Task< bool > SaslSuccess(string ProofBase64)
Returns a sucess response to the client.
void ResetState(bool Authenticated, bool ExpectStream)
Resets the state machine.
override bool CheckLive()
Checks if the connection is live.
async override Task DisposeAsync()
IDisposable.Dispose
override Task< bool > BeginWrite(string Xml, EventHandlerAsync< DeliveryEventArgs > Callback, object State)
Starts sending an XML fragment to the client.
override string Protocol
String representing protocol being used.
override void ResetState(bool Authenticated)
Resets the state machine.
string GetRandomHexString(int NrBytes)
Generates a random hexadecimal string.
Definition: XmppServer.cs:696
Task< DateTime?> GetEarliestLoginOpportunity(IClientConnection Connection)
Evaluates when a client is allowed to login.
Definition: XmppServer.cs:1518
IXmppServerPersistenceLayer PersistenceLayer
Reference to persistence layer
Definition: XmppServer.cs:982
bool IsServerDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the server domain, or optionally, an alternative domain.
Definition: XmppServer.cs:898
X509Certificate ServerCertificate
Server domain certificate.
Definition: XmppServer.cs:938
bool EncryptionRequired
If C2S encryption is requried.
Definition: XmppServer.cs:955
CaseInsensitiveString Domain
Domain name.
Definition: XmppServer.cs:922
Represents a case-insensitive string.
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 authentication mechanisms.
Task< bool?> AuthenticationRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer)
Authentication request has been made.
bool Allowed(SslStream SslStream)
Checks if a mechanism is allowed during the current conditions.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
Definition: ImplTypes.g.cs:58
XmppConnectionState
State of XMPP connection.
ClientCertificates
Client Certificate Options