From cd47d41512f99aff9eaaaaf6e796a34aa00fa8bf Mon Sep 17 00:00:00 2001 From: Jim Popovitch Date: Wed, 6 Jun 2018 10:06:19 +0000 Subject: Improved logging of security related events --- Mailman/Cgi/admin.py | 5 +++++ Mailman/Cgi/admindb.py | 5 +++++ Mailman/Cgi/edithtml.py | 5 +++++ Mailman/Cgi/private.py | 5 +++++ Mailman/Cgi/roster.py | 5 +++++ Mailman/MailList.py | 4 ++++ Mailman/Utils.py | 6 +++++- 7 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index eeefc056..2a86298a 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -119,6 +119,11 @@ def main(): if cgidata.has_key('adminpw'): # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', 'Authorization failed (admin): list=%s: remote=%s', listname, remote) else: msg = '' Auth.loginpage(mlist, 'admin', msg=msg) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index cc863306..58afb46a 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -159,6 +159,11 @@ def main(): if cgidata.has_key('adminpw'): # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', 'Authorization failed (admindb): list=%s: domain=%s', listname, remote) else: msg = '' Auth.loginpage(mlist, 'admindb', msg=msg) diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py index d3d04a31..69421688 100644 --- a/Mailman/Cgi/edithtml.py +++ b/Mailman/Cgi/edithtml.py @@ -126,6 +126,11 @@ def main(): if cgidata.has_key('admlogin'): # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', 'Authorization failed (edithtml): list=%s: remote=%s', listname, remote) else: msg = '' Auth.loginpage(mlist, 'admin', msg=msg) diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index 80369e84..f5c73821 100755 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -142,6 +142,11 @@ def main(): if cgidata.has_key('submit'): # This is a re-authorization attempt message = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', 'Authorization failed (private): list=%s: remote=%s', listname, remote) # give an HTTP 401 for authentication failure print 'Status: 401 Unauthorized' # Are we processing a password reminder from the login screen? diff --git a/Mailman/Cgi/roster.py b/Mailman/Cgi/roster.py index 739d4fff..88391997 100644 --- a/Mailman/Cgi/roster.py +++ b/Mailman/Cgi/roster.py @@ -118,6 +118,11 @@ def main(): error_page_doc(doc, _('%(realname)s roster authentication failed.')) doc.AddItem(mlist.GetMailmanFooter()) print doc.Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', 'Authorization failed (roster): list=%s: remote=%s', listname, remote) return # The document and its language diff --git a/Mailman/MailList.py b/Mailman/MailList.py index fdc3802a..7b096bb1 100755 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1070,6 +1070,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, {"listname" : realname, "member" : formataddr((name, email)), }, mlist=self) + if whence: + text = "%s\nReason: %s" % (text, whence) msg = Message.OwnerNotification(self, subject, text) msg.send(self) @@ -1106,6 +1108,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, {'member' : name, 'listname': self.real_name, }, mlist=self) + if whence: + text = "%s\nReason: %s" % (text, whence) msg = Message.OwnerNotification(self, subject, text) msg.send(self) if whence: diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 6038667b..b38776f8 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -111,7 +111,11 @@ def list_exists(listname): # But first ensure the list name doesn't contain a path traversal # attack. if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname)) > 0: - syslog('mischief', 'Hostile listname: %s', listname) + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('mischief', 'Hostile listname: listname=%s remote=%s', listname, remote) return False basepath = Site.get_listpath(listname) for ext in ('.pck', '.pck.last', '.db', '.db.last'): -- cgit v1.2.3 From 540452e22108455e4efebc8fa7340760a68607f7 Mon Sep 17 00:00:00 2001 From: Jim Popovitch Date: Sun, 10 Jun 2018 23:01:35 +0000 Subject: Changes based on feedback from Mark. --- Mailman/Cgi/options.py | 10 ++++++---- Mailman/Cgi/private.py | 3 ++- Mailman/MailList.py | 4 ---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 6608df4f..e947ff60 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -288,13 +288,15 @@ def main(): # message. if cgidata.has_key('password'): doc.addError(_('Authentication failed.')) + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', 'Authorization failed (private): user=%s + list=%s remote=%s', user, listname, remote) # So as not to allow membership leakage, prompt for the email # address and the password here. if mlist.private_roster <> 0: - remote = os.environ.get('HTTP_FORWARDED_FOR', - os.environ.get('HTTP_X_FORWARDED_FOR', - os.environ.get('REMOTE_ADDR', - 'unidentified origin'))) syslog('mischief', 'Login failure with private rosters: %s from %s', user, remote) diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index f5c73821..cb954d3c 100755 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -146,7 +146,8 @@ def main(): os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - syslog('security', 'Authorization failed (private): list=%s: remote=%s', listname, remote) + syslog('security', 'Authorization failed (private): user=%s list=%s + remote=%s', username, listname, remote) # give an HTTP 401 for authentication failure print 'Status: 401 Unauthorized' # Are we processing a password reminder from the login screen? diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 7b096bb1..fdc3802a 100755 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1070,8 +1070,6 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, {"listname" : realname, "member" : formataddr((name, email)), }, mlist=self) - if whence: - text = "%s\nReason: %s" % (text, whence) msg = Message.OwnerNotification(self, subject, text) msg.send(self) @@ -1108,8 +1106,6 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, {'member' : name, 'listname': self.real_name, }, mlist=self) - if whence: - text = "%s\nReason: %s" % (text, whence) msg = Message.OwnerNotification(self, subject, text) msg.send(self) if whence: -- cgit v1.2.3 From e7fff57cf267b6a4c5ba8ce25db19806fe172b32 Mon Sep 17 00:00:00 2001 From: Jim Popovitch Date: Mon, 11 Jun 2018 17:55:03 +0000 Subject: Support for verifying subscriber domains against the Spamhaus DBL --- Mailman/Defaults.py.in | 4 ++++ Mailman/MailList.py | 6 ++++++ Mailman/Utils.py | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 7a86f63c..63a8e99f 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -150,6 +150,10 @@ GLOBAL_BAN_LIST = [] # will be blocked. BLOCK_SPAMHAUS_LISTED_IP_SUBSCRIBE = No +# IF the following is set to Yes, and a subscriper uses a domain that is +# listed in the Spamhaus DBL, the subscription will be blocked. +BLOCK_SPAMHAUS_LISTED_DBL_SUBSCRIBE = No + # Command that is used to convert text/html parts into plain text. This # should output results to standard output. %(filename)s will contain the # name of the temporary file that the program should operate on. diff --git a/Mailman/MailList.py b/Mailman/MailList.py index fdc3802a..b2bb22a5 100755 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -915,6 +915,12 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, syslog('vette', '%s banned subscription: %s%s (Spamhaus IP)', realname, email, whence) raise Errors.MembershipIsBanned, 'Spamhaus IP' + # See if this is from a spamhaus listed domain. + if email and mm_cfg.BLOCK_SPAMHAUS_LISTED_DBL_SUBSCRIBE: + if Utils.banned_domain(email): + syslog('vette', '%s banned subscription: %s (Spamhaus DBL)', + realname, email) + raise Errors.MembershipIsBanned, 'Spamhaus DBL' # Sanity check the digest flag if digest and not self.digestable: raise Errors.MMCantDigestError diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 6038667b..a8c8fd82 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1530,3 +1530,25 @@ def banned_ip(ip): if re.search(r'127\.0\.0\.[2-7]$', text, re.MULTILINE): return True return False + +def banned_domain(email): + if not dns_resolver: + return False + + email = email.lower() + user, domain = ParseEmail(email) + + lookup = '%s.zen.spamhaus.org' % (domain) + + resolver = dns.resolver.Resolver() + try: + ans = resolver.query(lookup, dns.rdatatype.A) + except DNSException: + return False + if not ans: + return False + text = ans.rrset.to_text() + if re.search(r'127\.0\.1\.\d{1,3}$', text, re.MULTILINE): + if not re.search(r'127\.0\.1\.255$', text, re.MULTILINE): + return True + return False -- cgit v1.2.3 From 4c54a401f9070bcb385a44bbaec8c08facb10ee4 Mon Sep 17 00:00:00 2001 From: Jim Popovitch Date: Tue, 12 Jun 2018 02:05:08 +0000 Subject: Add whence to owner subscribe|unsubscribe notifications. --- Mailman/MailList.py | 2 ++ templates/en/adminsubscribeack.txt | 2 +- templates/en/adminunsubscribeack.txt | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index fdc3802a..1755ae64 100755 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1069,6 +1069,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, "adminsubscribeack.txt", {"listname" : realname, "member" : formataddr((name, email)), + "whence" : "" if whence == None else " (" + whence + ")" }, mlist=self) msg = Message.OwnerNotification(self, subject, text) msg.send(self) @@ -1105,6 +1106,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, 'adminunsubscribeack.txt', {'member' : name, 'listname': self.real_name, + "whence" : "" if whence == None else " (" + whence + ")" }, mlist=self) msg = Message.OwnerNotification(self, subject, text) msg.send(self) diff --git a/templates/en/adminsubscribeack.txt b/templates/en/adminsubscribeack.txt index 388a3a24..f5d94e36 100644 --- a/templates/en/adminsubscribeack.txt +++ b/templates/en/adminsubscribeack.txt @@ -1,3 +1,3 @@ -%(member)s has been successfully subscribed to %(listname)s. +%(member)s has been successfully subscribed to %(listname)s%(whence)s. diff --git a/templates/en/adminunsubscribeack.txt b/templates/en/adminunsubscribeack.txt index 2ebcfeb7..5b30f805 100644 --- a/templates/en/adminunsubscribeack.txt +++ b/templates/en/adminunsubscribeack.txt @@ -1,2 +1,2 @@ -%(member)s has been removed from %(listname)s. +%(member)s has been removed from %(listname)s%(whence)s. -- cgit v1.2.3