using System; using System.Collections.Generic; using System.Linq; using Duplicati.Library.Interface; using Duplicati.Library.Logging; using System.Net.NetworkInformation; using Duplicati.Library.Modules.Builtin.ResultSerialization; namespace Duplicati.Library.Modules.Builtin { public class SendJabberMessage : ReportHelper { /// /// The tag used for log messages /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); #region Option names /// /// Option used to specify server username /// private const string OPTION_USERNAME = "send-xmpp-username"; /// /// Option used to specify server password /// private const string OPTION_PASSWORD = "send-xmpp-password"; /// /// Option used to specify recipient(s) /// private const string OPTION_RECIPIENT = "send-xmpp-to"; /// /// Option used to specify report body /// private const string OPTION_MESSAGE = "send-xmpp-message"; /// /// Option used to specify report level /// private const string OPTION_SENDLEVEL = "send-xmpp-level"; /// /// Option used to specify if reports are sent for other operations than backups /// private const string OPTION_SENDALL = "send-xmpp-any-operation"; /// /// Option used to specify what format the result is sent in. /// private const string OPTION_RESULT_FORMAT = "send-xmpp-result-output-format"; /// /// Option used to set the log level /// private const string OPTION_LOG_LEVEL = "send-xmpp-log-level"; /// /// Option used to set the log level /// private const string OPTION_LOG_FILTER = "send-xmpp-log-filter"; /// /// Option used to set the maximum number of log lines /// private const string OPTION_MAX_LOG_LINES = "send-xmpp-max-log-lines"; #endregion #region Option defaults /// /// The default message body /// protected override string DEFAULT_BODY => string.Format("Duplicati %OPERATIONNAME% report for %backup-name%{0}{0}%RESULT%", Environment.NewLine); /// /// Don't use the subject for XMPP /// protected override string DEFAULT_SUBJECT => string.Empty; #endregion #region Private variables /// /// The server username /// private string m_username; /// /// The server password /// private string m_password; /// /// The XMPP recipient /// private string m_to; #endregion #region Implementation of IGenericModule /// /// The module key, used to activate or deactivate the module on the commandline /// public override string Key { get { return "sendxmpp"; } } /// /// A localized string describing the module with a friendly name /// public override string DisplayName { get { return Strings.SendJabberMessage.DisplayName;} } /// /// A localized description of the module /// public override string Description { get { return Strings.SendJabberMessage.Description; } } /// /// A boolean value that indicates if the module should always be loaded. /// If true, the user can choose to not load the module by entering the appropriate commandline option. /// If false, the user can choose to load the module by entering the appropriate commandline option. /// public override bool LoadAsDefault { get { return true; } } /// /// Gets a list of supported commandline arguments /// public override IList SupportedCommands { get { return new List(new ICommandLineArgument[] { new CommandLineArgument(OPTION_RECIPIENT, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpptoShort, Strings.SendJabberMessage.SendxmpptoLong), new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppmessageShort, Strings.SendJabberMessage.SendxmppmessageLong, DEFAULT_BODY), new CommandLineArgument(OPTION_USERNAME, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppusernameShort, Strings.SendJabberMessage.SendxmppusernameLong), new CommandLineArgument(OPTION_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpppasswordShort, Strings.SendJabberMessage.SendxmpppasswordLong), new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.SendJabberMessage.SendxmpplevelShort, Strings.SendJabberMessage.SendxmpplevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendJabberMessage.SendxmppanyoperationShort, Strings.SendJabberMessage.SendxmppanyoperationLong), new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevellShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), }); } } protected override string SubjectOptionName => OPTION_MESSAGE; protected override string BodyOptionName => OPTION_MESSAGE; protected override string ActionLevelOptionName => OPTION_SENDLEVEL; protected override string ActionOnAnyOperationOptionName => OPTION_SENDALL; protected override string LogLevelOptionName => OPTION_LOG_LEVEL; protected override string LogFilterOptionName => OPTION_LOG_FILTER; protected override string LogLinesOptionName => OPTION_MAX_LOG_LINES; protected override string ResultFormatOptionName => OPTION_RESULT_FORMAT; /// /// This method is the interception where the module can interact with the execution environment and modify the settings. /// /// A set of commandline options passed to Duplicati protected override bool ConfigureModule(IDictionary commandlineOptions) { //We need at least a recipient commandlineOptions.TryGetValue(OPTION_RECIPIENT, out m_to); if (string.IsNullOrEmpty(m_to)) return false; commandlineOptions.TryGetValue(OPTION_USERNAME, out m_username); commandlineOptions.TryGetValue(OPTION_PASSWORD, out m_password); return true; } #endregion protected override string ReplaceTemplate(string input, object result, bool subjectline) { // No need to do the expansion as we throw away the result if (subjectline) return string.Empty; return base.ReplaceTemplate(input, result, subjectline); } protected override void SendMessage(string subject, string body) { Exception ex = null; var waitEvent = new System.Threading.ManualResetEvent(false); var uri = new Library.Utility.Uri(m_username.Contains("://") ? m_username : "http://" + m_username); var con = new agsXMPP.XmppClientConnection(uri.Host, uri.Port == -1 ? (uri.Scheme == "https" ? 5223 :5222) : uri.Port); if (uri.Scheme == "https") con.UseSSL = true; var resource = uri.Path ?? ""; if (resource.StartsWith("/", StringComparison.Ordinal)) resource = resource.Substring(1); if (string.IsNullOrWhiteSpace(resource)) resource = "Duplicati"; agsXMPP.ObjectHandler loginDelegate = (sender) => { try { foreach(var recipient in m_to.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries)) con.Send(new agsXMPP.protocol.client.Message(recipient, agsXMPP.protocol.client.MessageType.chat, body)); } catch (Exception e) { Logging.Log.WriteWarningMessage(LOGTAG, "XMPPSendError", e, "Failed to send to XMPP messages: {0}", e.Message); ex = e; } finally { waitEvent.Set(); } }; agsXMPP.ErrorHandler errorHandler = (sender, e) => { Logging.Log.WriteWarningMessage(LOGTAG, "XMPPError", e, "An error ocurred in XMPP: {0}", e.Message); ex = e; waitEvent.Set(); }; agsXMPP.XmppElementHandler loginErroHandler = (sender, e) => { Logging.Log.WriteWarningMessage(LOGTAG, "XMPPLoginError", null, "Failed to login to XMPP: {0}", e); ex = new Exception(string.Format("Failed to log in: {0}", e)); waitEvent.Set(); }; con.OnLogin += loginDelegate; con.OnError += errorHandler; con.OnAuthError += loginErroHandler; //con.OnBinded += (sender) => {Console.WriteLine("Binded: {0}", sender);}; //con.OnIq += (sender, iq) => {Console.WriteLine("Iq: {0}", iq);}; //con.OnReadXml += (sender, xml) => {Console.WriteLine("ReadXml: {0}", xml);}; //con.OnWriteXml += (sender, xml) => {Console.WriteLine("writeXml: {0}", xml);};; con.Open(uri.Username, string.IsNullOrWhiteSpace(m_password) ? uri.Password : m_password, resource); var timeout = !waitEvent.WaitOne(TimeSpan.FromSeconds(30), true); con.OnLogin -= loginDelegate; con.OnError -= errorHandler; con.OnAuthError -= loginErroHandler; try { con.Close(); } catch (Exception lex) { Logging.Log.WriteExplicitMessage(LOGTAG, "CloseConnectionError", lex, "Failed to close XMPP connection: {0}", lex.Message); } if (ex != null) throw ex; if (timeout) throw new TimeoutException(Strings.SendJabberMessage.LoginTimeoutError); } } }