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