diff options
Diffstat (limited to 'Mailman')
-rw-r--r-- | Mailman/Archiver/pipermail.py | 4 | ||||
-rw-r--r-- | Mailman/Bouncers/SimpleMatch.py | 9 | ||||
-rw-r--r-- | Mailman/Bouncers/SimpleWarning.py | 30 | ||||
-rw-r--r-- | Mailman/Cgi/options.py | 5 | ||||
-rw-r--r-- | Mailman/Cgi/private.py | 10 | ||||
-rw-r--r-- | Mailman/Commands/cmd_unsubscribe.py | 11 | ||||
-rwxr-xr-x | Mailman/Defaults.py.in | 2 | ||||
-rw-r--r-- | Mailman/MailList.py | 16 | ||||
-rw-r--r-- | Mailman/Utils.py | 6 | ||||
-rw-r--r-- | Mailman/Version.py | 8 |
10 files changed, 70 insertions, 31 deletions
diff --git a/Mailman/Archiver/pipermail.py b/Mailman/Archiver/pipermail.py index 62fb92e8..d0883a3f 100644 --- a/Mailman/Archiver/pipermail.py +++ b/Mailman/Archiver/pipermail.py @@ -12,6 +12,10 @@ import cPickle as pickle from cStringIO import StringIO from string import lowercase +# Work around for some misguided Python packages that add iso-8859-1 +# accented characters to string.lowercase. +lowercase = lowercase[:26] + __version__ = '0.09 (Mailman edition)' VERSION = __version__ CACHESIZE = 100 # Number of slots in the cache diff --git a/Mailman/Bouncers/SimpleMatch.py b/Mailman/Bouncers/SimpleMatch.py index ca648f65..6de9a858 100644 --- a/Mailman/Bouncers/SimpleMatch.py +++ b/Mailman/Bouncers/SimpleMatch.py @@ -25,6 +25,9 @@ import email.Iterators def _c(pattern): return re.compile(pattern, re.IGNORECASE) +# Pattern to match any valid email address and not much more. +VALID = _c(r'^[\x21-\x3d\x3f\x41-\x7e]+@[a-z0-9._]+$') + # This is a list of tuples of the form # # (start cre, end cre, address cre) @@ -192,6 +195,10 @@ PATTERNS = [ (_c('Message could not be delivered to some recipients.'), _c('Message headers follow'), _c('Recipient: \[SMTP:(?P<addr>[^\s@]+@[^\s@]+)\]')), + # This one is from Yahoo but dosen't fit the yahoo recognizer format + (_c(r'wasn\'t able to deliver the following message'), + _c(r'---Below this line is a copy of the message.'), + _c(r'To: (?P<addr>[^\s@]+@[^\s@]+)')), # Next one goes here... ] @@ -227,4 +234,4 @@ def process(msg, patterns=None): break if addrs: break - return addrs.keys() + return [x for x in addrs.keys() if VALID.match(x)] diff --git a/Mailman/Bouncers/SimpleWarning.py b/Mailman/Bouncers/SimpleWarning.py index 43ad5396..e51e5931 100644 --- a/Mailman/Bouncers/SimpleWarning.py +++ b/Mailman/Bouncers/SimpleWarning.py @@ -17,9 +17,10 @@ """Recognizes simple heuristically delimited warnings.""" +import email + from Mailman.Bouncers.BouncerAPI import Stop from Mailman.Bouncers.SimpleMatch import _c -from Mailman.Bouncers.SimpleMatch import process as _process @@ -67,8 +68,25 @@ patterns = [ def process(msg): - if _process(msg, patterns): - # It's a recognized warning so stop now - return Stop - else: - return [] + # We used to just import process from SimpleMatch, but with the change in + # SimpleMatch to return only vaild addresses, that doesn't work any more. + # So, we copy most of the process from SimpleMatch here. + addrs = {} + for scre, ecre, acre in patterns: + state = 0 + for line in email.Iterators.body_line_iterator(msg, decode=True): + if state == 0: + if scre.search(line): + state = 1 + if state == 1: + mo = acre.search(line) + if mo: + addr = mo.group('addr') + if addr: + addrs[addr.strip('<>')] = 1 + elif ecre.search(line): + break + if addrs: + # It's a recognized warning so stop now + return Stop + return [] diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 641ec134..1037f8f9 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -173,7 +173,7 @@ def main(): try: Utils.ValidateEmail(user) except Errors.EmailAddressError: - doc.addError(_('Illegal Email Address: %(safeuser)s')) + doc.addError(_('Illegal Email Address')) loginpage(mlist, doc, None, language) print doc.Format() return @@ -206,6 +206,7 @@ def main(): # Are we processing an unsubscription request from the login screen? msgc = _('If you are a list member, a confirmation email has been sent.') + msgb = _('You already have a subscription pending confirmation') msga = _("""If you are a list member, your unsubscription request has been forwarded to the list administrator for approval.""") if cgidata.has_key('login-unsub'): @@ -228,6 +229,8 @@ def main(): mlist.ConfirmUnsubscription(user, userlang, remote=ip) doc.addError(msgc, tag='') mlist.Save() + except Errors.MMAlreadyPending: + doc.addError(msgb) finally: mlist.Unlock() else: diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index 731e2d19..4b6f2501 100644 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -162,13 +162,9 @@ def main(): if mlist.isMember(username): mlist.MailUserPassword(username) elif username: - # Not a member - if mlist.private_roster == 0: - # Public rosters - safeuser = Utils.websafe(username) - message = Bold(FontSize('+1', - _('No such member: %(safeuser)s.'))).Format() - else: + # Not a member. Don't report address in any case. It leads to + # Content injection. Just log if roster is not public. + if mlist.private_roster != 0: syslog('mischief', 'Reminder attempt of non-member w/ private rosters: %s', username) diff --git a/Mailman/Commands/cmd_unsubscribe.py b/Mailman/Commands/cmd_unsubscribe.py index 9ffa80cd..3a156536 100644 --- a/Mailman/Commands/cmd_unsubscribe.py +++ b/Mailman/Commands/cmd_unsubscribe.py @@ -73,9 +73,14 @@ approval.""")) # No password was given, so we need to do a mailback confirmation # instead of unsubscribing them here. cpaddr = mlist.getMemberCPAddress(address) - mlist.ConfirmUnsubscription(cpaddr) - # We don't also need to send a confirmation to this command - res.respond = 0 + try: + mlist.ConfirmUnsubscription(cpaddr) + except Errors.MMAlreadyPending: + res.results.append( + _('You already have a subscription pending confirmation')) + else: + # We don't also need to send a confirmation to this command + res.respond = 0 else: # No admin approval is necessary, so we can just delete them if the # passwords match. diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 277e3ab0..e5d8b3cf 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -1128,7 +1128,7 @@ ANONYMOUS_LIST_KEEP_HEADERS = ['^(?!x-)', '^x-mailman-', # pending a subscription confirmation when one is already pending. The down # side to this is if a subscriber loses or doesn't receive the confirmation # request email, she has to wait PENDING_REQUEST_LIFE (default 3 days) before -# she can request another. +# she can request another. This setting also applies to repeated unsubscribes. REFUSE_SECOND_PENDING = No diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 9e6bbcb6..fdd47ae1 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2018 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2020 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -833,8 +833,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, # # Membership management front-ends and assertion checks # - def CheckPending(self, email): - """Check if there is already an unexpired pending subscription for + def CheckPending(self, email, unsub=False): + """Check if there is already an unexpired pending (un)subscription for this email. """ if not mm_cfg.REFUSE_SECOND_PENDING: @@ -846,9 +846,11 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, for k, v in pends.items(): if k in ('evictions', 'version'): continue - op, data = v - if (op == Pending.SUBSCRIPTION and - data.address.lower() == email.lower()): + op, data = v[:2] + if (op == Pending.SUBSCRIPTION and not unsub and + data.address.lower() == email.lower() or + op == Pending.UNSUBSCRIPTION and unsub and + data.lower() == email.lower()): return True return False @@ -1477,6 +1479,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, assert 0, 'Bad op: %s' % op def ConfirmUnsubscription(self, addr, lang=None, remote=None): + if self.CheckPending(addr, unsub=True): + raise Errors.MMAlreadyPending, email if lang is None: lang = self.getMemberLanguage(addr) cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 36fbd1f9..6e39c532 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1343,12 +1343,14 @@ def _DMARCProhibited(mlist, email, dmarc_domain, org=False): cnames = {} want_names = set([dmarc_domain + '.']) for txt_rec in txt_recs.response.answer: + # Don't be fooled by an answer with uppercase in the name. + name = txt_rec.name.to_text().lower() if txt_rec.rdtype == dns.rdatatype.CNAME: - cnames[txt_rec.name.to_text()] = ( + cnames[name] = ( txt_rec.items[0].target.to_text()) if txt_rec.rdtype != dns.rdatatype.TXT: continue - results_by_name.setdefault(txt_rec.name.to_text(), []).append( + results_by_name.setdefault(name, []).append( "".join(txt_rec.items[0].strings)) expands = list(want_names) seen = set(expands) diff --git a/Mailman/Version.py b/Mailman/Version.py index 649e0f68..af7d8085 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -16,7 +16,7 @@ # USA. # Mailman version -VERSION = '2.1.30rc1' +VERSION = '2.1.33' # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -28,10 +28,10 @@ FINAL = 0xf MAJOR_REV = 2 MINOR_REV = 1 -MICRO_REV = 30 -REL_LEVEL = GAMMA +MICRO_REV = 33 +REL_LEVEL = FINAL # at most 15 beta releases! -REL_SERIAL = 1 +REL_SERIAL = 0 HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) | (REL_LEVEL << 4) | (REL_SERIAL << 0)) |