aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Mailman/Archiver/HyperArch.py6
-rw-r--r--Mailman/Cgi/confirm.py7
-rw-r--r--Mailman/Cgi/options.py32
-rw-r--r--Mailman/Commands/cmd_confirm.py8
-rwxr-xr-xMailman/Defaults.py.in4
-rw-r--r--Mailman/Gui/Privacy.py11
-rw-r--r--Mailman/Gui/Topics.py10
-rw-r--r--Mailman/HTMLFormatter.py5
-rw-r--r--Mailman/Handlers/Moderate.py9
-rw-r--r--Mailman/Handlers/Tagger.py6
-rwxr-xr-xMailman/MailList.py81
-rw-r--r--Mailman/Utils.py58
-rw-r--r--Mailman/Version.py4
-rwxr-xr-xMailman/versions.py13
-rwxr-xr-xNEWS55
-rwxr-xr-xbin/newlist28
-rw-r--r--templates/en/adminaddrchgack.txt4
-rw-r--r--templates/en/admindbdetails.html2
18 files changed, 279 insertions, 64 deletions
diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py
index 9b1df75a..0c0e3356 100644
--- a/Mailman/Archiver/HyperArch.py
+++ b/Mailman/Archiver/HyperArch.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2015 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
@@ -509,7 +509,7 @@ class Article(pipermail.Article):
subject = self._get_subject_enc(self.prev)
prev = ('<LINK REL="Previous" HREF="%s">'
% (url_quote(self.prev.filename)))
- prev_wsubj = ('<LI>' + _('Previous message:') +
+ prev_wsubj = ('<LI>' + _('Previous message (by thread):') +
' <A HREF="%s">%s\n</A></li>'
% (url_quote(self.prev.filename),
self.quote(subject)))
@@ -531,7 +531,7 @@ class Article(pipermail.Article):
subject = self._get_subject_enc(self.next)
next = ('<LINK REL="Next" HREF="%s">'
% (url_quote(self.next.filename)))
- next_wsubj = ('<LI>' + _('Next message:') +
+ next_wsubj = ('<LI>' + _('Next message (by thread):') +
' <A HREF="%s">%s\n</A></li>'
% (url_quote(self.next.filename),
self.quote(subject)))
diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py
index bb529318..97297e10 100644
--- a/Mailman/Cgi/confirm.py
+++ b/Mailman/Cgi/confirm.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2015 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
@@ -99,8 +99,9 @@ def main():
%(safecookie)s.
<p>Note that confirmation strings expire approximately
- %(days)s days after the initial subscription request. If your
- confirmation has expired, please try to re-submit your subscription.
+ %(days)s days after the initial request. They also expire if the
+ request has already been handled in some way. If your confirmation
+ has expired, please try to re-submit your request.
Otherwise, <a href="%(confirmurl)s">re-enter</a> your confirmation
string.''')
diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
index 69ac52a9..74f186d7 100644
--- a/Mailman/Cgi/options.py
+++ b/Mailman/Cgi/options.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2015 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
@@ -33,6 +33,7 @@ from Mailman import i18n
from Mailman.htmlformat import *
from Mailman.Logging.Syslog import syslog
+OR = '|'
SLASH = '/'
SETLANGUAGE = -1
@@ -176,6 +177,9 @@ def main():
return
# Are we processing an unsubscription request from the login screen?
+ msgc = _('If you are a list member, a confirmation email has been sent.')
+ 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'):
# Because they can't supply a password for unsubscribing, we'll need
# to do the confirmation dance.
@@ -187,14 +191,11 @@ def main():
# be held. Otherwise, send a confirmation.
if mlist.unsubscribe_policy:
mlist.HoldUnsubscription(user)
- doc.addError(_("""Your unsubscription request has been
- forwarded to the list administrator for approval."""),
- tag='')
+ doc.addError(msga, tag='')
else:
ip = os.environ.get('REMOTE_ADDR')
mlist.ConfirmUnsubscription(user, userlang, remote=ip)
- doc.addError(_('The confirmation email has been sent.'),
- tag='')
+ doc.addError(msgc, tag='')
mlist.Save()
finally:
mlist.Unlock()
@@ -207,19 +208,21 @@ def main():
syslog('mischief',
'Unsub attempt of non-member w/ private rosters: %s',
user)
- doc.addError(_('The confirmation email has been sent.'),
- tag='')
+ if mlist.unsubscribe_policy:
+ doc.addError(msga, tag='')
+ else:
+ doc.addError(msgc, tag='')
loginpage(mlist, doc, user, language)
print doc.Format()
return
# Are we processing a password reminder from the login screen?
+ msg = _("""If you are a list member,
+ your password has been emailed to you.""")
if cgidata.has_key('login-remind'):
if mlist.isMember(user):
mlist.MailUserPassword(user)
- doc.addError(
- _('A reminder of your password has been emailed to you.'),
- tag='')
+ doc.addError(msg, tag='')
else:
# Not a member
if mlist.private_roster == 0:
@@ -229,9 +232,7 @@ def main():
syslog('mischief',
'Reminder attempt of non-member w/ private rosters: %s',
user)
- doc.addError(
- _('A reminder of your password has been emailed to you.'),
- tag='')
+ doc.addError(msg, tag='')
loginpage(mlist, doc, user, language)
print doc.Format()
return
@@ -1068,7 +1069,8 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp):
table.AddRow([Bold(Label(_('Name:'))),
Utils.websafe(name)])
table.AddRow([Bold(Label(_('Pattern (as regexp):'))),
- '<pre>' + Utils.websafe(pattern) + '</pre>'])
+ '<pre>' + Utils.websafe(OR.join(pattern.splitlines()))
+ + '</pre>'])
table.AddRow([Bold(Label(_('Description:'))),
Utils.websafe(description)])
# Make colors look nice
diff --git a/Mailman/Commands/cmd_confirm.py b/Mailman/Commands/cmd_confirm.py
index a3accf64..379d23c2 100644
--- a/Mailman/Commands/cmd_confirm.py
+++ b/Mailman/Commands/cmd_confirm.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2002-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 2002-2015 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
@@ -49,9 +49,9 @@ def process(res, args):
days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1) + 0.5)
res.results.append(_("""\
Invalid confirmation string. Note that confirmation strings expire
-approximately %(days)s days after the initial subscription request. If your
-confirmation has expired, please try to re-submit your original request or
-message."""))
+approximately %(days)s days after the initial request. They also expire if
+the request has already been handled in some way. If your confirmation has
+expired, please try to re-submit your original request or message."""))
except Errors.MMNeedApproval:
res.results.append(_("""\
Your request has been forwarded to the list moderator for approval."""))
diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in
index bec3e441..8a5e6b0e 100755
--- a/Mailman/Defaults.py.in
+++ b/Mailman/Defaults.py.in
@@ -1188,6 +1188,10 @@ DEFAULT_SUBSCRIBE_POLICY = 1
# Does this site allow completely unchecked subscriptions?
ALLOW_OPEN_SUBSCRIBE = No
+# This is the default list of addresses and regular expressions (beginning
+# with ^) that are exempt from approval if SUBSCRIBE_POLICY is 2 or 3.
+DEFAULT_SUBSCRIBE_AUTO_APPROVAL = []
+
# The default policy for unsubscriptions. 0 (unmoderated unsubscribes) is
# highly recommended!
# 0 - unmoderated unsubscribes
diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py
index 7f1e12f3..3c32bf50 100644
--- a/Mailman/Gui/Privacy.py
+++ b/Mailman/Gui/Privacy.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2015 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
@@ -113,6 +113,15 @@ class Privacy(GUIBase):
sub_cfentry,
+ ('subscribe_auto_approval', mm_cfg.EmailListEx, (10, WIDTH), 1,
+ _("""List of addresses (or regexps) whose subscriptions do not
+ require approval."""),
+
+ _("""When subscription requires approval, addresses in this list
+ are allowed to subscribe without administrator approval. Add
+ addresses one per line. You may begin a line with a ^ character
+ to designate a (case insensitive) regular expression match.""")),
+
('unsubscribe_policy', mm_cfg.Radio, (_('No'), _('Yes')), 0,
_("""Is the list moderator's approval required for unsubscription
requests? (<em>No</em> is recommended)"""),
diff --git a/Mailman/Gui/Topics.py b/Mailman/Gui/Topics.py
index 96f9b421..ec60dbda 100644
--- a/Mailman/Gui/Topics.py
+++ b/Mailman/Gui/Topics.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2015 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
@@ -29,6 +29,8 @@ except NameError:
True = 1
False = 0
+OR = '|'
+
class Topics(GUIBase):
@@ -126,10 +128,10 @@ class Topics(GUIBase):
# Make sure the pattern was a legal regular expression
name = Utils.websafe(name)
try:
- # Tagger compiles in verbose mode so we do too.
- re.compile(pattern, re.VERBOSE)
+ orpattern = OR.join(pattern.splitlines())
+ re.compile(orpattern)
except (re.error, TypeError):
- safepattern = Utils.websafe(pattern)
+ safepattern = Utils.websafe(orpattern)
doc.addError(_("""The topic pattern '%(safepattern)s' is not a
legal regular expression. It will be discarded."""))
continue
diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py
index dad51e74..df22e5f2 100644
--- a/Mailman/HTMLFormatter.py
+++ b/Mailman/HTMLFormatter.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2010 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2015 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
@@ -90,6 +90,9 @@ class HTMLFormatter:
showing = Utils.ObscureEmail(person, for_text=1)
else:
showing = person
+ realname = Utils.uncanonstr(self.getMemberName(person), lang)
+ if realname:
+ showing += " (%s)" % Utils.websafe(realname)
got = Link(url, showing)
if self.getDeliveryStatus(person) <> MemberAdaptor.ENABLED:
got = Italic('(', got, ')')
diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py
index 4400d086..225ee37f 100644
--- a/Mailman/Handlers/Moderate.py
+++ b/Mailman/Handlers/Moderate.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2015 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
@@ -162,9 +162,10 @@ def do_reject(mlist):
Utils.wrap(_(mlist.nonmember_rejection_notice))
else:
raise Errors.RejectMessage, Utils.wrap(_("""\
-You are not allowed to post to this mailing list, and your message has been
-automatically rejected. If you think that your messages are being rejected in
-error, contact the mailing list owner at %(listowner)s."""))
+Your message has been rejected, probably because you are not subscribed to the
+mailing list and the list's policy is to prohibit non-members from posting to
+it. If you think that your messages are being rejected in error, contact the
+mailing list owner at %(listowner)s."""))
diff --git a/Mailman/Handlers/Tagger.py b/Mailman/Handlers/Tagger.py
index cb90bfc4..ed9a7e71 100644
--- a/Mailman/Handlers/Tagger.py
+++ b/Mailman/Handlers/Tagger.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2015 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
@@ -29,6 +29,7 @@ from Mailman import Utils
from Mailman.Logging.Syslog import syslog
from Mailman.Handlers.CookHeaders import change_header
+OR = '|'
CRNL = '\r\n'
EMPTYSTRING = ''
NLTAB = '\n\t'
@@ -63,7 +64,8 @@ def process(mlist, msg, msgdata):
# added to the specific topics bucket.
hits = {}
for name, pattern, desc, emptyflag in mlist.topics:
- cre = re.compile(pattern, re.IGNORECASE | re.VERBOSE)
+ pattern = OR.join(pattern.splitlines())
+ cre = re.compile(pattern, re.IGNORECASE)
for line in matchlines:
if cre.search(line):
hits[name] = 1
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index 0ebf8ab8..dca8c8f5 100755
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2015 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
@@ -74,6 +74,7 @@ from Mailman.Logging.Syslog import syslog
_ = i18n._
EMPTYSTRING = ''
+OR = '|'
try:
True, False
@@ -356,6 +357,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.welcome_msg = ''
self.goodbye_msg = ''
self.subscribe_policy = mm_cfg.DEFAULT_SUBSCRIBE_POLICY
+ self.subscribe_auto_approval = mm_cfg.DEFAULT_SUBSCRIBE_AUTO_APPROVAL
self.unsubscribe_policy = mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY
self.private_roster = mm_cfg.DEFAULT_PRIVATE_ROSTER
self.obscure_addresses = mm_cfg.DEFAULT_OBSCURE_ADDRESSES
@@ -773,10 +775,11 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
goodtopics = []
for name, pattern, desc, emptyflag in self.topics:
try:
- re.compile(pattern)
+ orpattern = OR.join(pattern.splitlines())
+ re.compile(orpattern)
except (re.error, TypeError):
syslog('error', 'Bad topic pattern "%s" for list: %s',
- pattern, self.internal_name())
+ orpattern, self.internal_name())
else:
goodtopics.append((name, pattern, desc, emptyflag))
self.topics = goodtopics
@@ -941,6 +944,9 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
syslog('subscribe', '%s: pending %s %s',
self.internal_name(), who, by)
raise Errors.MMSubscribeNeedsConfirmation
+ elif self.HasAutoApprovedSender(email):
+ # no approval necessary:
+ self.ApprovedAddMember(userdesc)
else:
# Subscription approval is required. Add this entry to the admin
# requests database. BAW: this should probably take a userdesc
@@ -1164,12 +1170,14 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# CP address of a member, then if the old address yields a different
# CP address, we can simply remove the old address, otherwise we can
# do nothing.
+ cpoldaddr = self.getMemberCPAddress(oldaddr)
if self.isMember(newaddr) and (self.getMemberCPAddress(newaddr) ==
newaddr):
- if self.getMemberCPAddress(oldaddr) <> newaddr:
+ if cpoldaddr <> newaddr:
self.removeMember(oldaddr)
else:
self.changeMemberAddress(oldaddr, newaddr)
+ self.log_and_notify_admin(cpoldaddr, newaddr)
# If globally is true, then we also include every list for which
# oldaddr is a member.
if not globally:
@@ -1189,16 +1197,46 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
mlist.Lock()
try:
# Same logic as above, re newaddr is already a member
+ cpoldaddr = mlist.getMemberCPAddress(oldaddr)
if mlist.isMember(newaddr) and (
mlist.getMemberCPAddress(newaddr) == newaddr):
- if mlist.getMemberCPAddress(oldaddr) <> newaddr:
+ if cpoldaddr <> newaddr:
mlist.removeMember(oldaddr)
else:
mlist.changeMemberAddress(oldaddr, newaddr)
+ mlist.log_and_notify_admin(cpoldaddr, newaddr)
mlist.Save()
finally:
mlist.Unlock()
+ def log_and_notify_admin(self, oldaddr, newaddr):
+ """Log member address change and notify admin if requested."""
+ syslog('subscribe', '%s: changed member address from %s to %s',
+ self.internal_name(), oldaddr, newaddr)
+ if self.admin_notify_mchanges:
+ lang = self.preferred_language
+ otrans = i18n.get_translation()
+ i18n.set_language(lang)
+ try:
+ realname = self.real_name
+ subject = _('%(realname)s address change notification')
+ finally:
+ i18n.set_translation(otrans)
+ name = self.getMemberName(newaddr)
+ if name is None:
+ name = ''
+ if isinstance(name, UnicodeType):
+ name = name.encode(Utils.GetCharSet(lang), 'replace')
+ text = Utils.maketext(
+ 'adminaddrchgack.txt',
+ {'name' : name,
+ 'oldaddr' : oldaddr,
+ 'newaddr' : newaddr,
+ 'listname': self.real_name,
+ }, mlist=self)
+ msg = Message.OwnerNotification(self, subject, text)
+ msg.send(self)
+
#
# Confirmation processing
@@ -1242,7 +1280,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# list administrators.
self.SendHostileSubscriptionNotice(invitation, addr)
raise Errors.HostileSubscriptionError
- elif self.subscribe_policy in (2, 3):
+ elif self.subscribe_policy in (2, 3) and \
+ not self.HasAutoApprovedSender(addr):
self.HoldSubscription(addr, fullname, password, digest, lang)
name = self.real_name
raise Errors.MMNeedApproval, _(
@@ -1521,13 +1560,30 @@ bad regexp in bounce_matching_header line: %s
"""Returns matched entry in ban_list if email matches.
Otherwise returns None.
"""
- ban = False
- for pattern in self.ban_list:
+ return self.GetPattern(email, self.ban_list)
+
+ def HasAutoApprovedSender(self, sender):
+ """Returns True and logs if sender matches address or pattern
+ in subscribe_auto_approval. Otherwise returns False.
+ """
+ auto_approve = False
+ if self.GetPattern(sender, self.subscribe_auto_approval):
+ auto_approve = True
+ syslog('vette', '%s: auto approved subscribe from %s',
+ self.internal_name(), sender)
+ return auto_approve
+
+ def GetPattern(self, email, pattern_list):
+ """Returns matched entry in pattern_list if email matches.
+ Otherwise returns None.
+ """
+ matched = None
+ for pattern in pattern_list:
if pattern.startswith('^'):
# This is a regular expression match
try:
if re.search(pattern, email, re.IGNORECASE):
- ban = True
+ matched = pattern
break
except re.error:
# BAW: we should probably remove this pattern
@@ -1535,12 +1591,9 @@ bad regexp in bounce_matching_header line: %s
else:
# Do the comparison case insensitively
if pattern.lower() == email.lower():
- ban = True
+ matched = pattern
break
- if ban:
- return pattern
- else:
- return None
+ return matched
diff --git a/Mailman/Utils.py b/Mailman/Utils.py
index 1a08c119..0cb9f122 100644
--- a/Mailman/Utils.py
+++ b/Mailman/Utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2015 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
@@ -80,6 +80,7 @@ except ImportError:
EMPTYSTRING = ''
UEMPTYSTRING = u''
+CR = '\r'
NL = '\n'
DOT = '.'
IDENTCHARS = ascii_letters + digits + '_'
@@ -918,6 +919,61 @@ def oneline(s, cset):
return EMPTYSTRING.join(s.splitlines())
+def strip_verbose_pattern(pattern):
+ # Remove white space and comments from a verbose pattern and return a
+ # non-verbose, equivalent pattern. Replace CR and NL in the result
+ # with '\\r' and '\\n' respectively to avoid multi-line results.
+ if not isinstance(pattern, str):
+ return pattern
+ newpattern = ''
+ i = 0
+ inclass = False
+ skiptoeol = False
+ copynext = False
+ while i < len(pattern):
+ c = pattern[i]
+ if copynext:
+ if c == NL:
+ newpattern += '\\n'
+ elif c == CR:
+ newpattern += '\\r'
+ else:
+ newpattern += c
+ copynext = False
+ elif skiptoeol:
+ if c == NL:
+ skiptoeol = False
+ elif c == '#' and not inclass:
+ skiptoeol = True
+ elif c == '[' and not inclass:
+ inclass = True
+ newpattern += c
+ copynext = True
+ elif c == ']' and inclass:
+ inclass = False
+ newpattern += c
+ elif re.search('\s', c):
+ if inclass:
+ if c == NL:
+ newpattern += '\\n'
+ elif c == CR:
+ newpattern += '\\r'
+ else:
+ newpattern += c
+ elif c == '\\' and not inclass:
+ newpattern += c
+ copynext = True
+ else:
+ if c == NL:
+ newpattern += '\\n'
+ elif c == CR:
+ newpattern += '\\r'
+ else:
+ newpattern += c
+ i += 1
+ return newpattern
+
+
# Patterns and functions to flag possible XSS attacks in HTML.
# This list is compiled from information at http://ha.ckers.org/xss.html,
# http://www.quirksmode.org/js/events_compinfo.html,
diff --git a/Mailman/Version.py b/Mailman/Version.py
index 66eed767..93616e5f 100644
--- a/Mailman/Version.py
+++ b/Mailman/Version.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2015 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
@@ -37,7 +37,7 @@ HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) |
(REL_LEVEL << 4) | (REL_SERIAL << 0))
# config.pck schema version number
-DATA_FILE_VERSION = 105
+DATA_FILE_VERSION = 106
# qfile/*.db schema version number
QFILE_SCHEMA_VERSION = 3
diff --git a/Mailman/versions.py b/Mailman/versions.py
index 9006ec0c..d0960e0d 100755
--- a/Mailman/versions.py
+++ b/Mailman/versions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2015 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
@@ -313,6 +313,15 @@ def UpdateOldVars(l, stored_state):
pass
else:
l.digest_members[k] = 0
+ #
+ # Convert pre 2.2 topics regexps which were compiled in verbose mode
+ # to a non-verbose equivalent.
+ #
+ if stored_state['data_version'] < 106 and stored_state.has_key('topics'):
+ l.topics = []
+ for name, pattern, description, emptyflag in stored_state['topics']:
+ pattern = Utils.strip_verbose_pattern(pattern)
+ l.topics.append((name, pattern, description, emptyflag))
# from_is_list was called author_is_list in 2.1.16rc2 (only).
PreferStored('author_is_list', 'from_is_list',
mm_cfg.DEFAULT_FROM_IS_LIST)
@@ -352,6 +361,8 @@ def NewVars(l):
add_only_if_missing('personalize', 0)
add_only_if_missing('first_strip_reply_to',
mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO)
+ add_only_if_missing('subscribe_auto_approval',
+ mm_cfg.DEFAULT_SUBSCRIBE_AUTO_APPROVAL)
add_only_if_missing('unsubscribe_policy',
mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY)
add_only_if_missing('send_goodbye_msg', mm_cfg.DEFAULT_SEND_GOODBYE_MSG)
diff --git a/NEWS b/NEWS
index 2c05020c..2b736f0f 100755
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,61 @@ Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
Here is a history of user visible changes to Mailman.
+2.2 Branch Backports (released in conjunction with 2.1.19)
+
+ The following New Features and Bug Fixes have been in an "unofficial,
+ never to be released" Mailman 2.2 branch for several years. Until now,
+ they were never implemented on the official 2.1 branch because of their
+ i18n impacts. Given that there have been a number of i18n impacting
+ changes due to DMARC mitigations in the last few releases, it has been
+ decided to backport these as well.
+
+ All of these changes have been running in production on several lists
+ for years without problems other than untranslated strings, so they should
+ be reasonably "bug free".
+
+ New Features
+
+ - There is a new list attribute 'subscribe_auto_approval' which is a list
+ of email addresses and regular expressions matching email addresses
+ whose subscriptions are exempt from admin approval. (LP: #266609)
+
+ - Confirmed member change of address is logged in the 'subscribe' log,
+ and if admin_notify_mchanges is true, a notice is sent to the list
+ owner using a new adminaddrchgack.txt template.
+
+ - Added an 'automate' option to bin/newlist to send the notice to the
+ admin without the prompt.
+
+ - The processing of Topics regular expressions has changed. Previously the
+ Topics regexp was compiled in verbose mode but not documented as such
+ which caused some confusion. Also, the documentation indicated that
+ topic keywords could be entered one per line, but these entries were not
+ handled properly. Topics regexps are now compiled in non-verbose mode
+ and multi-line entries are 'ored'. Existing Topics regexps will be
+ converted when the list is updated so they will continue to work.
+
+ - Added real name display to the web roster. (LP: #266754)
+
+
+ Bug fixes and other patches
+
+ - Changed the response to an invalid confirmation to be more generic.
+ Not all confirmations are subscription requests.
+
+ - Changed the default nonmember_rejection_notice to be more user friendly.
+ (LP: #418728)
+
+ - Added "If you are a list member" qualification to some messages from the
+ options login page. (LP: #266442)
+
+ - Changed the 'Approve' wording in the admindbdetails.html template to
+ 'Accept/Approve' for better agreement with the button labels.
+
+ - Added '(by thread)' to the previous and next message links in the
+ archive to emphasize that even if you got to the message from a
+ subject, date or author index, previous and next are still by thread.
+
2.1.19 (xx-xxx-xxxx)
New Features
diff --git a/bin/newlist b/bin/newlist
index 940ca9f4..f80595c8 100755
--- a/bin/newlist
+++ b/bin/newlist
@@ -1,6 +1,6 @@
#! @PYTHON@
#
-# Copyright (C) 1998-2010 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2015 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
@@ -41,6 +41,13 @@ Options:
their list has been created. This option suppresses the prompt and
notification.
+ -a/--automate
+ This option suppresses the prompt prior to administrator notification
+ but still sends the notification. It can be used to make newlist
+ totally non-interactive but still send the notification, assuming
+ listname, listadmin-addr and admin-password are all specified on the
+ command line.
+
-h/--help
Print this help text and exit.
@@ -84,8 +91,9 @@ where www.mydom.ain is used for `urlhost' but it will also be used for
'--urlhost' and '--emailhost' have precedence to this notation.
If you spell the list name as just `mylist', then the email hostname will be
-taken from DEFAULT_EMAIL_HOST and the url will be taken from DEFAULT_URL (as
-defined in your Defaults.py file or overridden by settings in mm_cfg.py).
+taken from DEFAULT_EMAIL_HOST and the url will be taken from DEFAULT_URL_HOST
+interpolated into DEFAULT_URL_PATTERN (as defined in your Defaults.py file or
+overridden by settings in mm_cfg.py).
Note that listnames are forced to lowercase.
"""
@@ -123,21 +131,24 @@ def usage(code, msg=''):
def main():
try:
- opts, args = getopt.getopt(sys.argv[1:], 'hql:u:e:',
- ['help', 'quiet', 'language=',
+ opts, args = getopt.getopt(sys.argv[1:], 'hqal:u:e:',
+ ['help', 'quiet', 'automate', 'language=',
'urlhost=', 'emailhost='])
except getopt.error, msg:
usage(1, msg)
lang = mm_cfg.DEFAULT_SERVER_LANGUAGE
- quiet = 0
+ quiet = False
+ automate = False
urlhost = None
emailhost = None
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(0)
if opt in ('-q', '--quiet'):
- quiet = 1
+ quiet = True
+ if opt in ('-a', '--automate'):
+ automate = True
if opt in ('-l', '--language'):
lang = arg
if opt in ('-u', '--urlhost'):
@@ -228,9 +239,10 @@ def main():
sys.modules[modname].create(mlist)
# And send the notice to the list owner
- if not quiet:
+ if not quiet and not automate:
print _('Hit enter to notify %(listname)s owner...'),
sys.stdin.readline()
+ if not quiet:
siteowner = Utils.get_site_email(mlist.host_name, 'owner')
text = Utils.maketext(
'newlist.txt',
diff --git a/templates/en/adminaddrchgack.txt b/templates/en/adminaddrchgack.txt
new file mode 100644
index 00000000..a24dd3d9
--- /dev/null
+++ b/templates/en/adminaddrchgack.txt
@@ -0,0 +1,4 @@
+Address for member %(name)s has been successfully changed
+from %(oldaddr)s to %(newaddr)s for list %(listname)s.
+
+
diff --git a/templates/en/admindbdetails.html b/templates/en/admindbdetails.html
index a6b7eb96..4c19fc49 100644
--- a/templates/en/admindbdetails.html
+++ b/templates/en/admindbdetails.html
@@ -12,7 +12,7 @@ excerpt of the message body.
taken now for this pending administrative request, but for held
postings, you can still forward or preserve the message (see below).
-<li><b>Approve</b> -- Approve the message, sending it on to the list.
+<li><b>Accept/Approve</b> -- Accept the message, sending it on to the list.
For membership requests, approve the change in membership status.
<li><b>Reject</b> -- Reject the message, sending a rejection notice to