5using System.ComponentModel;
6using System.Security.Authentication;
8using System.Security.Cryptography.X509Certificates;
10using System.Threading;
11using System.Threading.Tasks;
52 private const int KeepAliveTimeSeconds = 30;
53 private const int MaxFragmentSize = 40000000;
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();
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;
74 private Timer secondTimer =
null;
75 private int fragmentLength = 0;
78 private LinkedList<Tuple<string, int, int>> stanzasOnHold =
null;
79 private string authKey =
null;
80 private string authStreamId =
null;
81 private string remoteStreamId;
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;
117 int Port, X509Certificate DomainCertificate,
XmppServer Server,
bool TrustRemoteCertificate,
124 this.localDomainCertificate = DomainCertificate;
125 this.server = Server;
127 this.remoteStreamId =
null;
128 this.trustServer = TrustRemoteCertificate;
129 this.incomingConnection =
false;
130 this.temporary = Temporary;
132 if ((QueuedStanzas?.Length ?? 0) > 0)
151 this.client = Client;
152 this.server = Server;
154 this.remoteStreamId =
null;
155 this.localDomainCertificate = DomainCertificate;
156 this.trustServer = TrustRemoteCertificate;
157 this.incomingConnection =
true;
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;
176 public Task<bool>
Connect(
bool DisposeCurrent)
178 return this.
Connect(this.host, DisposeCurrent);
184 public override string Type =>
"XMPP";
196 public async Task<bool>
Connect(
string Host,
bool DisposeCurrent)
201 await this.DisposeClient(
"Making a new connection to " + Host);
203 this.connectTimeUtc = DateTime.UtcNow;
205 this.checkConnection =
true;
207 this.pingResponse =
true;
208 this.upgradeToTlsAsClient =
false;
209 this.upgradeToTlsAsServer =
false;
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;
221 if (!await this.client.
ConnectAsync(
this.host,
this.port))
226 if (!await this.BeginWrite(
"<?xml version='1.0' encoding='utf-8'?><stream:stream id='" + this.localStreamId +
"' to='" +
XML.
Encode(
this.remoteDomain) +
239 await this.ConnectionError(ex);
249 return this.DisposeClient(
"Closing connection.");
252 private void ResetState()
257 this.compressionMethods.Clear();
260 private async Task ConnectionError(Exception ex)
266 this.inputState = -1;
267 await this.DisposeClient(
"Connection Error: " + ex.Message);
271 private async Task Error(Exception Exception)
275 if (Exception is AggregateException ex)
277 foreach (Exception ex2
in ex.InnerExceptions)
278 await this.Error(ex2);
282 this.Error(Exception.Message);
284 await this.
OnError.Raise(
this, EventArgs.Empty);
303 get => this.trustServer;
304 set => this.trustServer = value;
334 return this.SetState(NewState,
null);
342 internal async Task SetState(
XmppS2sState NewState,
string Reason)
344 if (this.state != NewState)
346 this.state = NewState;
349 this.connectedTimeUtc = DateTime.UtcNow;
351 StringBuilder sb =
new StringBuilder();
353 sb.Append(
"State changed to ");
354 sb.Append(NewState.ToString());
356 if (!
string.IsNullOrEmpty(Reason))
379 DateTime UtcNow = DateTime.UtcNow;
381 if ((UtcNow - this.creationTimeUtc).TotalSeconds < 30)
387 return (UtcNow - this.connectTimeUtc).TotalSeconds > 90;
415 internal QueuedStanza[] GetAndClearQueuedStanzas()
417 lock (this.synchObject)
419 if (this.queue is
null)
422 QueuedStanza[] Result = this.queue.
ToArray();
437 this.checkConnection =
false;
439 this.secondTimer?.Dispose();
440 this.secondTimer =
null;
444 this.state ==
XmppS2sState.StartingEncryptionAsClient ||
445 this.state ==
XmppS2sState.StartingEncryptionAsServer ||
451 await this.BeginWrite(this.streamFooter, async (Sender, e) =>
458 await this.CleanUp(
this,
XmppS2sState.Offline, ex.Message);
462 await this.CleanUp(
this, EventArgs.Empty);
471 return this.CleanUp(
this,
XmppS2sState.Offline,
"Goind hard-offline.");
474 private Task CleanUp(
object Sender, EventArgs e)
476 return this.CleanUp(Sender,
XmppS2sState.Offline,
"Closing connection.");
481 await this.SetState(
State, Reason);
483 this.compressionMethods?.Clear();
485 this.secondTimer?.Dispose();
486 this.secondTimer =
null;
488 if (
string.IsNullOrEmpty(Reason))
489 await this.DisposeClient(
"Cleaning up connection.");
491 await this.DisposeClient(
"Cleaning up connection: " + Reason);
494 private async Task DisposeClient(
string Reason)
499 if (!(this.authConnection is
null))
503 if (
string.IsNullOrEmpty(Reason))
504 this.authConnection.KeyAuthenticated(
S2sValidationResult.Error,
"Disposing connection client.");
506 this.authConnection.KeyAuthenticated(
S2sValidationResult.Error,
"Disposing connection client: " + Reason);
513 this.authConnection =
null;
518 if (!
string.IsNullOrEmpty(this.server.DomainSnifferPath))
519 await this.server.CacheSniffers(this.Sniffers);
521 await this.
OnDisposed.Raise(
this, EventArgs.Empty,
false);
522 this.OnDisposed =
null;
524 this.server.S2sEndpointDisposed(
this);
532 private Task<bool> BeginWrite(
string Xml, EventHandlerAsync<DeliveryEventArgs> Callback,
object State)
534 this.nextPingUtc = DateTime.UtcNow.AddMilliseconds(this.keepAliveSeconds * 500);
535 return this.client?.
SendAsync(Xml, Callback,
State) ?? Task.FromResult(
false);
538 private Task<bool> Client_OnSent(
object Sender,
string Text)
540 this.server?.DataTransmitted(this.client?.LastTransmittedBytes ?? 0);
542 return Task.FromResult(
true);
545 private string Client_OnWarning(
string Text)
551 private string Client_OnInformation(
string Text)
557 private async Task<bool> Client_OnReceived(
object Sender,
string Text)
561 this.server?.DataReceived(this.client?.LastReceivedBytes ?? 0);
563 if (this.openBracketReceived)
565 this.openBracketReceived =
false;
568 else if (Text ==
"<")
569 this.openBracketReceived =
true;
573 return await this.ParseIncoming(Text);
584 private async Task Client_OnError(
object Sender, Exception Exception)
587 this.Error(Exception.Message);
591 private async Task Client_OnDisconnected(
object Sender, EventArgs e)
597 private const string FragmentTooBig =
"Fragment too big.";
598 private const string IllegalCharacterReceived =
"Illegal character received.";
600 private async Task<bool> ParseIncoming(
string s)
604 foreach (
char ch
in s)
606 switch (this.inputState)
611 this.fragment.Append(ch);
612 if (++this.fragmentLength > MaxFragmentSize)
614 await this.ToError(FragmentTooBig);
622 await this.ToError(IllegalCharacterReceived);
628 this.fragment.Append(ch);
629 if (++this.fragmentLength > MaxFragmentSize)
631 await this.ToError(FragmentTooBig);
640 if (!await this.ProcessStream(this.fragment.ToString()))
642 this.fragment.Clear();
643 this.fragmentLength = this.contentStart = this.contentEnd = 0;
648 if (++this.fragmentLength > MaxFragmentSize)
650 await this.ToError(FragmentTooBig);
655 this.fragment.Clear();
661 this.fragment.Append(ch);
662 if (++this.fragmentLength > MaxFragmentSize)
664 await this.ToError(FragmentTooBig);
671 await this.ToError(IllegalCharacterReceived);
677 this.fragment.Append(ch);
678 if (++this.fragmentLength > MaxFragmentSize)
680 await this.ToError(FragmentTooBig);
687 if (!await this.ProcessStream(this.fragment.ToString()))
689 this.fragment.Clear();
690 this.fragmentLength = this.contentStart = this.contentEnd = 0;
697 this.fragment.Append(ch);
698 if (++this.fragmentLength > MaxFragmentSize)
700 await this.ToError(FragmentTooBig);
706 else if (this.inputDepth > 1)
708 this.fragment.Append(ch);
709 if (++this.fragmentLength > MaxFragmentSize)
711 await this.ToError(FragmentTooBig);
717 await this.ToError(IllegalCharacterReceived);
723 this.fragment.Append(ch);
724 if (++this.fragmentLength > MaxFragmentSize)
726 await this.ToError(FragmentTooBig);
731 if (this.inputDepth == 2)
732 this.contentEnd = this.fragmentLength - 2;
740 this.
Warning(
"Processing instruction received. Assuming connection reused. Resetting state.");
748 this.inputState = 13;
750 this.inputState += 2;
754 this.fragment.Append(ch);
755 if (++this.fragmentLength > MaxFragmentSize)
757 await this.ToError(FragmentTooBig);
763 if (this.inputDepth < 1)
765 this.inputState = -1;
766 await this.CleanUp(
this,
XmppS2sState.Offline,
"Closing tag.");
771 if (this.inputDepth == 1)
773 if (!await this.ProcessFragment(this.fragment.ToString(),
this.contentStart,
this.contentEnd -
this.contentStart))
776 this.fragment.Clear();
777 this.fragmentLength = this.contentStart = this.contentEnd = 0;
780 if (this.inputState > 0)
787 this.fragment.Append(ch);
788 if (++this.fragmentLength > MaxFragmentSize)
790 await this.ToError(FragmentTooBig);
795 if (this.inputDepth == 1)
796 this.contentStart = this.fragmentLength;
804 this.inputState += 2;
808 this.fragment.Append(ch);
809 if (++this.fragmentLength > MaxFragmentSize)
811 await this.ToError(FragmentTooBig);
816 if (this.inputDepth == 1)
818 if (!await this.ProcessFragment(this.fragment.ToString(),
this.contentStart,
this.contentEnd -
this.contentStart))
821 this.fragment.Clear();
822 this.fragmentLength = this.contentStart = this.contentEnd = 0;
825 if (this.inputState != 0)
833 this.fragment.Append(ch);
834 if (++this.fragmentLength > MaxFragmentSize)
836 await this.ToError(FragmentTooBig);
841 if (this.inputDepth == 1)
842 this.contentStart = this.fragmentLength;
852 this.inputState += 2;
856 this.fragment.Append(ch);
857 if (++this.fragmentLength > MaxFragmentSize)
859 await this.ToError(FragmentTooBig);
867 this.fragment.Append(ch);
868 if (++this.fragmentLength > MaxFragmentSize)
870 await this.ToError(FragmentTooBig);
874 this.inputState -= 2;
878 this.fragment.Append(ch);
879 if (++this.fragmentLength > MaxFragmentSize)
881 await this.ToError(FragmentTooBig);
887 this.inputState = 18;
890 await this.ToError(IllegalCharacterReceived);
896 this.fragment.Append(ch);
897 if (++this.fragmentLength > MaxFragmentSize)
899 await this.ToError(FragmentTooBig);
906 await this.ToError(IllegalCharacterReceived);
912 this.fragment.Append(ch);
913 if (++this.fragmentLength > MaxFragmentSize)
915 await this.ToError(FragmentTooBig);
923 this.fragment.Append(ch);
924 if (++this.fragmentLength > MaxFragmentSize)
926 await this.ToError(FragmentTooBig);
936 this.fragment.Append(ch);
937 if (++this.fragmentLength > MaxFragmentSize)
939 await this.ToError(FragmentTooBig);
945 this.inputState -= 2;
949 this.fragment.Append(ch);
950 if (++this.fragmentLength > MaxFragmentSize)
952 await this.ToError(FragmentTooBig);
959 await this.ToError(IllegalCharacterReceived);
965 this.fragment.Append(ch);
966 if (++this.fragmentLength > MaxFragmentSize)
968 await this.ToError(FragmentTooBig);
975 await this.ToError(IllegalCharacterReceived);
981 this.fragment.Append(ch);
982 if (++this.fragmentLength > MaxFragmentSize)
984 await this.ToError(FragmentTooBig);
991 await this.ToError(IllegalCharacterReceived);
997 this.fragment.Append(ch);
998 if (++this.fragmentLength > MaxFragmentSize)
1000 await this.ToError(FragmentTooBig);
1007 await this.ToError(IllegalCharacterReceived);
1013 this.fragment.Append(ch);
1014 if (++this.fragmentLength > MaxFragmentSize)
1016 await this.ToError(FragmentTooBig);
1023 await this.ToError(IllegalCharacterReceived);
1029 this.fragment.Append(ch);
1030 if (++this.fragmentLength > MaxFragmentSize)
1032 await this.ToError(FragmentTooBig);
1039 await this.ToError(IllegalCharacterReceived);
1045 this.fragment.Append(ch);
1046 if (++this.fragmentLength > MaxFragmentSize)
1048 await this.ToError(FragmentTooBig);
1056 this.fragment.Append(ch);
1057 if (++this.fragmentLength > MaxFragmentSize)
1059 await this.ToError(FragmentTooBig);
1069 this.fragment.Append(ch);
1070 if (++this.fragmentLength > MaxFragmentSize)
1072 await this.ToError(FragmentTooBig);
1076 this.inputState = 5;
1078 this.inputState -= 2;
1089 private Task ToError(
string Reason)
1091 this.inputState = -1;
1095 private async Task<bool> ProcessStream(
string Xml)
1099 this.streamHeader = Xml;
1101 if (Xml.StartsWith(
"</"))
1103 await this.ConnectionError(
new Exception(
"Connection closed by the remote endpoint."));
1107 int i = Xml.IndexOf(
":stream");
1109 this.streamFooter =
"</stream>";
1111 this.streamFooter =
"</" + Xml[1..i] +
":stream>";
1113 XmlDocument Doc =
XML.
ParseXml(Xml + this.streamFooter,
true);
1115 if (Doc.DocumentElement.LocalName !=
"stream")
1116 throw new Exception(
"Invalid stream.");
1118 XmlElement Stream = Doc.DocumentElement;
1121 if (this.version < 1.0)
1122 throw new Exception(
"Version not supported.");
1126 if (this.incomingConnection)
1132 foreach (
ISniffer Sniffer
in this.Sniffers)
1141 this.
Remove(InMemorySniffer);
1143 InMemorySniffer.Replay(
this);
1147 if (!this.server.IsServerDomain(To,
true))
1149 if (
string.IsNullOrEmpty(To))
1150 throw new Exception(
"No to attribute in S2S connection stream. From: " +
XML.
Attribute(Stream,
"from"));
1152 throw new Exception(
"Unexpected domain: " + To);
1156 this.localDomain = To;
1164 throw new Exception(
"Unexpected domain: " + From +
". Expected: " + this.
remoteDomain);
1171 if (this.incomingConnection)
1173 StringBuilder sb =
new StringBuilder();
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='");
1183 sb.Append(
"' xmlns:stream='");
1185 sb.Append(
"'><stream:features><dialback xmlns='");
1187 sb.Append(
"'/></stream:features>");
1189 if (!await this.BeginWrite(sb.ToString(),
null,
null))
1195 if (!await this.SendQueued())
1202 if (this.incomingConnection)
1204 StringBuilder sb =
new StringBuilder();
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='");
1214 sb.Append(
"' xmlns:stream='");
1216 sb.Append(
"'><stream:features>");
1222 if (!(this.remoteDomainCertificateDomains is
null) &&
1223 Array.IndexOf(
this.remoteDomainCertificateDomains,
this.remoteDomain) >= 0)
1225 sb.Append(
"<mechanisms xmlns='");
1227 sb.Append(
"'><mechanism>EXTERNAL</mechanism></mechanisms>");
1231 sb.Append(
"<dialback xmlns='");
1233 sb.Append(
"'><errors/></dialback>");
1237 sb.Append(
"<starttls xmlns='");
1239 sb.Append(
"'><required/></starttls>");
1242 sb.Append(
"<bidi xmlns='");
1244 sb.Append(
"'/></stream:features>");
1246 if (!await this.BeginWrite(sb.ToString(),
null,
null))
1252 catch (Exception ex)
1254 await this.ConnectionError(ex);
1260 private async Task<bool> SendQueued()
1263 Tuple<string, int, int> OnHold;
1268 lock (this.synchObject)
1289 lock (this.synchObject)
1298 while (!(
Stanza is
null));
1304 lock (this.synchObject)
1306 if (this.stanzasOnHold is
null || this.stanzasOnHold.First is
null)
1308 this.stanzasOnHold =
null;
1312 OnHold = this.stanzasOnHold.First.Value;
1313 this.stanzasOnHold.RemoveFirst();
1314 if (this.stanzasOnHold.First is
null)
1315 this.stanzasOnHold =
null;
1321 this.
Information(
"Processing incoming stanzas put on hold.");
1324 this.
Information(OnHold.Item1.Substring(OnHold.Item2, OnHold.Item3));
1326 if (!await this.ProcessFragment(OnHold.Item1, OnHold.Item2, OnHold.Item3))
1328 lock (this.synchObject)
1330 this.stanzasOnHold ??=
new LinkedList<Tuple<string, int, int>>();
1331 this.stanzasOnHold.AddFirst(OnHold);
1337 while (!(OnHold is
null));
1342 private async Task<bool> ProcessFragment(
string Xml,
int ContentStart,
int ContentLen)
1353 Doc =
XML.
ParseXml(this.streamHeader + Xml + this.streamFooter,
true);
1355 Stanza =
new Stanza(Doc.DocumentElement, Xml, ContentStart, ContentLen);
1360 switch (E.LocalName)
1367 lock (this.synchObject)
1369 this.stanzasOnHold ??=
new LinkedList<Tuple<string, int, int>>();
1370 this.stanzasOnHold.AddLast(
new Tuple<string, int, int>(Xml, ContentStart, ContentLen));
1373 this.
Warning(
"Keeping stanza on hold while performing verification.");
1385 this.server.IncCounters(
"iq",
Type, From, To, E);
1387 if (!await this.CheckFrom(From))
1390 if (To.
Address ==
this.localDomain && From.
Address ==
this.remoteDomain && (
Type ==
"result" ||
Type ==
"error") &&
Id ==
this.pingId)
1392 this.pingResponse =
true;
1394 if (
Type ==
"error")
1395 this.supportsPing =
false;
1408 lock (this.synchObject)
1410 this.stanzasOnHold ??=
new LinkedList<Tuple<string, int, int>>();
1411 this.stanzasOnHold.AddLast(
new Tuple<string, int, int>(Xml, ContentStart, ContentLen));
1414 this.
Warning(
"Keeping stanza on hold while performing verification");
1426 this.server.IncCounters(
"message",
Type, From, To, E);
1428 if (!await this.CheckFrom(From))
1431 this.ProcessMessage(
Type,
Id, To, From, Language,
Stanza);
1439 lock (this.synchObject)
1441 this.stanzasOnHold ??=
new LinkedList<Tuple<string, int, int>>();
1442 this.stanzasOnHold.AddLast(
new Tuple<string, int, int>(Xml, ContentStart, ContentLen));
1445 this.
Warning(
"Keeping stanza on hold while performing verification");
1457 this.server.IncCounters(
"presence",
Type, From, To, E);
1459 if (!await this.CheckFrom(From))
1462 this.ProcessPresence(
Type,
Id, To, From, Language,
Stanza);
1466 if (E.FirstChild is
null)
1467 this.DialbackCompleted(
false,
false,
"No features available.");
1470 bool StartTls =
false;
1471 bool Dialback =
false;
1472 bool Bidirectional =
false;
1473 bool ExternalAuth =
false;
1475 foreach (XmlNode N2
in E.ChildNodes)
1477 switch (N2.LocalName)
1484 foreach (XmlNode N3
in N2.ChildNodes)
1486 if (N3.LocalName ==
"method")
1487 this.compressionMethods[N3.InnerText.Trim().ToUpper()] =
true;
1492 Bidirectional =
true;
1500 foreach (XmlNode N3
in N2.ChildNodes)
1502 if (N3.LocalName ==
"mechanism")
1504 switch (N3.InnerText)
1507 ExternalAuth =
true;
1519 if (StartTls && this.allowEncryption)
1521 else if (ExternalAuth)
1523 sb =
new StringBuilder();
1527 sb.Append(
"<bidi xmlns='");
1532 sb.Append(
"<auth xmlns='");
1534 sb.Append(
"' mechanism='EXTERNAL'>");
1535 sb.Append(Convert.ToBase64String(Encoding.UTF8.GetBytes(
this.localDomain)));
1536 sb.Append(
"</auth>");
1538 return await this.BeginWrite(sb.ToString(),
null,
null);
1540 else if (Dialback && !this.verified)
1544 sb =
new StringBuilder();
1546 if (!
string.IsNullOrEmpty(this.authKey))
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));
1556 sb.Append(
"</db:verify>");
1557#if LogToWebHookTester
1558 _ = Task.Run(async () =>
1563 new Dictionary<string, object>()
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() }
1573 new KeyValuePair<string, string>(
"Accept",
"application/json"));
1575 catch (Exception ex)
1581 this.authVerifyRequestSent =
true;
1586 this.authResultRequestSent =
true;
1590 sb.Append(
"<bidi xmlns='");
1595 sb.Append(
"<db:result from='");
1596 sb.Append(
XML.
Encode(
this.localDomain));
1597 sb.Append(
"' to='");
1598 sb.Append(
XML.
Encode(
this.remoteDomain));
1600 sb.Append(this.authKey);
1601 sb.Append(
"</db:result>");
1603#if LogToWebHookTester
1605 _ = Task.Run(async () =>
1610 new Dictionary<string, object>()
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() }
1620 new KeyValuePair<string, string>(
"Accept",
"application/json"));
1622 catch (Exception ex)
1630 return await this.BeginWrite(sb.ToString(),
null,
null);
1636 this.upgradeToTlsAsClient =
true;
1641 this.upgradeToTlsAsServer =
true;
1646 this.bidirectional =
true;
1650 sb =
new StringBuilder();
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='");
1660 sb.Append(
"' xmlns:stream='");
1664 if (!await this.BeginWrite(sb.ToString(),
null,
null))
1667 this.verified =
true;
1668 this.DialbackCompleted(
true,
false,
null);
1672 this.DialbackCompleted(
false,
false,
"Authentication failed.");
1678 string s = E.InnerText;
1685 byte[] Bin = Convert.FromBase64String(s);
1686 Domain = Encoding.UTF8.GetString(Bin);
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;
1694 if (RemoteCertificateValid &&
1696 RemoteDomainsInCertificate &&
1697 DomainInCertificate)
1702 this.verified =
true;
1703 this.DialbackCompleted(
true,
false,
null);
1707 if (!await this.BeginWrite(
"<failure xmlns='" +
XmppServer.
SaslNamespace +
"'><not-authorized/></failure>",
null,
null))
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.");
1719 this.DialbackCompleted(
false,
false,
"Something went wrong.");
1728 if (
string.IsNullOrEmpty(
Type))
1730#if LogToWebHookTester
1732 _ = Task.Run(async () =>
1737 new Dictionary<string, object>()
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 }
1746 new KeyValuePair<string, string>(
"Accept",
"application/json"));
1748 catch (Exception ex)
1756 sb =
new StringBuilder();
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'/>");
1766 this.
Information(
"Performing dialback to validate domain name claim.");
1771#if LogToWebHookTester
1773 _ = Task.Run(async () =>
1778 new Dictionary<string, object>()
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 }
1787 new KeyValuePair<string, string>(
"Accept",
"application/json"));
1789 catch (Exception ex)
1796 false,
"Performing dialback to validate domain name claim.");
1800 XmppS2SEndpoint.authKey = E.InnerText;
1801 XmppS2SEndpoint.authStreamId = this.localStreamId;
1802 XmppS2SEndpoint.authConnection =
this;
1805 catch (Exception ex)
1809 sb =
new StringBuilder();
1813#if LogToWebHookTester
1815 _ = Task.Run(async () =>
1820 new Dictionary<string, object>()
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 }
1831 new KeyValuePair<string, string>(
"Accept",
"application/json"));
1833 catch (Exception ex2)
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='");
1846 sb.Append(ErrorXml);
1847 sb.Append(
"<text xmlns='");
1851 sb.Append(
"</text></error></db:result>");
1853 if (!await this.BeginWrite(sb.ToString(),
null,
null))
1858 else if (this.authResultRequestSent)
1862 if (
Type ==
"valid")
1864 Endpoint.verified =
true;
1865 Endpoint.DialbackCompleted(
true,
true,
null);
1868 Endpoint.DialbackCompleted(
false,
true,
"Remote end rejects authentication result.");
1870 if (!(this.authConnection is
null))
1872 this.authConnection =
null;
1873 await this.CleanUp(
this,
XmppS2sState.Offline,
"Dialback check completed.");
1880 if (E.NamespaceURI == DialbackNamespace)
1887 if (
string.IsNullOrEmpty(Type))
1889 string Key = this.server.GetDialbackKey(From.
Address, To.
Address, Id);
1890 sb =
new StringBuilder();
1892 this.verified = Key == E.InnerText;
1894 sb.Append(
"<db:verify to='");
1896 sb.Append(
"' from='");
1898 sb.Append(
"' id='");
1900 sb.Append(
"' type='");
1901 sb.Append(this.verified ?
"valid" :
"invalid");
1904#if LogToWebHookTester
1906 _ = Task.Run(async () =>
1910 await
InternetContent.
PostAsync(
new Uri(
"https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
1911 new Dictionary<string, object>()
1913 {
"id", this.Id.ToString() },
1914 {
"event",
"verify stanza" },
1915 {
"local", this.localDomain.Value },
1916 {
"remote", this.remoteDomain.Value },
1917 {
"verified", this.verified },
1919 {
"receivedKey", E.InnerText },
1920 {
"xml", sb.ToString() }
1922 new KeyValuePair<string, string>(
"Accept",
"application/json"));
1924 catch (Exception ex)
1932 await this.BeginWrite(sb.ToString(), (Sender, e) =>
this.DisposeAsync(
"Verification failed."),
null);
1937 if (!await this.BeginWrite(sb.ToString(),
null,
null))
1940 if (this.verified && this.state ==
XmppS2sState.Connected)
1942 if (!await this.SendQueued())
1949 this.verified = Type ==
"valid";
1951 if (!(this.authConnection is
null))
1957 this.authConnection.KeyAuthenticated(
1962 this.authConnection.KeyAuthenticated(
1964 "Remote validation failed.");
1968 catch (Exception ex)
1973 this.authConnection =
null;
1976 if (this.authVerifyRequestSent)
1978 await this.DisposeAsync(
"Verification request already sent.");
1981 else if (this.verified)
1983 if (!await this.SendQueued())
1994 catch (Exception ex)
1996 await this.ConnectionError(ex);
2007 await this.server.ProcessIq(Id, To, From, Type, Language,
Stanza,
this);
2009 catch (Exception ex)
2019 await this.server.Message(Type, Id, To, From, Language,
Stanza,
this);
2021 catch (Exception ex)
2031 await this.server.Presence(Type, Id, To, From, Language,
Stanza,
this);
2033 catch (Exception ex)
2039 private async Task Client_OnPaused(
object Sender, EventArgs e)
2041 if (this.upgradeToTlsAsClient || this.upgradeToTlsAsServer)
2043 bool AsClient = this.upgradeToTlsAsClient;
2045 this.upgradeToTlsAsClient =
false;
2046 this.upgradeToTlsAsServer =
false;
2048 string RemoteEndPoint = this.client.
RemoteEndPoint.RemovePortNumber();
2056 await this.SetState(
XmppS2sState.StartingEncryptionAsClient);
2061 await this.SetState(
XmppS2sState.StartingEncryptionAsServer);
2067 this.remoteDomainCertificateDomains = this.GetIdentities(this.client.
RemoteCertificate);
2069 if (this.HasSniffers)
2071 StringBuilder sb =
new StringBuilder();
2074 sb.Append(
"Remote Certificate received. Valid: ");
2076 sb.Append(
", SslPolicyErrors: ");
2078 sb.Append(
", Subject: ");
2081 if (Subject.StartsWith(
"CN="))
2082 Subject = Subject[3..];
2088 if (Name != Subject)
2092 sb.Append(
", Alternative Names: ");
2102 sb.Append(
", Issuer: ");
2104 sb.Append(
", S/N: ");
2106 sb.Append(
", Hash: ");
2116 sb.Append(Convert.ToBase64String(Bin, Base64FormattingOptions.InsertLineBreaks));
2120 sb.Append(
" No raw data in certificate.");
2124 string Msg = sb.ToString();
2126 this.Information(Msg);
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='" +
2144 catch (AuthenticationException ex)
2146 await this.LoginFailure(ex, AsClient, RemoteEndPoint);
2148 catch (Win32Exception ex)
2150 await this.LoginFailure(ex, AsClient, RemoteEndPoint);
2152 catch (Exception ex)
2154 await this.ConnectionError(ex);
2158 await this.ConnectionError(
new Exception(
"Remote endpoint rejected due to suspected TLS hacking."));
2162 private async Task LoginFailure(Exception ex,
bool AsClient,
string RemoteIpEndpoint)
2169 await this.ConnectionError(ex);
2173 private async Task<bool> CheckFrom(
XmppAddress From)
2175 if (this.remoteDomain is
null)
2180 if (FromDomain == this.remoteDomain)
2183 if (FromDomain.
EndsWith(
"." +
this.remoteDomain, StringComparison.CurrentCultureIgnoreCase))
2186 await this.BeginWrite(
"<stream:error><invalid-from xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error></stream:stream>", async (Sender, e) =>
2188 await this.CleanUp(
this,
XmppS2sState.Error,
"Invalid from attribute value.");
2198 if (this.client?.Connected ??
false)
2200 if (
string.IsNullOrEmpty(this.authKey))
2202 StringBuilder Xml =
new StringBuilder();
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='");
2210 switch (ValidationResult)
2213 Xml.Append(
"valid'/>");
2217 Xml.Append(
"invalid'/>");
2222 Xml.Append(
"error'><error type='wait'>");
2223 Xml.Append(
"<internal-server-error xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>");
2225 if (!
string.IsNullOrEmpty(Reason))
2227 Xml.Append(
"<text xmlns='");
2231 Xml.Append(
"</text>");
2234 Xml.Append(
"</error></db:result>");
2238#if LogToWebHookTester
2240 _ = Task.Run(async () =>
2244 await
InternetContent.
PostAsync(
new Uri(
"https://lab.tagroot.io/WebHookTester/Show.ws?ID=" + this.localDomain.Value),
2245 new Dictionary<string, object>()
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() }
2255 new KeyValuePair<string, string>(
"Accept",
"application/json"));
2257 catch (Exception ex)
2263 await this.BeginWrite(Xml.ToString(),
null,
null);
2270 Log.
Informational(
"XMPP S2S connection accepted.", this.remoteDomain, this.localDomain,
"XmppIncomingS2SSuccess",
EventLevel.Minor,
2271 new KeyValuePair<string, object>(
"RemoteEndPoint",
this.client.RemoteEndPoint));
2273 if (this.bidirectional && !this.temporary)
2274 this.server.RegisterS2SEndpoint(
this);
2276 await this.SendQueued();
2280 Log.
Warning(
"XMPP S2S connection failed.", this.remoteDomain, this.localDomain,
2282 new KeyValuePair<string, object>(
"RemoteEndPoint",
this.client.RemoteEndPoint),
2283 new KeyValuePair<string, object>(
"Reason", Reason));
2287 catch (Exception ex)
2293 internal static Exception GetStreamExceptionObject(XmlElement E)
2298 internal static Exception GetExceptionObject(XmlElement E,
string Namespace)
2300 string Msg =
string.Empty;
2302 foreach (XmlNode N2
in E.ChildNodes)
2304 if (N2.LocalName ==
"text")
2305 Msg = N2.InnerText.Trim();
2308 foreach (XmlNode N2
in E.ChildNodes)
2310 if (N2.NamespaceURI == Namespace)
2312 if (
string.IsNullOrEmpty(Msg))
2315 return new Exception(Msg);
2319 return new Exception(
string.IsNullOrEmpty(Msg) ?
"Unspecified error returned." : Msg);
2322 internal static Exception GetStanzaExceptionObject(XmlElement E)
2327 internal static Exception GetSaslExceptionObject(XmlElement E)
2334 List<CaseInsensitiveString> Domains =
new List<CaseInsensitiveString>();
2335 bool HasAlternativeNames =
false;
2339 foreach (
string Part
in certificate.Subject.Split(certificateSubjectSeparator, StringSplitOptions.None))
2341 if (Part.StartsWith(
"CN="))
2342 Domains.Add(Part[3..]);
2343 else if (Part.StartsWith(
"SAN="))
2345 Domains.Add(Part[4..]);
2346 HasAlternativeNames =
true;
2350 if (!HasAlternativeNames)
2352 if (!(certificate is X509Certificate2 Cert2))
2354 byte[] Bin = certificate.GetRawCertData();
2355 Cert2 =
new X509Certificate2(Bin);
2358 foreach (X509Extension Extension
in Cert2.Extensions)
2360 if (Extension.Oid.Value ==
"2.5.29.17")
2362 AsnEncodedData Parsed =
new AsnEncodedData(Extension.Oid, Extension.RawData);
2363 string[] SAN = Parsed.Format(
true).Split(
CommonTypes.
CRLF, StringSplitOptions.RemoveEmptyEntries);
2365 foreach (
string Name
in SAN)
2367 int i = Name.LastIndexOf(
'=');
2369 Domains.Add(Name[(i + 1)..]);
2375 catch (Exception ex)
2377 StringBuilder sb =
new StringBuilder();
2379 sb.Append(
"Unable to extract domain names from certificate subject (");
2380 sb.Append(ex.Message);
2385 byte[] Bin = certificate.GetRawCertData();
2388 sb.Append(Convert.ToBase64String(Bin, Base64FormattingOptions.InsertLineBreaks));
2392 sb.Append(
" No raw data in certificate.");
2395 this.Error(sb.ToString());
2398 return Domains.ToArray();
2401 private readonly
static string[] certificateSubjectSeparator =
new string[] {
", " };
2403 private async
void DialbackCompleted(
bool Successful,
bool SendQueued,
string Reason)
2410 this.supportsPing =
true;
2411 this.secondTimer =
new Timer(this.SecondTimerCallback,
null, 1000, 1000);
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));
2418 await this.SendQueued();
2422 await this.SetState(
XmppS2sState.Error,
"Dialback failed.");
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));
2431 lock (this.synchObject)
2434 this.stanzasOnHold =
null;
2439 catch (Exception ex)
2449 public int KeepAliveSeconds
2451 get => this.keepAliveSeconds;
2455 throw new ArgumentException(
"Value must be positive.", nameof(this.KeepAliveSeconds));
2457 this.keepAliveSeconds = value;
2461 private async
void SecondTimerCallback(
object State)
2465 if (!this.checkConnection)
2468 DateTime UtcNow = DateTime.UtcNow;
2470 if (UtcNow >= this.nextPingUtc && this.state ==
XmppS2sState.Connected)
2472 this.server.TouchServerConnection(this.remoteDomain);
2474 this.nextPingUtc = DateTime.UtcNow.AddMilliseconds(this.keepAliveSeconds * 500);
2477 if (this.supportsPing)
2479 if (this.pingResponse)
2481 this.pingResponse =
false;
2482 await this.SendPing();
2485 await this.DisposeAsync(
"No ping response.");
2488 await this.BeginWrite(
" ",
null,
null);
2490 catch (Exception ex)
2494 await this.DisposeAsync(ex.Message);
2498 catch (Exception ex)
2509 StringBuilder Xml =
new StringBuilder();
2511 this.pingId = this.server.NewId(16);
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='");
2521 Xml.Append(
"'/></iq>");
2523 return this.BeginWrite(Xml.ToString(),
null,
null);
2531 return this.SendStanza(
"iq", Type, Id, To, From, Language,
Stanza?.Content, Sender);
2537 return this.SendStanza(
"iq", Type, Id, To, From, Language, ContentXml, Sender);
2540 internal async Task<bool> SendStanza(
string StanzaType,
string Type,
string Id,
XmppAddress To,
XmppAddress From,
string Language,
string ContentXml,
ISender Sender)
2544 lock (this.synchObject)
2553 this.queue.
Add(
new QueuedStanza()
2555 StanzaType = StanzaType,
2560 Language = Language,
2561 ContentXml = ContentXml,
2571 if (!(Sender is
null))
2573 switch (StanzaType.ToLower())
2579 return !(await Sender.
IqError(Id, From, To, this.errorType, this.errorXml,
"S2S connection failed.",
"en") is
null);
2582 if (!await (Sender?.MessageError(Id, From, To, this.errorType, this.errorXml,
"S2S connection failed.",
"en") ?? Task.FromResult(
true)))
2587 if (!await (Sender?.PresenceError(Id, From, To, this.errorType, this.errorXml,
"S2S connection failed.",
"en") ?? Task.FromResult(
true)))
2597 if (!await this.BeginWrite(StanzaType, Type, Id, To, From, Language, ContentXml))
2600 catch (Exception ex)
2602 if (!(Sender is
null))
2606 switch (StanzaType.ToLower())
2612 return !(await Sender.
IqError(Id, From, To, this.errorType, this.errorXml,
"S2S connection failed: " + ex.Message,
"en") is
null);
2615 if (!await (Sender?.MessageError(Id, From, To, this.errorType, this.errorXml,
"S2S connection failed: " + ex.Message,
"en") ?? Task.FromResult(
true)))
2620 if (!await (Sender?.PresenceError(Id, From, To, this.errorType, this.errorXml,
"S2S connection failed: " + ex.Message,
"en") ?? Task.FromResult(
true)))
2625 catch (Exception ex2)
2636 private Task<bool> BeginWrite(
string StanzaType,
string Type,
string Id,
XmppAddress To,
XmppAddress From,
string Language,
string ContentXml)
2638 StringBuilder Xml =
new StringBuilder();
2641 Xml.Append(StanzaType);
2645 Xml.Append(
" from='");
2651 Xml.Append(
"' to='");
2655 if (!
string.IsNullOrEmpty(Type))
2657 Xml.Append(
"' type='");
2661 if (!
string.IsNullOrEmpty(Id))
2663 Xml.Append(
"' id='");
2667 if (!
string.IsNullOrEmpty(Language))
2669 Xml.Append(
"' xml:lang='");
2673 if (
string.IsNullOrEmpty(ContentXml))
2678 Xml.Append(ContentXml);
2680 Xml.Append(StanzaType);
2684 return this.BeginWrite(Xml.ToString(),
null,
null);
2690 return this.SendStanza(
"message", Type, Id, To, From, Language,
Stanza?.Content, Sender);
2696 return this.SendStanza(
"message", Type, Id, To, From, Language, ContentXml, Sender);
2702 return this.SendStanza(
"presence", Type, Id, To, From, Language,
Stanza?.Content, Sender);
2708 return this.SendStanza(
"presence", Type, Id, To, From, Language, ContentXml, Sender);
2718 StringBuilder Xml =
new StringBuilder();
2723 Xml.Append(ErrorXml);
2724 Xml.Append(
"</iq>");
2726 return this.BeginWrite(Xml.ToString(),
null,
null);
2733 return this.IqError(Id, To, From, Type, Xml, ex.Message +
"\r\n\r\n" +
Log.
CleanStackTrace(ex.StackTrace),
string.Empty);
2739 StringBuilder Xml =
new StringBuilder();
2744 Xml.Append(ResultXml);
2745 Xml.Append(
"</iq>");
2747 return this.BeginWrite(Xml.ToString(),
null,
null);
2753 return this.SendStanza(
"presence", Type, Id, To, From, Language, ContentXml,
null);
2760 StringBuilder Xml =
new StringBuilder();
2762 Xml.Append(
"<presence id='");
2764 Xml.Append(
"' from='");
2766 Xml.Append(
"' to='");
2768 Xml.Append(
"' type='error'>");
2769 Xml.Append(ErrorXml);
2770 Xml.Append(
"</presence>");
2772 return this.BeginWrite(Xml.ToString(),
null,
null);
2779 return this.PresenceError(Id, To, From, Type, Xml, ex.Message +
"\r\n\r\n" +
Log.
CleanStackTrace(ex.StackTrace),
string.Empty);
2785 return this.Message(Type, Id, To, From, Language, ContentXml,
null);
2792 return this.MessageError(Id, To, From, Type, Xml, ex.Message +
"\r\n\r\n" +
Log.
CleanStackTrace(ex.StackTrace),
string.Empty);
Helps with parsing of commong data types.
static readonly char[] CRLF
Contains the CR LF character sequence.
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.
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
static string Encode(string s)
Encodes a string for use in XML.
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Static class managing the application event log. Applications and services log events on this static ...
static string CleanStackTrace(string StackTrace)
Cleans a Stack Trace string, removing entries from the asynchronous execution model,...
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.
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.
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.
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
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
Task DisposeAsync()
IDisposable.Dispose
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.
XmlElement StanzaElement
Stanza element.
Contains information about one XMPP address.
bool IsEmpty
If the address is empty.
CaseInsensitiveString Domain
Domain
CaseInsensitiveString Address
XMPP Address
Class managing a connection.
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
Task Close()
Closes the connection.
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 IsStale
If connection is stale.
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.
string RemoteEndPoint
Remote endpoint
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.
static void GetErrorInformation(Exception ex, out string Type, out string Xml)
Converts an Exception to an XMPP error message.
const string SaslNamespace
urn:ietf:params:xml:ns:xmpp-sasl
const string PingNamespace
urn:xmpp:ping (XEP-0199)
const string StanzaNamespace
urn:ietf:params:xml:ns:xmpp-stanzas (RFC 6120)
const string StreamsNamespace
urn:ietf:params:xml:ns:xmpp-streams
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 .
void Clear()
Clears the collection.
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
T RemoveFirst()
Removes the first item in the collection.
void Add(T Item)
Adds an item to the collection.
T[] ToArray()
Returns an array containing all elements of the collection.
Helper methods for encrypting and decrypting streams of data.
const SslProtocols SecureTls
TLS 1.2 & 1.3
Class that monitors login events, and help applications determine malicious intent....
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...
Interface for XMPP S2S endpoints
Interface for senders of stanzas.
Task< string > IqError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends an IQ Error stanza.
delegate Task EventHandlerAsync(object Sender, EventArgs e)
Asynchronous version of EventArgs.
XmppS2sState
State of XMPP connection.
S2sValidationResult
S2S validation result.
ClientCertificates
Client Certificate Options