diff options
Diffstat (limited to 'Mailman/Gui')
-rw-r--r-- | Mailman/Gui/Bounce.py | 11 | ||||
-rw-r--r-- | Mailman/Gui/Digest.py | 6 | ||||
-rw-r--r-- | Mailman/Gui/GUIBase.py | 20 | ||||
-rw-r--r-- | Mailman/Gui/General.py | 80 | ||||
-rw-r--r-- | Mailman/Gui/Membership.py | 1 | ||||
-rwxr-xr-x | Mailman/Gui/NonDigest.py | 2 | ||||
-rw-r--r-- | Mailman/Gui/Privacy.py | 206 | ||||
-rw-r--r-- | Mailman/Gui/Topics.py | 10 |
8 files changed, 298 insertions, 38 deletions
diff --git a/Mailman/Gui/Bounce.py b/Mailman/Gui/Bounce.py index 1dc837fc..e559dcc8 100644 --- a/Mailman/Gui/Bounce.py +++ b/Mailman/Gui/Bounce.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001-2004 by the Free Software Foundation, Inc. +# Copyright (C) 2001-2014 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 @@ -142,6 +142,15 @@ class Bounce(GUIBase): <a href="?VARHELP=autoreply/autoresponse_admin_text">autoresponse message</a> for email to the -owner and -admin address.""")), + ('bounce_notify_owner_on_bounce_increment', mm_cfg.Toggle, + (_('No'), _('Yes')), 0, + _("""Should Mailman notify you, the list owner, when bounces + cause a member's bounce score to be incremented?"""), + _("""Setting this value to <em>Yes</em> will cause Mailman to + send a notice including a copy of the bounce message to the list + owners whenever a bounce increments a member's bounce score but + doesn't cause a disable or a probe to be sent.""")), + ('bounce_notify_owner_on_disable', mm_cfg.Toggle, (_('No'), _('Yes')), 0, _("""Should Mailman notify you, the list owner, when bounces diff --git a/Mailman/Gui/Digest.py b/Mailman/Gui/Digest.py index f7722019..77691aee 100644 --- a/Mailman/Gui/Digest.py +++ b/Mailman/Gui/Digest.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2013 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 @@ -56,8 +56,8 @@ class Digest(GUIBase): _('When receiving digests, which format is default?')), ('digest_size_threshhold', mm_cfg.Number, 3, 0, - _('How big in Kb should a digest be before it gets sent out?')), - # Should offer a 'set to 0' for no size threshhold. + _('How big in Kb should a digest be before it gets sent out?' + ' 0 implies no maximum size.')), ('digest_send_periodic', mm_cfg.Radio, (_('No'), _('Yes')), 1, _('Should a digest be dispatched daily when the size threshold ' diff --git a/Mailman/Gui/GUIBase.py b/Mailman/Gui/GUIBase.py index a365acaf..32d19929 100644 --- a/Mailman/Gui/GUIBase.py +++ b/Mailman/Gui/GUIBase.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2008 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 @@ -63,6 +63,7 @@ class GUIBase: if isinstance(val, ListType): return val addrs = [] + bad_addrs = [] for addr in [s.strip() for s in val.split(NL)]: # Discard empty lines if not addr: @@ -77,22 +78,25 @@ class GUIBase: try: re.compile(addr) except re.error: - raise ValueError + bad_addrs.append(addr) elif (wtype == mm_cfg.EmailListEx and addr.startswith('@') - and property.endswith('_these_nonmembers')): + and (property.endswith('_these_nonmembers') or + property == 'subscribe_auto_approval')): # XXX Needs to be reviewed for list@domain names. # don't reference your own list if addr[1:] == mlist.internal_name(): - raise ValueError + bad_addrs.append(addr) # check for existence of list? For now allow # reference to list before creating it. else: - raise + bad_addrs.append(addr) if property in ('regular_exclude_lists', 'regular_include_lists'): if addr.lower() == mlist.GetListEmail().lower(): - raise Errors.EmailAddressError + bad_addrs.append(addr) addrs.append(addr) + if bad_addrs: + raise Errors.EmailAddressError, ', '.join(bad_addrs) return addrs # This is a host name, i.e. verbatim if wtype == mm_cfg.Host: @@ -168,9 +172,9 @@ class GUIBase: except ValueError: doc.addError(_('Invalid value for variable: %(property)s')) # This is the parent of MMBadEmailError and MMHostileAddress - except Errors.EmailAddressError: + except Errors.EmailAddressError, error: doc.addError( - _('Bad email address for option %(property)s: %(val)s')) + _('Bad email address for option %(property)s: %(error)s')) else: # Set the attribute, which will normally delegate to the mlist self._setValue(mlist, property, val, doc) diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py index e9f8f9b5..980e5f2b 100644 --- a/Mailman/Gui/General.py +++ b/Mailman/Gui/General.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001-2011 by the Free Software Foundation, Inc. +# Copyright (C) 2001-2014 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 @@ -154,6 +154,72 @@ class General(GUIBase): (listname %%05d) -> (listname 00123) """)), + ('from_is_list', mm_cfg.Radio, + (_('No'), _('Munge From'), _('Wrap Message')), 0, + _("""Replace the From: header address with the list's posting + address to mitigate issues stemming from the original From: + domain's DMARC or similar policies."""), + _("""Several protocols now in wide use attempt to ensure that use + of the domain in the author's address (ie, in the From: header + field) is authorized by that domain. These protocols may be + incompatible with common list features such as footers, causing + participating email services to bounce list traffic merely + because of the address in the From: field. <b>This has resulted + in members being unsubscribed despite being perfectly able to + receive mail.</b> + <p> + The following actions are applied to all list messages when + selected here. To apply these actions only to messages where the + domain in the From: header is determined to use such a protocol, + see the <a + href="?VARHELP=privacy/sender/dmarc_moderation_action"> + dmarc_moderation_action</a> settings under Privacy options... + -> Sender filters. + <p>Settings:<p> + <dl> + <dt>No</dt> + <dd>Do nothing special. This is appropriate for anonymous lists. + It is appropriate for dedicated announcement lists, unless the + From: address of authorized posters might be in a domain with a + DMARC or similar policy. It is also appropriate if you choose to + use dmarc_moderation_action other than Accept for this list.</dd> + <dt>Munge From</dt> + <dd>This action replaces the poster's address in the From: header + with the list's posting address and adds the poster's address to + the addresses in the original Reply-To: header.</dd> + <dt>Wrap Message</dt> + <dd>Just wrap the message in an outer message with the From: + header containing the list's posting address and with the original + From: address added to the addresses in the original Reply-To: + header and with Content-Type: message/rfc822. This is effectively + a one message MIME format digest.</dd> + </dl> + <p>The transformations for anonymous_list are applied before + any of these actions. It is not useful to apply actions other + than No to an anonymous list, and if you do so, the result may + be surprising. + <p>The Reply-To: header munging actions below interact with these + actions as follows: + <p> first_strip_reply_to = Yes will remove all the incoming + Reply-To: addresses but will still add the poster's address to + Reply-To: for all three settings of reply_goes_to_list which + respectively will result in just the poster's address, the + poster's address and the list posting address or the poster's + address and the explicit reply_to_address in the outgoing + Reply-To: header. If first_strip_reply_to = No the poster's + address in the original From: header, if not already included in + the Reply-To:, will be added to any existing Reply-To: + address(es). + <p>These actions, whether selected here or via <a + href="?VARHELP=privacy/sender/dmarc_moderation_action"> + dmarc_moderation_action</a>, do not apply to messages in digests + or archives or sent to usenet via the Mail<->News gateways. + <p>If <a + href="?VARHELP=privacy/sender/dmarc_moderation_action"> + dmarc_moderation_action</a> applies to this message with an + action other than Accept, that action rather than this is + applied""")), + ('anonymous_list', mm_cfg.Radio, (_('No'), _('Yes')), 0, _("""Hide the sender of a message, replacing it with the list address (Removes From, Sender and Reply-To fields)""")), @@ -189,11 +255,11 @@ class General(GUIBase): their own <tt>Reply-To:</tt> settings to convey their valid return address. Another is that modifying <tt>Reply-To:</tt> makes it much more difficult to send private replies. See <a - href="http://www.unicom.com/pw/reply-to-harmful.html">`Reply-To' + href="http://marc.merlins.org/netrants/reply-to-harmful.html">`Reply-To' Munging Considered Harmful</a> for a general discussion of this issue. See <a - href="http://www.metasystema.net/essays/reply-to.mhtml">Reply-To - Munging Considered Useful</a> for a dissenting opinion. + href="http://marc.merlins.org/netrants/reply-to-useful.html"> + Reply-To Munging Considered Useful</a> for a dissenting opinion. <p>Some mailing lists have restricted posting privileges, with a parallel list devoted to discussions. Examples are `patches' or @@ -217,11 +283,11 @@ class General(GUIBase): their own <tt>Reply-To:</tt> settings to convey their valid return address. Another is that modifying <tt>Reply-To:</tt> makes it much more difficult to send private replies. See <a - href="http://www.unicom.com/pw/reply-to-harmful.html">`Reply-To' + href="http://marc.merlins.org/netrants/reply-to-harmful.html">`Reply-To' Munging Considered Harmful</a> for a general discussion of this issue. See <a - href="http://www.metasystema.net/essays/reply-to.mhtml">Reply-To - Munging Considered Useful</a> for a dissenting opinion. + href="http://marc.merlins.org/netrants/reply-to-useful.html"> + Reply-To Munging Considered Useful</a> for a dissenting opinion. <p>Some mailing lists have restricted posting privileges, with a parallel list devoted to discussions. Examples are `patches' or diff --git a/Mailman/Gui/Membership.py b/Mailman/Gui/Membership.py index 7e1bf324..fdf5a7ca 100644 --- a/Mailman/Gui/Membership.py +++ b/Mailman/Gui/Membership.py @@ -30,5 +30,6 @@ class Membership: return [('list', _('Membership List')), ('add', _('Mass Subscription')), ('remove', _('Mass Removal')), + ('change', _('Address Change')), ] return None diff --git a/Mailman/Gui/NonDigest.py b/Mailman/Gui/NonDigest.py index 411f5d4f..e4d7d526 100755 --- a/Mailman/Gui/NonDigest.py +++ b/Mailman/Gui/NonDigest.py @@ -160,7 +160,7 @@ and footers: siblings.""")), ('regular_exclude_ignore', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _("""Ignore regular_exlude_lists of which the poster is not a + _("""Ignore regular_exclude_lists of which the poster is not a member."""), _("""If a post is addressed to this list and to one or more of the exclude lists, regular members of those lists will not be diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py index 75eff2b5..2a9cca26 100644 --- a/Mailman/Gui/Privacy.py +++ b/Mailman/Gui/Privacy.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001-2008 by the Free Software Foundation, Inc. +# Copyright (C) 2001-2016 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 @@ -17,6 +17,7 @@ """MailList mixin class managing the privacy options.""" +import os import re from Mailman import mm_cfg @@ -61,7 +62,7 @@ class Privacy(GUIBase): _('Confirm and approve')), 0, _('What steps are required for subscription?<br>'), - _('''None - no verification steps (<em>Not + _("""None - no verification steps (<em>Not Recommended </em>)<br> Confirm (*) - email confirmation step required <br> Require approval - require list administrator @@ -75,7 +76,7 @@ class Privacy(GUIBase): This prevents mischievous (or malicious) people from creating subscriptions for others without - their consent.''')) + their consent.""")) else: sub_cfentry = ('subscribe_policy', mm_cfg.Radio, # choices @@ -84,7 +85,7 @@ class Privacy(GUIBase): _('Confirm and approve')), 1, _('What steps are required for subscription?<br>'), - _('''Confirm (*) - email confirmation required <br> + _("""Confirm (*) - email confirmation required <br> Require approval - require list administrator approval for subscriptions <br> Confirm and approve - both confirm and approve @@ -94,7 +95,7 @@ class Privacy(GUIBase): subscription request number that they must reply to in order to subscribe.<br> This prevents mischievous (or malicious) people from creating - subscriptions for others without their consent.''')) + subscriptions for others without their consent.""")) # some helpful values admin = mlist.GetScriptURL('admin') @@ -108,11 +109,23 @@ class Privacy(GUIBase): _('Subscribing'), ('advertised', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('''Advertise this list when people ask what lists are on this - machine?''')), + _("""Advertise this list when people ask what lists are on this + machine?""")), 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.""") + + ' ' + + _("""You may also use the @listname notation to designate the + members of another list in this installation."""))), + ('unsubscribe_policy', mm_cfg.Radio, (_('No'), _('Yes')), 0, _("""Is the list moderator's approval required for unsubscription requests? (<em>No</em> is recommended)"""), @@ -143,8 +156,8 @@ class Privacy(GUIBase): (_('Anyone'), _('List members'), _('List admin only')), 0, _('Who can view subscription list?'), - _('''When set, the list of subscribers is protected by member or - admin password authentication.''')), + _("""When set, the list of subscribers is protected by member or + admin password authentication.""")), ('obscure_addresses', mm_cfg.Radio, (_('No'), _('Yes')), 0, _("""Show member addresses so they're not directly recognizable @@ -158,6 +171,11 @@ class Privacy(GUIBase): ] adminurl = mlist.GetScriptURL('admin', absolute=1) + + if mlist.dmarc_quarantine_moderation_action: + quarantine = _('/Quarantine') + else: + quarantine = '' sender_rtn = [ _("""When a message is posted to the list, a series of moderation steps are taken to decide whether a moderator must @@ -212,6 +230,37 @@ class Privacy(GUIBase): <a href="%(adminurl)s/members">membership management screens</a>.""")), + ('member_verbosity_threshold', mm_cfg.Number, 5, 0, + _("""Ceiling on acceptable number of member posts, per interval, + before automatic moderation."""), + + _("""If a member posts this many times, within a period of time + the member is automatically moderated. Use 0 to disable. See + <a href="?VARHELP=privacy/sender/member_verbosity_interval" + >member_verbosity_interval</a> for details on the time period. + + <p>This is intended to stop people who join a list or lists and + then use a bot to send many spam messages in a short interval. + + <p>Be careful when using this setting. If it is set too low, + this can be triggered by a single post cross-posted to + multiple lists or by a single post to an umbrella list.""")), + + ('member_verbosity_interval', mm_cfg.Number, 5, 0, + _("""Number of seconds to remember posts to this list to determine + member_verbosity_threshold for automatic moderation of a + member."""), + + _("""If a member's total posts to all lists in this installation + with member_verbosity_threshold enabled reaches this list's + member_verbosity_threshold, the member is automatically + moderated on this list. + + <p>Posts which are counted towards this list's + member_verbosity_threshold are all posts to any list with + member_verbosity_threshold enabled that arrived within that + list's member_verbosity_interval.""")), + ('member_moderation_action', mm_cfg.Radio, (_('Hold'), _('Reject'), _('Discard')), 0, _("""Action to take when a moderated member posts to the @@ -235,6 +284,119 @@ class Privacy(GUIBase): >rejection notice</a> to be sent to moderated members who post to this list.""")), + ('dmarc_moderation_action', mm_cfg.Radio, + (_('Accept'), _('Munge From'), _('Wrap Message'), _('Reject'), + _('Discard')), 0, + _("""Action to take when anyone posts to the + list from a domain with a DMARC Reject%(quarantine)s Policy."""), + + _("""<ul><li><b>Munge From</b> -- applies the <a + href="?VARHELP=general/from_is_list">from_is_list Munge From</a> + transformation to these messages. + + <p><li><b>Wrap Message</b> -- applies the <a + href="?VARHELP=general/from_is_list">from_is_list Wrap + Message</a> transformation to these messages. + + <p><li><b>Reject</b> -- this automatically rejects the message by + sending a bounce notice to the post's author. The text of the + bounce notice can be <a + href="?VARHELP=privacy/sender/dmarc_moderation_notice" + >configured by you</a>. + + <p><li><b>Discard</b> -- this simply discards the message, with + no notice sent to the post's author. + </ul> + + <p>This setting takes precedence over the <a + href="?VARHELP=general/from_is_list"> from_is_list</a> setting + if the message is From: an affected domain and the setting is + other than Accept.""")), + + ('dmarc_quarantine_moderation_action', mm_cfg.Radio, + (_('No'), _('Yes')), 0, + _("""Shall the above dmarc_moderation_action apply to messages + From: domains with DMARC p=quarantine as well as p=reject"""), + + _("""<ul><li><b>No</b> -- this applies dmarc_moderation_action to + only those posts From: a domain with DMARC p=reject. This is + appropriate if you are concerned about bounced messages, but + want to apply dmarc_moderation_action to as few messages as + possible. + <p><li><b>Yes</b> -- this applies dmarc_moderation_action to + posts From: a domain with DMARC p=reject or p=quarantine. + </ul><p>If a message is From: a domain with DMARC p=quarantine + and dmarc_moderation_action is not applied (this set to No) + the message will likely not bounce, but will be delivered to + recipients' spam folders or other hard to find places.""")), + + ('dmarc_none_moderation_action', mm_cfg.Radio, + (_('No'), _('Yes')), 0, + _("""Shall the above dmarc_moderation_action apply to messages + From: domains with DMARC p=none as well as p=quarantine and + p=reject"""), + + _("""<ul><li><b>No</b> -- this applies dmarc_moderation_action to + only those posts From: a domain with DMARC p=reject and + possibly p=quarantine depending on the setting of + dmarc_quarantine_moderation_action. + <p><li><b>Yes</b> -- this applies dmarc_moderation_action to + posts From: a domain with DMARC p=none if + dmarc_moderation_action is Munge From or Wrap Message and + dmarc_quarantine_moderation_action is Yes. + <p>The intent of this setting is to eliminate failure reports + to the owner of a domain that publishes DMARC p=none by applying + the message transformations that would be applied if the + domain's DMARC policy were stronger.""")), + + ('dmarc_moderation_notice', mm_cfg.Text, (10, WIDTH), 1, + _("""Text to include in any + <a href="?VARHELP=privacy/sender/dmarc_moderation_action" + >rejection notice</a> to + be sent to anyone who posts to this list from a domain + with a DMARC Reject%(quarantine)s Policy.""")), + + ('dmarc_wrapped_message_text', mm_cfg.Text, (10, WIDTH), 1, + _("""If dmarc_moderation_action applies and is Wrap Message, + and this text is provided, the text will be placed in a + separate text/plain MIME part preceding the original message + part in the wrapped message."""), + + _("""A wrapped message will either be a multipart/mixed message + with up to four sub-parts; a text/plain part containing + msg_header, a text/plain part containing + dmarc_wrapped_message_text, a message/rfc822 part containing the + original message and a text/plain part containing msg_footer, or + a message/rfc822 message containing only the original message if + none of the other parts are applicable.""")), + + ('equivalent_domains', mm_cfg.Text, (10, WIDTH), 1, + _("""A 'two dimensional' list of email address domains which are + considered equivalent when checking if a post is from a list + member."""), + + _("""If two poster addresses with the same local part but + different domains are to be considered equivalents for list + membership tests, the domains are put here. The format is + one or more groups of equivalent domains. Within a group, + the domains are separated by commas and multiple groups are + separated by semicolons. White space is ignored. + <p>For example:<pre> + example.com,mail.example.com;mac.com,me.com,icloud.com + </pre> + <p>In this example, if user@example.com is a list member, + a post from user@mail.example.com will be treated as if it is + from user@example.com for list membership/moderation purposes, + and likewise, if user@me.com is a list member, posts from + user@mac.com or user@icloud.com will be treated as if from + user@me.com. + <p>Note that the poster's address is first tested for list + membership, and the equivalent domain addresses are only tested + if the poster's address is not that of a member. + <p>Also note that moderation of the equivalent domain address + will apply to the post, but other options such as 'ack' or + 'not metoo' will not.""")), + _('Non-member filters'), ('accept_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, @@ -373,8 +535,8 @@ class Privacy(GUIBase): ('max_num_recipients', mm_cfg.Number, 5, 0, _('Ceiling on acceptable number of recipients for a posting.'), - _('''If a posting has this number, or more, of recipients, it is - held for admin approval. Use 0 for no ceiling.''')), + _("""If a posting has this number, or more, of recipients, it is + held for admin approval. Use 0 for no ceiling.""")), ] spam_rtn = [ @@ -399,7 +561,7 @@ class Privacy(GUIBase): case, each rule is matched in turn, with processing stopped after the first match. - Note that headers are collected from all the attachments + Note that headers are collected from all the attachments (except for the mailman administrivia message) and matched against the regular expressions. With this feature, you can effectively sort out messages with dangerous file @@ -442,6 +604,11 @@ class Privacy(GUIBase): # an option. if property == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE: val += 1 + if (property == 'dmarc_moderation_action' and + val < mm_cfg.DEFAULT_DMARC_MODERATION_ACTION): + doc.addError(_("""dmarc_moderation_action must be >= the configured + default value.""")) + val = mm_cfg.DEFAULT_DMARC_MODERATION_ACTION setattr(mlist, property, val) # We need to handle the header_filter_rules widgets specially, but @@ -492,9 +659,20 @@ class Privacy(GUIBase): doc.addError(_("""Header filter rules require a pattern. Incomplete filter rules will be ignored.""")) continue - # Make sure the pattern was a legal regular expression + # Make sure the pattern was a legal regular expression. + # Convert it to unicode if necessary. + mo = re.match('.*charset=([-_a-z0-9]+)', + os.environ.get('CONTENT_TYPE', ''), + re.IGNORECASE + ) + if mo: + cset = mo.group(1) + else: + cset = Utils.GetCharSet(mlist.preferred_language) try: - re.compile(pattern) + upattern = Utils.xml_to_unicode(pattern, cset) + re.compile(upattern) + pattern = upattern except (re.error, TypeError): safepattern = Utils.websafe(pattern) doc.addError(_("""The header filter rule pattern 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 |