2using System.Collections.Generic;
3using System.ComponentModel;
5using System.Net.Security;
6using System.Net.Sockets;
7using System.Security.Authentication;
8using System.Security.Cryptography.X509Certificates;
10using System.Threading.Tasks;
26 private const int MaxFragmentSize = 40000000;
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;
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;
70 public override string Binding =>
"Socket";
77 get {
return this.client?.
Client?.Client?.RemoteEndPoint?.ToString(); }
109 this.isBound =
false;
110 await this.ProcessFragment(
"<presence type=\"unavailable\"/>", 0, 0);
111 this.
server?.ConnectionClosed(
this);
115 if (!(Sniffers is
null))
116 this.
server?.CacheSniffers(Sniffers);
121 await base.DisposeAsync();
130 private async Task<bool> Client_OnSent(
object Sender,
string Text)
132 this.
server?.DataTransmitted(this.client?.LastTransmittedBytes ?? 0);
137 private async Task<string> Client_OnWarning(
string Text)
143 private async Task<string> Client_OnInformation(
string Text)
149 private async Task<bool> Client_OnReceived(
object Sender,
string Text)
153 this.
server?.DataReceived(this.client?.LastReceivedBytes ?? 0);
155 if (this.openBracketReceived)
157 this.openBracketReceived =
false;
160 else if (Text ==
"<")
161 this.openBracketReceived =
true;
165 return await this.ParseIncoming(Text);
170 await this.Client_OnError(
this, ex);
176 private async Task Client_OnError(
object Sender, Exception Exception)
179 await this.
Error(Exception.Message);
183 private async Task Client_OnDisconnected(
object Sender, EventArgs e)
189 private async Task<bool> ParseIncoming(
string s)
193 foreach (
char ch
in s)
195 switch (this.inputState)
200 this.fragment.Append(ch);
201 if (++this.fragmentLength > MaxFragmentSize)
217 this.fragment.Append(ch);
218 if (++this.fragmentLength > MaxFragmentSize)
230 if (!await this.ProcessStream(this.fragment.ToString()))
233 this.fragment.Clear();
234 this.fragmentLength = this.contentStart = this.contentEnd = 0;
239 this.fragment.Append(ch);
240 if (++this.fragmentLength > MaxFragmentSize)
250 this.fragment.Append(ch);
251 if (++this.fragmentLength > MaxFragmentSize)
266 this.fragment.Append(ch);
267 if (++this.fragmentLength > MaxFragmentSize)
276 if (!await this.ProcessStream(this.fragment.ToString()))
279 this.fragment.Clear();
280 this.fragmentLength = this.contentStart = this.contentEnd = 0;
287 this.fragment.Append(ch);
288 if (++this.fragmentLength > MaxFragmentSize)
296 else if (this.inputDepth > 1)
298 this.fragment.Append(ch);
299 if (++this.fragmentLength > MaxFragmentSize)
313 this.fragment.Append(ch);
314 if (++this.fragmentLength > MaxFragmentSize)
321 if (this.inputDepth == 2)
322 this.contentEnd = this.fragmentLength - 2;
327 this.inputState = 13;
329 this.inputState += 2;
333 this.fragment.Append(ch);
334 if (++this.fragmentLength > MaxFragmentSize)
342 if (this.inputDepth < 1)
349 if (this.inputDepth == 1)
351 if (!await this.ProcessFragment(this.fragment.ToString(),
this.contentStart,
this.contentEnd -
this.contentStart))
354 this.fragment.Clear();
355 this.fragmentLength = this.contentStart = this.contentEnd = 0;
358 if (this.inputState > 0)
365 this.fragment.Append(ch);
366 if (++this.fragmentLength > MaxFragmentSize)
373 if (this.inputDepth == 1)
374 this.contentStart = this.fragmentLength;
382 this.inputState += 2;
386 this.fragment.Append(ch);
387 if (++this.fragmentLength > MaxFragmentSize)
394 if (this.inputDepth == 1)
396 if (!await this.ProcessFragment(this.fragment.ToString(), 0, 0))
399 this.fragment.Clear();
400 this.fragmentLength = this.contentStart = this.contentEnd = 0;
403 if (this.inputState != 0)
411 this.fragment.Append(ch);
412 if (++this.fragmentLength > MaxFragmentSize)
419 if (this.inputDepth == 1)
420 this.contentStart = this.fragmentLength;
430 this.inputState += 2;
434 this.fragment.Append(ch);
435 if (++this.fragmentLength > MaxFragmentSize)
445 this.fragment.Append(ch);
446 if (++this.fragmentLength > MaxFragmentSize)
452 this.inputState -= 2;
456 this.fragment.Append(ch);
457 if (++this.fragmentLength > MaxFragmentSize)
465 this.inputState = 18;
474 this.fragment.Append(ch);
475 if (++this.fragmentLength > MaxFragmentSize)
490 this.fragment.Append(ch);
491 if (++this.fragmentLength > MaxFragmentSize)
501 this.fragment.Append(ch);
502 if (++this.fragmentLength > MaxFragmentSize)
514 this.fragment.Append(ch);
515 if (++this.fragmentLength > MaxFragmentSize)
523 this.inputState -= 2;
527 this.fragment.Append(ch);
528 if (++this.fragmentLength > MaxFragmentSize)
543 this.fragment.Append(ch);
544 if (++this.fragmentLength > MaxFragmentSize)
559 this.fragment.Append(ch);
560 if (++this.fragmentLength > MaxFragmentSize)
575 this.fragment.Append(ch);
576 if (++this.fragmentLength > MaxFragmentSize)
591 this.fragment.Append(ch);
592 if (++this.fragmentLength > MaxFragmentSize)
607 this.fragment.Append(ch);
608 if (++this.fragmentLength > MaxFragmentSize)
623 this.fragment.Append(ch);
624 if (++this.fragmentLength > MaxFragmentSize)
634 this.fragment.Append(ch);
635 if (++this.fragmentLength > MaxFragmentSize)
647 this.fragment.Append(ch);
648 if (++this.fragmentLength > MaxFragmentSize)
656 this.inputState -= 2;
674 public override Task<bool>
StreamError(
string ErrorXml,
string Reason)
676 return this.ToError(
"<stream:error>" + ErrorXml +
"</stream:error>", Reason);
679 private async Task<bool> ToError(
string ErrorXml,
string Reason)
681 if (
string.IsNullOrEmpty(ErrorXml))
683 this.inputState = -1;
685 await this.Client_OnError(
this,
new Exception(Reason));
691 return await this.
BeginWrite(ErrorXml + this.streamFooter, async (Sender, e) =>
693 this.inputState = -1;
701 private async Task<bool> ProcessStream(
string Xml)
703 StringBuilder ToSend =
new StringBuilder();
707 int i = Xml.IndexOf(
"?>");
709 Xml = Xml.Substring(i + 2).TrimStart();
711 this.streamHeader = Xml;
713 i = Xml.IndexOf(
":stream");
715 this.streamFooter =
"</stream>";
717 this.streamFooter =
"</" + Xml.Substring(1, i - 1) +
":stream>";
719 XmlDocument Doc =
new XmlDocument()
721 PreserveWhitespace =
true
723 Doc.LoadXml(Xml + this.streamFooter);
725 XmlElement Stream = Doc.DocumentElement;
730 this.language =
XML.
Attribute(Stream,
"xml:lang",
"en");
732 this.bareAddress =
new XmppAddress(this.
bareJid);
734 if (
string.IsNullOrEmpty(this.streamId))
741 ToSend.Append(
"<?xml version='1.0' encoding='utf-8'?>");
742 ToSend.Append(
"<stream:stream from='");
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='");
759 if (Doc.DocumentElement.Prefix !=
"stream")
762 await this.
StreamError(
"<bad-namespace-prefix xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Bad namespace prefix.");
766 if (Doc.DocumentElement.LocalName !=
"stream")
769 await this.
StreamError(
"<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Bad format.");
773 if (!this.
server.
IsServerDomain(
this.domain,
true) && (!
string.IsNullOrEmpty(
this.server.Domain) || !IPAddress.TryParse(
this.domain, out IPAddress _)))
776 await this.
StreamError(
"<host-unknown xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Domain unknown.");
780 if (this.version != 1.0)
783 await this.ToError(
"<unsupported-version xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Unsupported version.");
791 ToSend.Append(
"<stream:features>");
793 this.qlMechanism =
null;
794 this.qlChallenge =
null;
795 this.qlResource =
null;
799 ToSend.Append(
"<ql xmlns='");
801 ToSend.Append(
"'/>");
806 ToSend.Append(
"<starttls xmlns='");
811 ToSend.Append(
"><required/></starttls>");
819 ToSend.Append(
"<mechanisms xmlns='" + XmppServer.SaslNamespace +
"'>");
821 SslStream SslStream = this.client.Stream as SslStream;
824 if (Mechanism.
Allowed(SslStream))
826 ToSend.Append(
"<mechanism>");
827 ToSend.Append(Mechanism.
Name);
828 ToSend.Append(
"</mechanism>");
832 ToSend.Append(
"</mechanisms>");
834 if (await this.
server.CanRegister(
this))
835 ToSend.Append(
"<register xmlns='http://jabber.org/features/iq-register'/>");
841 ToSend.Append(
"<bind xmlns='");
843 ToSend.Append(
"'/>");
844 ToSend.Append(
"<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
847 ToSend.Append(
"</stream:features>");
854 StringBuilder Msg =
new StringBuilder();
856 Msg.Append(
"Incoming XMPP stream rejected: ");
857 Msg.AppendLine(ex.Message);
859 if (!
string.IsNullOrEmpty(Xml))
862 if (Xml.Length > 1000)
863 Xml = Xml.Substring(0, 1000) +
"...";
866 Msg.AppendLine(
"```xml");
868 Msg.AppendLine(
"```");
871 string s = Msg.ToString();
878 await this.
StreamError(
"<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Bad format.");
889 public override Task<bool>
BeginWrite(
string Xml, EventHandlerAsync<DeliveryEventArgs> Callback,
object State)
891 return this.client?.
SendAsync(Xml, Callback,
State) ?? Task.FromResult(
false);
894 private Task<bool>
BeginWrite(StringBuilder ToSend)
896 string Xml = ToSend.ToString();
897 if (
string.IsNullOrEmpty(Xml))
898 return Task.FromResult(
true);
905 private async Task<bool> ProcessFragment(
string Xml,
int ContentStart,
int ContentLen)
915 if (!
string.IsNullOrEmpty(this.
fullJid))
918 Doc =
new XmlDocument()
920 PreserveWhitespace =
true
922 Doc.LoadXml(this.streamHeader + Xml + this.streamFooter);
924 Stanza =
new Stanza(Doc.DocumentElement, Xml, ContentStart, ContentLen);
931 await this.Exception(ex);
932 await this.
StreamError(
"<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", ex.Message);
937 private async Task Client_OnPaused(
object Sender, EventArgs e)
939 if (this.upgradeToTls)
941 this.upgradeToTls =
false;
943 string RemoteIpEndpoint;
944 EndPoint EP = this.client.
Client.Client.RemoteEndPoint;
946 if (EP is IPEndPoint IpEP)
947 RemoteIpEndpoint = IpEP.Address.ToString();
949 RemoteIpEndpoint = EP.ToString();
959 bool QuickLogin = !
string.IsNullOrEmpty(this.qlMechanism);
970 StringBuilder sb =
new StringBuilder();
971 DateTime TP = Next.Value;
972 DateTime Today = DateTime.Today;
974 if (Next.Value == DateTime.MaxValue)
976 sb.Append(
"This endpoint (");
978 sb.Append(
") has been blocked from the system.");
982 sb.Append(
"Too many failed login attempts in a row registered. Try again after ");
983 sb.Append(TP.ToLongTimeString());
985 if (TP.Date != Today)
987 if (TP.Date == Today.AddDays(1))
988 sb.Append(
" tomorrow");
992 sb.Append(TP.ToShortDateString());
996 sb.Append(
". Remote Endpoint: ");
1010 if (M.
Name ==
this.qlMechanism)
1012 if (!M.
Allowed(
this.GetSslStream()))
1025 if (AuthResult.HasValue)
1027 if (AuthResult.Value)
1035 catch (Exception ex)
1054 catch (AuthenticationException ex)
1056 await this.LoginFailure(ex, RemoteIpEndpoint);
1058 catch (Win32Exception ex)
1060 await this.LoginFailure(ex, RemoteIpEndpoint);
1062 catch (Exception ex)
1079 if (!
string.IsNullOrEmpty(this.qlMechanism))
1083 if (!
string.IsNullOrEmpty(this.qlResource))
1085 FullJid = this.bareJid +
"/" + this.qlResource;
1086 if (!await this.
server.RegisterFullJid(FullJid,
this))
1091 this.fullJid = await this.
server.RegisterBareJid(this.
bareJid,
this);
1096 this.isBound =
true;
1099 this.hasSession =
true;
1104 return await base.SaslSuccess(ProofBase64);
1107 private async Task LoginFailure(Exception ex,
string RemoteIpEndpoint)
1121 await base.SetUserIdentity(
UserName);
1146 this.
ResetState(Authenticated,
string.IsNullOrEmpty(this.qlMechanism));
1156 this.isAuthenticated = Authenticated;
1160 this.inputState = 0;
1161 this.inputDepth = 0;
1165 this.inputState = 5;
1166 this.inputDepth = 1;
1186 bool BlockingBak = this.client.
Client.Client.Blocking;
1189 byte[] Temp =
new byte[1];
1191 this.client.Client.Client.Blocking =
false;
1192 this.client.
Client.Client.Send(Temp, 0, 0);
1196 catch (SocketException e)
1198 if (e.NativeErrorCode.Equals(10035))
1205 this.client.Client.Client.Blocking = BlockingBak;
1220 return this.client.Stream as SslStream;
1231 switch (StanzaElement.LocalName)
1247 this.upgradeToTls =
true;
1268 this.upgradeToTls =
true;
1272 if (!await this.
StreamError(
"<unsupported-stanza-type xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Unsupported stanza: " + StanzaElement.LocalName))
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 string PrettyXml(string Xml)
Reformats XML to make it easier to read.
Static class managing the application event log. Applications and services log events on this static ...
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 Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
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.
CaseInsensitiveString UserName
User name
async Task< bool > ProcessStanza(Stanza Stanza)
Processes an XMPP Stanza.
bool isBound
If user is bound
const string StreamNamespace
http://etherx.jabber.org/streams
CaseInsensitiveString FullJid
Full JID
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.
bool disposed
If connection is disposed
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.
CaseInsensitiveString fullJid
Full JID
XmppServer server
XMPP Server
Task< bool > StreamErrorResourceConstraint()
Sends Stream Error that there's a resource constraint.
CaseInsensitiveString bareJid
Bare JID
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.
Contains information about one XMPP address.
Class managing a connection.
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.
override string Binding
Binding method.
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.
Task< DateTime?> GetEarliestLoginOpportunity(IClientConnection Connection)
Evaluates when a client is allowed to login.
bool IsServerDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the server domain, or optionally, an alternative domain.
X509Certificate ServerCertificate
Server domain certificate.
bool EncryptionRequired
If C2S encryption is requried.
CaseInsensitiveString Domain
Domain name.
Represents a case-insensitive string.
Class that monitors login events, and help applications determine malicious intent....
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.
string Name
Name of the mechanism.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
XmppConnectionState
State of XMPP connection.
ClientCertificates
Client Certificate Options