diff --git a/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs b/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs index f4f7c7cbe3..ab5690e473 100644 --- a/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs @@ -46,6 +46,12 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "EmailModule")] public class EmailModule : ISharedRegionModule, IEmailModule { + public class throttleControlInfo + { + public double lastTime; + public double count; + } + // // Log // @@ -56,21 +62,43 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules // private string m_HostName = string.Empty; private bool SMTP_SERVER_TLS = false; - private string SMTP_SERVER_HOSTNAME = string.Empty; + private string SMTP_SERVER_HOSTNAME = null; private int SMTP_SERVER_PORT = 25; private MailboxAddress SMTP_MAIL_FROM = null; - private string SMTP_SERVER_LOGIN = string.Empty; - private string SMTP_SERVER_PASSWORD = string.Empty; + private string SMTP_SERVER_LOGIN = null; + private string SMTP_SERVER_PASSWORD = null; + + private bool m_enableEmailToExternalObjects = true; + private bool m_enableEmailToSMTP = true; private ParserOptions m_mailParseOptions; private int m_MaxQueueSize = 50; // maximum size of an object mail queue private Dictionary> m_MailQueues = new Dictionary>(); private Dictionary m_LastGetEmailCall = new Dictionary(); + private Dictionary m_ownerThrottles = new Dictionary(); + private Dictionary m_primAddressThrottles = new Dictionary(); + private Dictionary m_SMPTAddressThrottles = new Dictionary(); private double m_QueueTimeout = 30 * 60; // 15min; private double m_nextQueuesExpire; + private double m_nextOwnerThrottlesExpire; + private double m_nextPrimAddressThrottlesExpire; + private double m_nextSMTPAddressThrottlesExpire; private string m_InterObjectHostname = "lsl.opensim.local"; + private int m_SMTP_MailsPerDay = 100; + private double m_SMTP_MailsRate = 100.0 / 86400.0; + private double m_SMTPLastTime; + private double m_SMTPCount; + + private int m_MailsToPrimAddressPerHour = 50; + private double m_MailsToPrimAddressRate = 50.0 / 3600.0; + private int m_MailsToSMTPAddressPerHour = 10; + private double m_MailsToSMTPAddressRate = 20.0 / 3600.0; + + private int m_MailsFromOwnerPerHour = 500; + private double m_MailsFromOwnerRate = 500.0 / 3600.0; + private int m_MaxEmailSize = 4096; // largest email allowed by default, as per lsl docs. private static SslPolicyErrors m_SMTP_SslPolicyErrorsMask; @@ -85,6 +113,7 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules #region ISharedRegionModule + public void Initialise(IConfigSource config) { IConfig startupConfig = config.Configs["Startup"]; @@ -101,35 +130,67 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules if (SMTPConfig == null) return; - if (!SMTPConfig.GetBoolean("enabled", false)) + if(!SMTPConfig.GetBoolean("enabled", false)) return; + m_enableEmailToExternalObjects = SMTPConfig.GetBoolean("enableEmailToExternalObjects", m_enableEmailToExternalObjects); + m_enableEmailToSMTP = SMTPConfig.GetBoolean("enableEmailToSMTP", m_enableEmailToSMTP); + + m_MailsToPrimAddressPerHour = SMTPConfig.GetInt("MailsToPrimAddressPerHour", m_MailsToPrimAddressPerHour); + m_MailsToPrimAddressRate = m_MailsToPrimAddressPerHour / 3600.0; + m_MailsFromOwnerPerHour = SMTPConfig.GetInt("MailsFromOwnerPerHour", m_MailsFromOwnerPerHour); + m_MailsFromOwnerRate = m_MailsFromOwnerPerHour / 3600.0; + m_mailParseOptions = new ParserOptions() { AllowAddressesWithoutDomain = false, }; - m_HostName = SMTPConfig.GetString("host_domain_header_from", m_HostName); m_InterObjectHostname = SMTPConfig.GetString("internal_object_host", m_InterObjectHostname); - SMTP_SERVER_TLS = SMTPConfig.GetBoolean("SMTP_SERVER_TLS", SMTP_SERVER_TLS); - SMTP_SERVER_HOSTNAME = SMTPConfig.GetString("SMTP_SERVER_HOSTNAME", SMTP_SERVER_HOSTNAME); - OSHHTPHost hosttmp = new OSHHTPHost(SMTP_SERVER_HOSTNAME, true); - if(!hosttmp.IsResolvedHost) + m_checkSpecName = !m_InterObjectHostname.Equals("lsl.secondlife.com"); + + if (m_enableEmailToSMTP) { - m_log.ErrorFormat("[EMAIL]: could not resolve SMTP_SERVER_HOSTNAME {0}", SMTP_SERVER_HOSTNAME); - return; + m_SMTP_MailsPerDay = SMTPConfig.GetInt("SMTP_MailsPerDay", m_SMTP_MailsPerDay); + m_SMTP_MailsRate = m_SMTP_MailsPerDay / 86400.0; + m_MailsToSMTPAddressPerHour = SMTPConfig.GetInt("MailsToSMTPAddressPerHour", m_MailsToPrimAddressPerHour); + m_MailsToSMTPAddressRate = m_MailsToPrimAddressPerHour / 3600.0; + + SMTP_SERVER_HOSTNAME = SMTPConfig.GetString("SMTP_SERVER_HOSTNAME", SMTP_SERVER_HOSTNAME); + OSHHTPHost hosttmp = new OSHHTPHost(SMTP_SERVER_HOSTNAME, true); + if(!hosttmp.IsResolvedHost) + { + m_log.ErrorFormat("[EMAIL]: could not resolve SMTP_SERVER_HOSTNAME {0}", SMTP_SERVER_HOSTNAME); + return; + } + + SMTP_SERVER_PORT = SMTPConfig.GetInt("SMTP_SERVER_PORT", SMTP_SERVER_PORT); + SMTP_SERVER_TLS = SMTPConfig.GetBoolean("SMTP_SERVER_TLS", SMTP_SERVER_TLS); + + string smtpfrom = SMTPConfig.GetString("SMTP_SERVER_FROM", string.Empty); + m_HostName = SMTPConfig.GetString("host_domain_header_from", m_HostName); + if (!string.IsNullOrEmpty(smtpfrom) && !MailboxAddress.TryParse(m_mailParseOptions, smtpfrom, out SMTP_MAIL_FROM)) + { + m_log.ErrorFormat("[EMAIL]: Invalid SMTP_SERVER_FROM {0}", smtpfrom); + return; + } + + SMTP_SERVER_LOGIN = SMTPConfig.GetString("SMTP_SERVER_LOGIN", SMTP_SERVER_LOGIN); + SMTP_SERVER_PASSWORD = SMTPConfig.GetString("SMTP_SERVER_PASSWORD", SMTP_SERVER_PASSWORD); + + bool VerifyCertChain = SMTPConfig.GetBoolean("SMTP_VerifyCertChain", true); + bool VerifyCertNames = SMTPConfig.GetBoolean("SMTP_VerifyCertNames", true); + m_SMTP_SslPolicyErrorsMask = VerifyCertChain ? 0 : SslPolicyErrors.RemoteCertificateChainErrors; + if (!VerifyCertNames) + m_SMTP_SslPolicyErrorsMask |= SslPolicyErrors.RemoteCertificateNameMismatch; + m_SMTP_SslPolicyErrorsMask = ~m_SMTP_SslPolicyErrorsMask; + } + else + { + m_SMTP_SslPolicyErrorsMask = ~SslPolicyErrors.None; + m_log.Warn("[EMAIL]: SMTP disabled, set enableEmailSMTP to enable"); } - SMTP_SERVER_PORT = SMTPConfig.GetInt("SMTP_SERVER_PORT", SMTP_SERVER_PORT); - string smtpfrom = SMTPConfig.GetString("SMTP_SERVER_FROM", string.Empty); - if(!string.IsNullOrEmpty(smtpfrom) && !MailboxAddress.TryParse(m_mailParseOptions, smtpfrom, out SMTP_MAIL_FROM)) - { - m_log.ErrorFormat("[EMAIL]: Invalid SMTP_SERVER_FROM {0}", smtpfrom); - return; - } - - SMTP_SERVER_LOGIN = SMTPConfig.GetString("SMTP_SERVER_LOGIN", SMTP_SERVER_LOGIN); - SMTP_SERVER_PASSWORD = SMTPConfig.GetString("SMTP_SERVER_PASSWORD", SMTP_SERVER_PASSWORD); m_MaxEmailSize = SMTPConfig.GetInt("email_max_size", m_MaxEmailSize); if(m_MaxEmailSize < 256 || m_MaxEmailSize > 1000000) { @@ -137,14 +198,6 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules m_MaxEmailSize = 4096; } - bool VerifyCertChain = SMTPConfig.GetBoolean("SMTP_VerifyCertChain", true); - bool VerifyCertNames = SMTPConfig.GetBoolean("SMTP_VerifyCertNames", true); - m_SMTP_SslPolicyErrorsMask = VerifyCertChain ? 0 : SslPolicyErrors.RemoteCertificateChainErrors; - if(!VerifyCertNames) - m_SMTP_SslPolicyErrorsMask |= SslPolicyErrors.RemoteCertificateNameMismatch; - m_SMTP_SslPolicyErrorsMask = ~m_SMTP_SslPolicyErrorsMask; - - m_checkSpecName = !m_InterObjectHostname.Equals("lsl.secondlife.com"); } catch (Exception e) { @@ -152,9 +205,17 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules return; } - m_nextQueuesExpire = Util.GetTimeStamp() + m_QueueTimeout; + double now = Util.GetTimeStamp(); + m_nextQueuesExpire = now + m_QueueTimeout; + m_nextOwnerThrottlesExpire = now + 3600; + m_nextPrimAddressThrottlesExpire = now + 3600; + m_nextSMTPAddressThrottlesExpire = now + 3600; + + m_SMTPLastTime = now; + m_SMTPCount = m_SMTP_MailsPerDay; + m_Enabled = true; - } + } public void AddRegion(Scene scene) { @@ -204,24 +265,30 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules public void AddPartMailBox(UUID objectID) { - lock (m_queuesLock) + if (m_Enabled) { - if (!m_MailQueues.TryGetValue(objectID, out List elist)) + lock (m_queuesLock) { - m_MailQueues[objectID] = null; - //TODO external global + if (!m_MailQueues.TryGetValue(objectID, out List elist)) + { + m_MailQueues[objectID] = null; + //TODO external global + } } } } public void RemovePartMailBox(UUID objectID) { - lock (m_queuesLock) + if (m_Enabled) { - m_LastGetEmailCall.Remove(objectID); - if (m_MailQueues.Remove(objectID)) + lock (m_queuesLock) { - //TODO external global + m_LastGetEmailCall.Remove(objectID); + if (m_MailQueues.Remove(objectID)) + { + //TODO external global + } } } } @@ -310,13 +377,43 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules /// /// /// - public void SendEmail(UUID objectID, string address, string subject, string body) + public void SendEmail(UUID objectID, UUID ownerID, string address, string subject, string body) { - //Check if address is empty - if (address.Length == 0) + //Check if address is empty or too large + if(string.IsNullOrEmpty(address)) + return; + address = address.Trim(); + if (address.Length == 0 || address.Length > 320) return; - if(!MailboxAddress.TryParse(address, out MailboxAddress mailTo)) + double now = Util.GetTimeStamp(); + throttleControlInfo tci; + lock (m_ownerThrottles) + { + if (m_ownerThrottles.TryGetValue(ownerID, out tci)) + { + tci.count += (now - tci.lastTime) * m_MailsFromOwnerRate; + tci.lastTime = now; + if (tci.count > m_MailsFromOwnerPerHour) + tci.count = m_MailsFromOwnerPerHour; + else if (tci.count <= 0) + return; + --tci.count; + } + else + { + tci = new throttleControlInfo + { + lastTime = now, + count = m_MailsFromOwnerPerHour + }; + m_ownerThrottles[ownerID] = tci; + } + } + + string addressLower = address.ToLower(); + + if (!MailboxAddress.TryParse(address, out MailboxAddress mailTo)) { m_log.ErrorFormat("[EMAIL]: invalid TO email address {0}",address); return; @@ -335,6 +432,40 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules if (!address.EndsWith(m_InterObjectHostname, StringComparison.InvariantCultureIgnoreCase) && !(m_checkSpecName && address.EndsWith("lsl.secondlife.com", StringComparison.InvariantCultureIgnoreCase))) { + if(!m_enableEmailToSMTP) + return; //smtp disabled + + m_SMTPCount += (m_SMTPLastTime - now) * m_SMTP_MailsRate; + m_SMTPLastTime = now; + if (m_SMTPCount > m_SMTP_MailsPerDay) + m_SMTPCount = m_SMTP_MailsPerDay; + else if (m_SMTPCount <= 0) + return; + --m_SMTPCount; + + lock (m_SMPTAddressThrottles) + { + if (m_SMPTAddressThrottles.TryGetValue(addressLower, out tci)) + { + tci.count += (now - tci.lastTime) * m_MailsToSMTPAddressRate; + tci.lastTime = now; + if (tci.count > m_MailsToSMTPAddressPerHour) + tci.count = m_MailsToSMTPAddressPerHour; + else if (tci.count <= 0) + return; + --tci.count; + } + else + { + tci = new throttleControlInfo + { + lastTime = now, + count = m_MailsToSMTPAddressPerHour + }; + m_SMPTAddressThrottles[addressLower] = tci; + } + } + // regular email, send it out try { @@ -386,6 +517,29 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules } else { + lock (m_primAddressThrottles) + { + if (m_primAddressThrottles.TryGetValue(addressLower, out tci)) + { + tci.count += (now - tci.lastTime) * m_MailsToPrimAddressRate; + tci.lastTime = now; + if (tci.count > m_MailsToPrimAddressPerHour) + tci.count = m_MailsToPrimAddressPerHour; + else if (tci.count <= 0) + return; + --tci.count; + } + else + { + tci = new throttleControlInfo + { + lastTime = now, + count = m_MailsToPrimAddressPerHour + }; + m_primAddressThrottles[addressLower] = tci; + } + } + // inter object email int indx = address.IndexOf('@'); if (indx < 0) @@ -403,11 +557,13 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules if (IsLocal(toID)) { - // object in this region + // object in this instance InsertEmail(toID, email); } else { + if (!m_enableEmailToExternalObjects) + return; // object on another region // TODO FIX } @@ -423,12 +579,67 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules /// public Email GetNextEmail(UUID objectID, string sender, string subject) { + double now = Util.GetTimeStamp(); + double lasthour = now - 3600; + lock (m_ownerThrottles) + { + if (m_ownerThrottles.Count > 0 && now > m_nextOwnerThrottlesExpire) + { + List removal = new List(m_ownerThrottles.Count); + foreach (KeyValuePair kpv in m_ownerThrottles) + { + if (kpv.Value.lastTime < lasthour) + removal.Add(kpv.Key); + } + + foreach (UUID remove in removal) + m_ownerThrottles.Remove(remove); + + m_nextOwnerThrottlesExpire = now + 3600; + } + } + + lock (m_primAddressThrottles) + { + if (m_primAddressThrottles.Count > 0 && now > m_nextPrimAddressThrottlesExpire) + { + List removal = new List(m_primAddressThrottles.Count); + foreach (KeyValuePair kpv in m_primAddressThrottles) + { + if (kpv.Value.lastTime < lasthour) + removal.Add(kpv.Key); + } + + foreach (string remove in removal) + m_primAddressThrottles.Remove(remove); + + m_nextPrimAddressThrottlesExpire = now + 3600; + } + } + + lock (m_SMPTAddressThrottles) + { + if (m_SMPTAddressThrottles.Count > 0 && now > m_nextSMTPAddressThrottlesExpire) + { + List removal = new List(m_SMPTAddressThrottles.Count); + foreach (KeyValuePair kpv in m_SMPTAddressThrottles) + { + if (kpv.Value.lastTime < lasthour) + removal.Add(kpv.Key); + } + + foreach (string remove in removal) + m_SMPTAddressThrottles.Remove(remove); + + m_nextSMTPAddressThrottlesExpire = now + 3600; + } + } + lock (m_queuesLock) { - double now = Util.GetTimeStamp(); m_LastGetEmailCall[objectID] = now + m_QueueTimeout; - if( now > m_nextQueuesExpire) + if(m_LastGetEmailCall.Count > 1 && now > m_nextQueuesExpire) { List removal = new List(m_LastGetEmailCall.Count); foreach (KeyValuePair kpv in m_LastGetEmailCall) @@ -463,12 +674,16 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules if (emptySender || sender.Equals(ret.sender, StringComparison.InvariantCultureIgnoreCase) && (emptySubject || subject.Equals(ret.subject, StringComparison.InvariantCultureIgnoreCase))) { - queue.RemoveAt(i); - ret.numLeft = queue.Count; - if (queue.Count == 0) + if (queue.Count == 1) { m_MailQueues[objectID] = null; m_LastGetEmailCall.Remove(objectID); + ret.numLeft = 0; + } + else + { + queue.RemoveAt(i); + ret.numLeft = queue.Count; } return ret; } diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index 121d666719..420e6d12cb 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -492,10 +492,32 @@ ;; The SMTP server enabled the email module to send email to external ;; destinations. - ;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable SMTP service?} {true false} false - ;; Enable sending email via SMTP + ;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable module?} {true false} false + ;; Enable the email module ; enabled = false + ;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable send to objects to regions not on instance?} {true false} true + ;; Enable sending email to regions not on current opensimulator instance. Currently does not work + ; enableEmailToExternalObjects = true + + + ;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {maximum number of emails from a object owner per hour} {} 500 + ; MailsFromOwnerPerHour = 500 + + ;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {maximum number of emails to a prim address per hour} {} 50 + ; MailsToPrimAddressPerHour = 20 + + ;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {maximum number of emails via smtp per day} {} 100 + ; MailsToAddressPerHour = 500 + ;SMTP_MailsPerDay = 100 + + ;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {maximum number of emails to a SMTP address per hour} {} 10 + ; MailsToSMTPAddressPerHour = 10 + + ;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable send to the world?} {true false} true + ;; Enable sending email to the world. + ; enableEmailToSMTP = true + ;# {internal_object_host} {[Startup]emailmodule:DefaultEmailModule enabled:true} {Host name to treat as internal (object to object) email?} {} lsl.opensim.local ; internal_object_host = lsl.opensim.local