3using System.ComponentModel;
7using System.Security.Authentication;
8using System.Security.Cryptography.X509Certificates;
10using System.Threading.Tasks;
28 private const int MaxFragmentSize = 40000000;
31 private readonly StringBuilder fragment =
new StringBuilder();
32 private int fragmentLength = 0;
33 private int inputState = 0;
34 private int inputDepth = 0;
35 private int contentStart = 0;
36 private int contentEnd = 0;
37 private string streamId;
38 private string streamHeader;
39 private string streamFooter;
40 private string language;
41 private double version;
42 private bool openBracketReceived =
false;
43 private bool upgradeToTls =
false;
44 private string qlMechanism =
null;
45 private string qlChallenge =
null;
46 private string qlResource =
null;
60 this.client.OnDisconnected += this.Client_OnDisconnected;
61 this.client.OnError += this.Client_OnError;
62 this.client.OnReceived += this.Client_OnReceived;
63 this.client.OnPaused += this.Client_OnPaused;
64 this.client.OnSent += this.Client_OnSent;
65 this.client.OnInformation += this.Client_OnInformation;
66 this.client.OnWarning += this.Client_OnWarning;
72 public override string Binding =>
"Socket";
108 this.isBound =
false;
109 await this.ProcessFragment(
"<presence type=\"unavailable\"/>", 0, 0);
110 this.
server?.ConnectionClosed(
this);
114 if (!(Sniffers is
null) && !(this.
server is
null))
115 await this.
server.CacheSniffers(Sniffers);
117 if (!(this.client is
null))
123 await base.DisposeAsync();
132 private Task<bool> Client_OnSent(
object Sender,
string Text)
134 this.
server?.DataTransmitted(this.client?.LastTransmittedBytes ?? 0);
136 return Task.FromResult(
true);
139 private string Client_OnWarning(
string Text)
145 private string Client_OnInformation(
string Text)
151 private async Task<bool> Client_OnReceived(
object Sender,
string Text)
155 this.
server?.DataReceived(this.client?.LastReceivedBytes ?? 0);
157 if (this.openBracketReceived)
159 this.openBracketReceived =
false;
162 else if (Text ==
"<")
163 this.openBracketReceived =
true;
167 return await this.ParseIncoming(Text);
172 await this.Client_OnError(
this, ex);
178 private async Task Client_OnError(
object Sender, Exception Exception)
181 this.
Error(Exception.Message);
185 private async Task Client_OnDisconnected(
object Sender, EventArgs e)
191 private async Task<bool> ParseIncoming(
string s)
195 foreach (
char ch
in s)
197 switch (this.inputState)
202 this.fragment.Append(ch);
203 if (++this.fragmentLength > MaxFragmentSize)
219 this.fragment.Append(ch);
220 if (++this.fragmentLength > MaxFragmentSize)
232 if (!await this.ProcessStream(this.fragment.ToString()))
235 this.fragment.Clear();
236 this.fragmentLength = this.contentStart = this.contentEnd = 0;
241 this.fragment.Append(ch);
242 if (++this.fragmentLength > MaxFragmentSize)
252 this.fragment.Append(ch);
253 if (++this.fragmentLength > MaxFragmentSize)
268 this.fragment.Append(ch);
269 if (++this.fragmentLength > MaxFragmentSize)
278 if (!await this.ProcessStream(this.fragment.ToString()))
281 this.fragment.Clear();
282 this.fragmentLength = this.contentStart = this.contentEnd = 0;
289 this.fragment.Append(ch);
290 if (++this.fragmentLength > MaxFragmentSize)
298 else if (this.inputDepth > 1)
300 this.fragment.Append(ch);
301 if (++this.fragmentLength > MaxFragmentSize)
315 this.fragment.Append(ch);
316 if (++this.fragmentLength > MaxFragmentSize)
323 if (this.inputDepth == 2)
324 this.contentEnd = this.fragmentLength - 2;
329 this.inputState = 13;
331 this.inputState += 2;
335 this.fragment.Append(ch);
336 if (++this.fragmentLength > MaxFragmentSize)
344 if (this.inputDepth < 1)
351 if (this.inputDepth == 1)
353 if (!await this.ProcessFragment(this.fragment.ToString(),
this.contentStart,
this.contentEnd -
this.contentStart))
356 this.fragment.Clear();
357 this.fragmentLength = this.contentStart = this.contentEnd = 0;
360 if (this.inputState > 0)
367 this.fragment.Append(ch);
368 if (++this.fragmentLength > MaxFragmentSize)
375 if (this.inputDepth == 1)
376 this.contentStart = this.fragmentLength;
384 this.inputState += 2;
388 this.fragment.Append(ch);
389 if (++this.fragmentLength > MaxFragmentSize)
396 if (this.inputDepth == 1)
398 if (!await this.ProcessFragment(this.fragment.ToString(), 0, 0))
401 this.fragment.Clear();
402 this.fragmentLength = this.contentStart = this.contentEnd = 0;
405 if (this.inputState != 0)
413 this.fragment.Append(ch);
414 if (++this.fragmentLength > MaxFragmentSize)
421 if (this.inputDepth == 1)
422 this.contentStart = this.fragmentLength;
432 this.inputState += 2;
436 this.fragment.Append(ch);
437 if (++this.fragmentLength > MaxFragmentSize)
447 this.fragment.Append(ch);
448 if (++this.fragmentLength > MaxFragmentSize)
454 this.inputState -= 2;
458 this.fragment.Append(ch);
459 if (++this.fragmentLength > MaxFragmentSize)
467 this.inputState = 18;
476 this.fragment.Append(ch);
477 if (++this.fragmentLength > MaxFragmentSize)
492 this.fragment.Append(ch);
493 if (++this.fragmentLength > MaxFragmentSize)
503 this.fragment.Append(ch);
504 if (++this.fragmentLength > MaxFragmentSize)
516 this.fragment.Append(ch);
517 if (++this.fragmentLength > MaxFragmentSize)
525 this.inputState -= 2;
529 this.fragment.Append(ch);
530 if (++this.fragmentLength > MaxFragmentSize)
545 this.fragment.Append(ch);
546 if (++this.fragmentLength > MaxFragmentSize)
561 this.fragment.Append(ch);
562 if (++this.fragmentLength > MaxFragmentSize)
577 this.fragment.Append(ch);
578 if (++this.fragmentLength > MaxFragmentSize)
593 this.fragment.Append(ch);
594 if (++this.fragmentLength > MaxFragmentSize)
609 this.fragment.Append(ch);
610 if (++this.fragmentLength > MaxFragmentSize)
625 this.fragment.Append(ch);
626 if (++this.fragmentLength > MaxFragmentSize)
636 this.fragment.Append(ch);
637 if (++this.fragmentLength > MaxFragmentSize)
649 this.fragment.Append(ch);
650 if (++this.fragmentLength > MaxFragmentSize)
658 this.inputState -= 2;
676 public override Task<bool>
StreamError(
string ErrorXml,
string Reason)
678 return this.ToError(
"<stream:error>" + ErrorXml +
"</stream:error>", Reason);
681 private async Task<bool> ToError(
string ErrorXml,
string Reason)
683 if (
string.IsNullOrEmpty(ErrorXml))
685 this.inputState = -1;
687 await this.Client_OnError(
this,
new Exception(Reason));
693 return await this.
BeginWrite(ErrorXml + this.streamFooter, async (Sender, e) =>
695 this.inputState = -1;
703 private async Task<bool> ProcessStream(
string Xml)
705 StringBuilder ToSend =
new StringBuilder();
709 int i = Xml.IndexOf(
"?>");
711 Xml = Xml[(i + 2)..].TrimStart();
713 this.streamHeader = Xml;
715 i = Xml.IndexOf(
":stream");
717 this.streamFooter =
"</stream>";
719 this.streamFooter =
"</" + Xml[1..i] +
":stream>";
721 XmlDocument Doc =
XML.
ParseXml(Xml + this.streamFooter,
true);
723 XmlElement Stream = Doc.DocumentElement;
728 this.language =
XML.
Attribute(Stream,
"xml:lang",
"en");
730 this.bareAddress =
new XmppAddress(this.
bareJid);
732 if (
string.IsNullOrEmpty(this.streamId))
739 ToSend.Append(
"<?xml version='1.0' encoding='utf-8'?>");
740 ToSend.Append(
"<stream:stream from='");
742 ToSend.Append(
"' version='1.0' xml:lang='");
743 ToSend.Append(
XML.
Encode(
this.language));
744 ToSend.Append(
"' id='");
745 ToSend.Append(this.streamId);
746 ToSend.Append(
"' xmlns='jabber:client' xmlns:stream='");
757 if (Doc.DocumentElement.Prefix !=
"stream")
760 await this.
StreamError(
"<bad-namespace-prefix xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Bad namespace prefix.");
764 if (Doc.DocumentElement.LocalName !=
"stream")
767 await this.
StreamError(
"<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Bad format.");
771 if (!this.
server.
IsServerDomain(
this.domain,
true) && (!
string.IsNullOrEmpty(
this.server.Domain) || !IPAddress.TryParse(
this.domain, out IPAddress
_)))
774 await this.
StreamError(
"<host-unknown xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Domain unknown.");
778 if (this.version != 1.0)
781 await this.ToError(
"<unsupported-version xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Unsupported version.");
789 ToSend.Append(
"<stream:features>");
791 this.qlMechanism =
null;
792 this.qlChallenge =
null;
793 this.qlResource =
null;
797 ToSend.Append(
"<ql xmlns='");
799 ToSend.Append(
"'/>");
804 ToSend.Append(
"<starttls xmlns='");
809 ToSend.Append(
"><required/></starttls>");
817 ToSend.Append(
"<mechanisms xmlns='" + XmppServer.SaslNamespace +
"'>");
819 SslStream SslStream = this.client.Stream as SslStream;
822 if (Mechanism.
Allowed(SslStream))
824 ToSend.Append(
"<mechanism>");
825 ToSend.Append(Mechanism.
Name);
826 ToSend.Append(
"</mechanism>");
830 ToSend.Append(
"</mechanisms>");
832 if (await this.
server.CanRegister(
this))
833 ToSend.Append(
"<register xmlns='http://jabber.org/features/iq-register'/>");
839 ToSend.Append(
"<bind xmlns='");
841 ToSend.Append(
"'/>");
842 ToSend.Append(
"<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
845 ToSend.Append(
"</stream:features>");
852 StringBuilder Msg =
new StringBuilder();
854 Msg.Append(
"Incoming XMPP stream rejected: ");
855 Msg.AppendLine(ex.Message);
857 if (!
string.IsNullOrEmpty(Xml))
860 if (Xml.Length > 1000)
861 Xml = Xml[..1000] +
"...";
864 Msg.AppendLine(
"```xml");
866 Msg.AppendLine(
"```");
869 string s = Msg.ToString();
871 Log.
Warning(s,
new KeyValuePair<string, object>(
"RemoteEP", this.RemoteEndPoint));
876 await this.
StreamError(
"<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>",
"Bad format.");
887 public override Task<bool>
BeginWrite(
string Xml, EventHandlerAsync<DeliveryEventArgs> Callback,
object State)
889 return this.client?.
SendAsync(Xml, Callback,
State) ?? Task.FromResult(
false);
892 private Task<bool>
BeginWrite(StringBuilder ToSend)
894 string Xml = ToSend.ToString();
895 if (
string.IsNullOrEmpty(Xml))
896 return Task.FromResult(
true);
903 private async Task<bool> ProcessFragment(
string Xml,
int ContentStart,
int ContentLen)
913 if (!
string.IsNullOrEmpty(this.
fullJid))
916 Doc =
XML.
ParseXml(this.streamHeader + Xml + this.streamFooter,
true);
918 Stanza =
new Stanza(Doc.DocumentElement, Xml, ContentStart, ContentLen);
926 if (Xml.Length < 100)
929 Content = Xml[..100] +
"...";
932 new KeyValuePair<string, object>(
"ContentLength", Xml.Length),
933 new KeyValuePair<string, object>(
"Content", Content));
936 await this.
StreamError(
"<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", ex.Message);
941 private async Task Client_OnPaused(
object Sender, EventArgs e)
943 if (this.upgradeToTls)
945 this.upgradeToTls =
false;
960 bool QuickLogin = !
string.IsNullOrEmpty(this.qlMechanism);
971 StringBuilder sb =
new StringBuilder();
972 DateTime TP = Next.Value;
973 DateTime Today = DateTime.Today;
975 if (Next.Value == DateTime.MaxValue)
977 sb.Append(
"This endpoint (");
978 sb.Append(this.RemoteEndPoint);
979 sb.Append(
") has been blocked from the system.");
983 sb.Append(
"Too many failed login attempts in a row registered. Try again after ");
984 sb.Append(TP.ToLongTimeString());
986 if (TP.Date != Today)
988 if (TP.Date == Today.AddDays(1))
989 sb.Append(
" tomorrow");
993 sb.Append(TP.ToShortDateString());
997 sb.Append(
". Remote Endpoint: ");
998 sb.Append(this.RemoteEndPoint);
1011 if (M.
Name ==
this.qlMechanism)
1013 if (!M.
Allowed(
this.GetSslStream()))
1026 if (AuthResult.HasValue)
1028 if (AuthResult.Value)
1036 catch (Exception ex)
1055 catch (AuthenticationException ex)
1059 catch (Win32Exception ex)
1063 catch (Exception ex)
1080 if (!
string.IsNullOrEmpty(this.qlMechanism))
1084 if (!
string.IsNullOrEmpty(this.qlResource))
1086 FullJid = this.bareJid +
"/" + this.qlResource;
1087 if (!await this.
server.RegisterFullJid(FullJid,
this))
1092 this.fullJid = await this.
server.RegisterBareJid(this.
bareJid,
this);
1097 this.isBound =
true;
1100 this.hasSession =
true;
1105 return await base.SaslSuccess(ProofBase64);
1108 private async Task LoginFailure(Exception ex,
string RemoteIpEndpoint)
1147 this.
ResetState(Authenticated,
string.IsNullOrEmpty(this.qlMechanism));
1157 this.isAuthenticated = Authenticated;
1161 this.inputState = 0;
1162 this.inputDepth = 0;
1166 this.inputState = 5;
1167 this.inputDepth = 1;
1187 bool BlockingBak = this.client.
Client.Client.Blocking;
1190 byte[] Temp =
new byte[1];
1192 this.client.Client.Client.Blocking =
false;
1193 this.client.
Client.Client.Send(Temp, 0, 0);
1197 catch (SocketException e)
1199 if (e.NativeErrorCode.Equals(10035))
1206 this.client.Client.Client.Blocking = BlockingBak;
1221 return this.client.Stream as SslStream;
1232 switch (StanzaElement.LocalName)
1248 this.upgradeToTls =
true;
1269 this.upgradeToTls =
true;
1273 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 XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
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.
string RemoteEndPoint
Remote End-point of connection. This corresponds to the IP Endpoint of the remote party in normal cas...
void Continue()
Continues reading from the socket, if paused in an event handler.
virtual Task DisposeAsync()
Disposes of the object asynchronously. The underlying TcpClient is either disposed directly,...
X509Certificate RemoteCertificate
Certificate used by the remote endpoint.
Task UpgradeToTlsAsServer(X509Certificate ServerCertificate)
Upgrades a server connection to TLS.
bool IsEncrypted
If connection is encrypted or not.
void TransmitText(string Text)
Called when text has been transmitted.
virtual bool Remove(ISniffer Sniffer)
ICommunicationLayer.Remove
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
void ReceiveText(string Text)
Called when text has been received.
ISniffer[] Sniffers
Registered sniffers.
void Error(string Error)
Called to inform the viewer of an error state.
void Warning(string Warning)
Called to inform the viewer of a warning state.
virtual void Add(ISniffer Sniffer)
ICommunicationLayer.Add
void Information(string Comment)
Called to inform the viewer of something.
Sniffer that stores events in memory.
void Replay(CommunicationLayer ComLayer)
Replays sniffer events.
Implements a text-based TCP Client, by using the thread-safe full-duplex BinaryTcpClient.
virtual Task< bool > SendAsync(string Text)
Sends a text packet.
Abstract base class for XMPP client connections
const string TlsNamespace
urn:ietf:params:xml:ns:xmpp-tls
const string QuickLoginNamespace
http://waher.se/Schema/QL.xsd
virtual void SetMechanism(IAuthenticationMechanism Mechanism)
Sets the authentication mechanism for the connection.
bool isAuthenticated
If user is authenticated
XmppConnectionState State
Current state of connection.
Task< bool > SaslErrorMechanismTooWeak()
Sends SASL Error that mechanism is too waek.
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.
override void SetUserIdentity(CaseInsensitiveString UserName)
Sets the authenticate user's identity.
XmppClientConnection(TextTcpClient Client, XmppServer Server, params ISniffer[] Sniffers)
Class managing a connection.
override Task< bool > StreamError(string ErrorXml, string Reason)
Sends a Stream Error.
override SslStream GetSslStream()
Gets underlying SSL-stream
override async Task< bool > SaslSuccess(string ProofBase64)
Returns a sucess response to the client.
void ResetState(bool Authenticated, bool ExpectStream)
Resets the state machine.
override bool CheckLive()
Checks if the connection is live.
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 RemoteEndPoint
Remote endpoint
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.
IXmppServerPersistenceLayer PersistenceLayer
Reference to persistence layer
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.
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 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