aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Mailman/Cgi/admin.py7
-rw-r--r--Mailman/Cgi/admindb.py7
-rw-r--r--Mailman/Cgi/edithtml.py7
-rw-r--r--Mailman/Cgi/options.py11
-rwxr-xr-xMailman/Cgi/private.py7
-rw-r--r--Mailman/Cgi/roster.py7
-rwxr-xr-xMailman/Defaults.py.in4
-rwxr-xr-xMailman/MailList.py8
-rw-r--r--Mailman/Utils.py29
-rw-r--r--NEWS18
-rw-r--r--templates/en/adminsubscribeack.txt2
-rw-r--r--templates/en/adminunsubscribeack.txt1
12 files changed, 102 insertions, 6 deletions
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
index eeefc056..174cf34f 100644
--- a/Mailman/Cgi/admin.py
+++ b/Mailman/Cgi/admin.py
@@ -119,6 +119,13 @@ 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..010d8aae 100644
--- a/Mailman/Cgi/admindb.py
+++ b/Mailman/Cgi/admindb.py
@@ -159,6 +159,13 @@ 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: remote=%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..8bf1652a 100644
--- a/Mailman/Cgi/edithtml.py
+++ b/Mailman/Cgi/edithtml.py
@@ -126,6 +126,13 @@ 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/options.py b/Mailman/Cgi/options.py
index 6608df4f..4f128baf 100644
--- a/Mailman/Cgi/options.py
+++ b/Mailman/Cgi/options.py
@@ -288,13 +288,16 @@ 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 80369e84..131c5de8 100755
--- a/Mailman/Cgi/private.py
+++ b/Mailman/Cgi/private.py
@@ -142,6 +142,13 @@ 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): 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/Cgi/roster.py b/Mailman/Cgi/roster.py
index 739d4fff..02286c09 100644
--- a/Mailman/Cgi/roster.py
+++ b/Mailman/Cgi/roster.py
@@ -118,6 +118,13 @@ 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/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..cc1be3b5 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
@@ -1069,6 +1075,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
"adminsubscribeack.txt",
{"listname" : realname,
"member" : formataddr((name, email)),
+ "whence" : "" if whence is None else "(" + whence + ")"
}, mlist=self)
msg = Message.OwnerNotification(self, subject, text)
msg.send(self)
@@ -1105,6 +1112,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
'adminunsubscribeack.txt',
{'member' : name,
'listname': self.real_name,
+ "whence" : "" if whence is None else "(" + whence + ")"
}, mlist=self)
msg = Message.OwnerNotification(self, subject, text)
msg.send(self)
diff --git a/Mailman/Utils.py b/Mailman/Utils.py
index 6038667b..d504ecce 100644
--- a/Mailman/Utils.py
+++ b/Mailman/Utils.py
@@ -111,7 +111,12 @@ 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'):
@@ -1530,3 +1535,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
diff --git a/NEWS b/NEWS
index e037982c..23cb0271 100644
--- a/NEWS
+++ b/NEWS
@@ -30,6 +30,24 @@ Here is a history of user visible changes to Mailman.
py2-ipaddress module is installed. The module can be installed via pip
if not included in your Python.
+ - Thanks to Jim Popovitch, Mailman has a new 'security' log and logs
+ authentication failures to the various web CGI functions. The logged
+ data include the remote IP and can be used to automate blocking of IPs
+ with something like fail2ban. Since Mailman 2.1.14, these have returned
+ an http 401 status and the information should be logged by the web
+ server, but this new log makes that more convenient. Also, the
+ 'mischief' log entries for 'hostile listname' noe include the remote IP
+ if available.
+
+ - Thanks to Jim Popovitch, admin notices of (un)subscribes now may give
+ the source of the action. This consists of a %(whence)s replacement
+ that can be added to the admin(un)subscribeack.txt templates. This
+ has been done for the 'en' templates, but not for most others.
+
+ - Thanks to Jim Popovitch, there is a new
+ BLOCK_SPAMHAUS_LISTED_DBL_SUBSCRIBE setting to enable blocking web
+ subscribes for addresses in domains listed in the Spamhaus DBL.
+
i18n
- The Japanese translation has been updated by Yasuhito FUTATSUKI.
diff --git a/templates/en/adminsubscribeack.txt b/templates/en/adminsubscribeack.txt
index 388a3a24..2c83f5c8 100644
--- a/templates/en/adminsubscribeack.txt
+++ b/templates/en/adminsubscribeack.txt
@@ -1,3 +1,3 @@
%(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..bbfcd430 100644
--- a/templates/en/adminunsubscribeack.txt
+++ b/templates/en/adminunsubscribeack.txt
@@ -1,2 +1,3 @@
%(member)s has been removed from %(listname)s.
+%(whence)s