using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Net.Sockets; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Xml; using System.Globalization; using FastReport.Export; using FastReport.Cloud; using FastReport.Cloud.StorageClient.FastCloud; using FastReport.Messaging.Authentication.Sasl; using FastReport.Utils; namespace FastReport.Messaging.Xmpp { /// /// Represents the XMPP messenger. /// public class XmppMessenger : MessengerBase, IDisposable { #region Constants private const int DEFAULT_PORT_NUMBER = 5222; private const int DEFAULT_CHUNK_SIZE = 4096; private const string DEFAULT_RESOURCE = "frnet"; private const string DEFAULT_SESSION_ID = "frnet_session_0"; #endregion // Constants #region Fields private string username; private string password; private string hostname; private int port; private string sendToUsername; private TcpClient client; private Stream stream; private StreamParser parser; private bool connected; private bool authenticated; private bool disposed; private string sessionId; private string jidFrom; private string jidTo; private int chunkSize; #endregion // Fields #region Properties /// /// Gets or sets the username. /// public string Username { get { return username; } set { username = value; } } /// /// Gets or sets the user's password. /// public string Password { get { return password; } set { password = value; } } /// /// Gets or sets the hostname of XMPP server. /// public string Hostname { get { return hostname; } set { hostname = value; } } /// /// Gets or sets the port number of the XMPP service of the server. /// public int Port { get { return port; } set { port = value; } } /// /// Gets or sets the username to send file to. /// public string SendToUsername { get { return sendToUsername; } set { sendToUsername = value; } } /// /// Gets or sets the JID to send from. /// public string JidFrom { get { return jidFrom; } set { jidFrom = value; if (!String.IsNullOrEmpty(jidFrom)) { int jidFromAtPosition = jidFrom.IndexOf("@"); this.username = jidFrom.Substring(0, jidFromAtPosition); this.hostname = jidFrom.Substring(jidFromAtPosition + 1, jidFrom.Length - jidFromAtPosition - 1); } } } /// /// Gets or set the JID to send to. /// public string JidTo { get { return jidTo; } set { jidTo = value; if (!String.IsNullOrEmpty(jidTo)) { int jidToAtPosition = jidTo.IndexOf("@"); this.sendToUsername = jidTo.Substring(0, jidToAtPosition); } } } #endregion // Properties #region Constructors /// /// Initializes a new instance of the class. /// public XmppMessenger() { username = ""; password = ""; hostname = ""; port = DEFAULT_PORT_NUMBER; sendToUsername = ""; client = null; stream = null; parser = null; connected = false; authenticated = false; disposed = false; sessionId = DEFAULT_SESSION_ID; jidFrom = ""; jidTo = ""; chunkSize = DEFAULT_CHUNK_SIZE; } /// /// Initializes a new instance of the class with specified parameters. /// /// Username. /// Password. /// Hostname. /// Port. /// Username to send file to. /// Send to user's resource. public XmppMessenger(string username, string password, string hostname, int port, string sendToUsername, string sendToResource) { this.username = username; this.password = password; this.hostname = hostname; this.port = port; this.sendToUsername = sendToUsername; client = null; stream = null; parser = null; connected = false; authenticated = false; disposed = false; sessionId = DEFAULT_SESSION_ID; jidFrom = username + "@" + hostname + "/" + DEFAULT_RESOURCE; jidTo = sendToUsername + "@" + hostname + "/" + sendToResource; chunkSize = DEFAULT_CHUNK_SIZE; } /// /// Initializes a new instance of the class with specified parameters. /// /// User's JID without resource. /// User's password. /// JID to send to with resource. public XmppMessenger(string jidFrom, string password, string jidTo) { this.port = DEFAULT_PORT_NUMBER; client = null; stream = null; parser = null; connected = false; authenticated = false; disposed = false; sessionId = DEFAULT_SESSION_ID; if (!String.IsNullOrEmpty(jidFrom)) { jidFrom += "/" + DEFAULT_RESOURCE; } JidFrom = jidFrom; this.password = password; JidTo = jidTo; chunkSize = DEFAULT_CHUNK_SIZE; } #endregion // Constructors #region Private Methods /// /// Sends the specified string to the server. /// /// The string to send. private void SendString(string str) { byte[] buffer = Encoding.UTF8.GetBytes(str); try { stream.Write(buffer, 0, buffer.Length); } catch { connected = false; } } /// /// Initiates the stream to the server. /// /// The hostname. /// The features response of the server. private XmlElement InitiateStream(string hostname) { XmlElement xml = Xml.CreateElement("stream:stream", "jabber:client"); Xml.AddAttribute(xml, "from", jidFrom); Xml.AddAttribute(xml, "to", hostname); Xml.AddAttribute(xml, "version", "1.0"); Xml.AddAttribute(xml, "xml:lang", "en"); Xml.AddAttribute(xml, "xmlns", "jabber:client"); Xml.AddAttribute(xml, "xmlns:stream", "http://etherx.jabber.org/streams"); string str = Xml.ToXmlString(xml, true, true); SendString(str); if (parser != null) { parser.Close(); } parser = new StreamParser(stream, true); return parser.ReadNextElement(new List(new string[] { "stream:features" })); } /// /// Validates the server certificate. /// /// The sender object. /// X509 certificate. /// The X509 chain. /// The SSL policy errors. /// True if successfull. public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (sslPolicyErrors != SslPolicyErrors.None) { return false; } return true; } /// /// Secures the stream by TLS. /// /// The hostname. /// The features response of the server. private XmlElement StartTls(string hostname) { XmlElement xml = Xml.CreateElement("starttls", "urn:ietf:params:xml:ns:xmpp-tls"); SendString(""); XmlElement proceed = parser.ReadNextElement(new List(new string[] { "proceed" })); SslStream sslStream = new SslStream(stream, false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null); sslStream.AuthenticateAsClient(hostname); stream = sslStream; return InitiateStream(hostname); } /// /// Selects the SASL authentication mechanism. /// /// List of mechanisms. /// The string containing mechanism name. private string SelectMechanism(List mechanisms) { bool hasPlain = false; bool hasDigestMd5 = false; foreach (string mechanism in mechanisms) { if (!hasPlain) { hasPlain = mechanism.ToUpper().Contains(PlainMechanism.MECHANISM_NAME); } if (!hasDigestMd5) { hasDigestMd5 = mechanism.ToUpper().Contains(DigestMd5Mechanism.MECHANISM_NAME); } } if (hasDigestMd5) { return DigestMd5Mechanism.MECHANISM_NAME; } else if (hasPlain) { return PlainMechanism.MECHANISM_NAME; } return null; } /// /// Authenticates the user on the server using Plain mechanism. /// private void AuthenticateUsingPlainMechanism() { XmlElement xml = Xml.CreateElement("auth", "urn:ietf:params:xml:ns:xmpp-sasl"); Xml.AddAttribute(xml, "mechanism", PlainMechanism.MECHANISM_NAME); Xml.AddText(xml, new PlainMechanism(username, password).GetResponse("")); SendString(Xml.ToXmlString(xml, false, true)); XmlElement element = parser.ReadNextElement(new List(new string[] { "success", "failure" })); if (element.Name == "success") { authenticated = true; } } /// /// Authenticates the user on the server using Digest-MD5 mechanism. /// private void AuthenticateUsingDigestMd5Mechanism() { XmlElement xml = Xml.CreateElement("auth", "urn:ietf:params:xml:ns:xmpp-sasl"); Xml.AddAttribute(xml, "mechanism", DigestMd5Mechanism.MECHANISM_NAME); SendString(Xml.ToXmlString(xml, false, true)); XmlElement element = parser.ReadNextElement(new List(new string[] { "challenge", "success", "failure" })); if (element.Name == "challenge") { //!! authenticated = true; } } /// /// Authenticates the user on the server. /// /// The SASL mechanisms list. private void Authenticate(List mechanisms) { if (mechanisms.Count > 0) { //string mechanismName = SelectMechanism(mechanisms); string mechanismName = PlainMechanism.MECHANISM_NAME; switch (mechanismName) { case DigestMd5Mechanism.MECHANISM_NAME: AuthenticateUsingDigestMd5Mechanism(); break; default: AuthenticateUsingPlainMechanism(); break; } } } /// /// Setups the connection with the server. /// private void SetupConnection() { XmlElement features = InitiateStream(hostname); //if (features.GetElementsByTagName("starttls").Count > 0) //{ // features = StartTls(hostname); //} XmlNodeList mechanismsNodes = features.GetElementsByTagName("mechanism"); List mechanisms = new List(); foreach (XmlNode node in mechanismsNodes) { mechanisms.Add(node.InnerText); } Authenticate(mechanisms); if (!authenticated && !Config.WebMode) { FRMessageBox.Error(Res.Get("Messaging,Xmpp,Errors,InvalidJidOrPassword")); } } /// /// Binds resource and gets the full JID that will be associated with current session. /// /// The full session JID. private string BindResource() { string jid = null; XmlElement xml = Xml.CreateElement("iq", ""); Xml.AddAttribute(xml, "type", "set"); Xml.AddAttribute(xml, "id", "bind-0"); XmlElement bind = Xml.CreateElement("bind", "urn:ietf:params:xml:ns:xmpp-bind"); XmlElement resource = Xml.CreateElement("resource", ""); Xml.AddText(resource, DEFAULT_RESOURCE); Xml.AddChild(bind, resource); Xml.AddChild(xml, bind); SendString(Xml.ToXmlString(xml, false, true)); XmlElement res = parser.ReadNextElement(new List(new string[] { "iq" })); XmlNodeList jidNode = res.GetElementsByTagName("jid"); if (jidNode.Count > 0) { jid = jidNode[0].InnerText; } return jid; } /// /// Opens session between client and server. /// /// The id of the opened session. private string OpenSession() { SendString(""); XmlElement result = parser.ReadNextElement(new List(new string[] { "iq" })); return result.GetAttribute("id"); ; } /// /// Connects to the server. /// private void Connect() { client = new TcpClient(hostname, port); stream = client.GetStream(); SetupConnection(); if (authenticated) { XmlElement features = InitiateStream(hostname); XmlNodeList bind = features.GetElementsByTagName("bind"); if (bind.Count > 0) { jidFrom = BindResource(); sessionId = OpenSession(); if (!String.IsNullOrEmpty(jidFrom) && !String.IsNullOrEmpty(sessionId)) { connected = true; } } } } /// /// Sends the message. /// /// The text of the message. /// True if message has been successfully sent. private bool SendMessage(string text) { XmlElement data = Xml.CreateElement("body", null); Xml.AddText(data, text); List list = new List(); list.Add(data); Message message = new Message(null, "chat", jidFrom, jidTo, sessionId, new CultureInfo("en"), list); SendString(message.ToString()); return true; } /// /// Sends the presence. /// /// The text of the presence. /// True if presence has been successfully sent. private bool SendPresence(string text) { SendString(""); while (true) { XmlElement resp = parser.ReadNextElement(new List(new string[] { "iq", "presence" })); if (resp == null) break; } return true; } /// /// Initiates the In Band Bytestream for sending the file (XEP-0047). /// /// True if bytestream has been successfully initiated. private bool InitiateInBandBytestream() { XmlElement open = Xml.CreateElement("open", "http://jabber.org/protocol/ibb"); Xml.AddAttribute(open, "block-size", chunkSize.ToString()); Xml.AddAttribute(open, "sid", sessionId); Xml.AddAttribute(open, "stanza", "message"); List list = new List(); list.Add(open); Iq iq = new Iq(null, "set", jidFrom, jidTo, "ibb1", null, list); //Message iq = new Message(null, "set", fullJid, jidTo + "/Miranda", sessionId, null, list); string str = iq.ToString(); //string str2 = ""; SendString(iq.ToString()); XmlElement resp = parser.ReadNextElement(new List(new string[] { "iq" })); //resp = parser.ReadNextElement(new List(new string[] { "iq" })); //resp = parser.ReadNextElement(new List(new string[] { "iq" })); resp = parser.ReadNextElement(new List(new string[] { "iq" })); resp = parser.ReadNextElement(new List(new string[] { "iq" })); while (resp == null) { resp = parser.ReadNextElement(new List(new string[] { "iq" })); resp = parser.ReadNextElement(new List(new string[] { "iq" })); } if (resp.GetElementsByTagName("service-unavailable").Count > 0 || resp.GetElementsByTagName("feature-not-implemented").Count > 0) { return false; } return true; } /// /// Sends the chunk to the XMPP server. /// /// The data of the chunk. /// The number of the chunk. private void SendChunk(byte[] chunk, int number) { XmlElement data = Xml.CreateElement("data", "http://jabber.org/protocol/ibb"); Xml.AddAttribute(data, "seq", number.ToString()); Xml.AddAttribute(data, "sid", sessionId); Xml.AddText(data, Convert.ToBase64String(chunk)); List list = new List(); list.Add(data); //Iq iq = new Iq(null, "set", fullJid, jidTo, sessionId, new CultureInfo("en"), list); Message iq = new Message(null, null, jidFrom, jidTo, sessionId, null, list); SendString(iq.ToString()); //XmlElement resp = parser.ReadNextElement(new List(new string[] { "iq" })); } /// /// Sends the file using In Band Bytestream. /// /// The memory stream containing data of the file. /// True if file has been successfully sent. private bool SendFileUsingIbb(MemoryStream ms) { InitiateInBandBytestream(); try { byte[] chunk = new byte[chunkSize]; int chunksCount = (int)Math.Ceiling((double)ms.Length / chunkSize); ms.Position = 0; for (int i = 0; i < chunksCount - 1; i++) { ms.Read(chunk, 0, chunk.Length); SendChunk(chunk, i); } byte[] lastChunk = new byte[ms.Length - ((chunksCount - 1) * chunkSize)]; ms.Read(lastChunk, 0, lastChunk.Length); SendChunk(lastChunk, chunksCount - 1); } catch { return false; } return true; } /// /// Sends the file using FastReport Cloud as a proxy server. /// /// The report template. /// The export filter to export report before sending. /// True if file has been successfully sent. private bool SendFileUsingFastReportCloud(Report report, ExportBase export) { FastCloudStorageClient client = new FastCloudStorageClient(); if (ProxySettings != null) { client.ProxySettings = new CloudProxySettings(FastReport.Cloud.ProxyType.Http, ProxySettings.Server, ProxySettings.Port, ProxySettings.Username, ProxySettings.Password); switch (ProxySettings.ProxyType) { case ProxyType.Socks4: client.ProxySettings.ProxyType = FastReport.Cloud.ProxyType.Socks4; break; case ProxyType.Socks5: client.ProxySettings.ProxyType = FastReport.Cloud.ProxyType.Socks5; break; default: client.ProxySettings.ProxyType = FastReport.Cloud.ProxyType.Http; break; } } client.AccessToken = "sozTWBzEm9toiTB5J2MA"; bool saved = false; try { // заменить null на export когда появится поддержка других форматов в облаке client.SaveReport(report, null); saved = true; } catch { } if (saved) { SendMessage(client.ReportUrl); } else { if (!Config.WebMode) { FRMessageBox.Error(Res.Get("Messaging,Xmpp,Errors,UnableToUploadFile")); } return false; } return true; } /// /// Disconnects from the server. /// private void Disconnect() { if (connected) { SendString(""); connected = false; authenticated = false; } } #endregion // Private Methods #region Protected Methods /// protected override bool Authorize() { Connect(); return connected; } #endregion // Protected Methods #region Public Methods /// public override bool SendReport(Report report, ExportBase export) { bool result = false; Authorize(); if (connected) { //using (MemoryStream ms = PrepareToSave(report, export)) //{ // result = SendFileUsingIbb(ms); //} result = SendFileUsingFastReportCloud(report, export); Disconnect(); } return result; } /// /// Closes the connection. /// public void Close() { if (connected) { Disconnect(); } if (!disposed) { Dispose(); } } /// /// Releases all the resources used by the XMPP messenger. /// public void Dispose() { if (!disposed) { if (parser != null) { parser.Close(); parser = null; } if (client != null) { client.Close(); client = null; } disposed = true; } } #endregion // Public Methods } }