Пример #1
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     if not as_boolean(config.mailman.hold_digest):
         return False
     # Convert the header value to a str because it may be an
     # email.header.Header instance.
     subject = str(msg.get('subject', '')).strip()
     if DIGRE.search(subject):
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('Message has a digest subject'))
         return True
         # Get the masthead, but without emails.
     mastheadtxt = getUtility(ITemplateLoader).get(
         'list:member:digest:masthead', mlist)
     mastheadtxt = wrap(
         expand(
             mastheadtxt, mlist,
             dict(
                 display_name=mlist.display_name,
                 listname='',
                 list_id=mlist.list_id,
                 request_email='',
                 owner_email='',
             )))
     msgtext = ''
     for part in msg.walk():
         if part.get_content_maintype() == 'text':
             cset = part.get_content_charset('utf-8')
             msgtext += part.get_payload(decode=True).decode(
                 cset, errors='replace')
     matches = 0
     lines = mastheadtxt.splitlines()
     for line in lines:
         line = line.strip()
         if not line:
             continue
         if msgtext.find(line) >= 0:
             matches += 1
     if matches >= int(config.mailman.masthead_threshold):
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('Message quotes digest boilerplate'))
         return True
     return False
Пример #2
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     # The MemberModeration rule misses unconditionally if any of the
     # senders are banned.
     ban_manager = IBanManager(mlist)
     for sender in msg.senders:
         if ban_manager.is_banned(sender):
             return False
     member = _find_sender_member(mlist, msg)
     if member is None:
         return False
     action = (mlist.default_member_action
               if member.moderation_action is None else
               member.moderation_action)
     if action is Action.defer:
         # The regular moderation rules apply.
         return False
     elif action is not None:
         # We must stringify the moderation action so that it can be
         # stored in the pending request table.
         msgdata['member_moderation_action'] = action.name
         msgdata['moderation_sender'] = sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('The message comes from a moderated member'))
         return True
     # The sender is not a member so this rule does not match.
     return False
Пример #3
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     # Collect all the headers in all subparts.
     headers = []
     for p in msg.walk():
         headers.extend(p.get_all(self.header, []))
     for value in headers:
         if isinstance(value, Header):
             value = value.encode()
         # RFC2047 decode, but don't change value as it affects the msg.
         new_value = str(make_header(decode_header(value)))
         try:
             mo = re.search(self.pattern, new_value, re.IGNORECASE)
         except re.error as error:
             log.error(
                 "Invalid regexp '{}' in header_matches for {}: {}".format(
                     self.pattern, mlist.list_id, error.msg))
             return False
         else:
             if mo:
                 msgdata['moderation_sender'] = msg.sender
                 with _.defer_translation():
                     # This will be translated at the point of use.
                     msgdata.setdefault('moderation_reasons', []).append(
                         (_('Header "{}" matched a header rule'),
                          str(value)))
                 return True
     return False
Пример #4
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     # Implicit destination checking must be enabled in the mailing list.
     if not mlist.require_explicit_destination:
         return False
     # Messages gated from NNTP will always have an implicit destination so
     # are never checked.
     if msgdata.get('fromusenet'):
         return False
     # Calculate the list of acceptable aliases.  If the alias starts with
     # a caret (i.e. ^), then it's a regular expression to match against.
     aliases = set()
     alias_patterns = set()
     # Adapt the mailing list to the appropriate interface.
     alias_set = IAcceptableAliasSet(mlist)
     for alias in alias_set.aliases:
         if alias.startswith('^'):
             alias_patterns.add(alias)
         else:
             aliases.add(alias)
     # Add the list's posting address, i.e. the explicit address, to the
     # set of acceptable aliases.
     aliases.add(mlist.posting_address)
     # Look at all the recipients.  If the recipient is any acceptable
     # alias (or the explicit posting address), then this rule does not
     # match.  If not, then add it to the set of recipients we'll check
     # against the alias patterns later.
     recipients = set()
     for header in ('to', 'cc', 'resent-to', 'resent-cc'):
         for fullname, address in getaddresses(msg.get_all(header, [])):
             if isinstance(address, bytes):
                 address = address.decode('ascii')
             address = address.lower()
             if address in aliases:
                 return False
             recipients.add(address)
     # Now for all alias patterns, see if any of the recipients matches a
     # pattern.  If so, then this rule does not match.
     for pattern in alias_patterns:
         escaped = re.escape(pattern)
         for recipient in recipients:
             try:
                 if re.match(pattern, recipient, re.IGNORECASE):
                     return False
             except re.error:
                 # The pattern is a malformed regular expression.  Try
                 # matching again with the pattern escaped.
                 with suppress(re.error):
                     if re.match(escaped, recipient, re.IGNORECASE):
                         return False
     # Nothing matched.
     msgdata['moderation_sender'] = msg.sender
     with _.defer_translation():
         # This will be translated at the point of use.
         msgdata.setdefault('moderation_reasons', []).append(
             _('Message has implicit destination'))
     return True
Пример #5
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     if mlist.emergency and not msgdata.get('moderator_approved'):
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('Emergency moderation is in effect for this list'))
         return True
     return False
Пример #6
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     if mlist.newsgroup_moderation is NewsgroupModeration.moderated:
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('Post to a moderated newsgroup gateway'))
         return True
     return False
Пример #7
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     if msg.sender:
         return False
     else:
         msgdata['moderation_sender'] = 'N/A'
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('The message has no valid senders'))
         return True
Пример #8
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     ban_manager = IBanManager(mlist)
     for sender in msg.senders:
         if ban_manager.is_banned(sender):
             msgdata['moderation_sender'] = sender
             with _.defer_translation():
                 # This will be translated at the point of use.
                 msgdata.setdefault('moderation_reasons', []).append(
                     (_('Message sender {} is banned from this list'),
                      sender))
             return True
     return False
Пример #9
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     # Has this message already been posted to this list?
     list_posts = set(value.strip().lower()
                      for value in msg.get_all('list-post', []))
     if mlist.posting_address in list_posts:
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('Message has already been posted to this list'))
         return True
     return False
Пример #10
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     # Convert the header value to a str because it may be an
     # email.header.Header instance.
     subject = str(msg.get('subject', '')).strip()
     if subject == '':
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons',
                                []).append(_('Message has no subject'))
         return True
     return False
Пример #11
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     if mlist.dmarc_mitigate_action is DMARCMitigateAction.no_mitigation:
         # Don't bother to check if we're not going to do anything.
         return False
     display_name, address = parseaddr(str(msg.get('from', '')))
     if maybe_mitigate(mlist, address):
         # If dmarc_mitigate_action is discard or reject, this rule fires
         # and jumps to the 'moderation' chain to do the actual discard.
         # Otherwise, the rule misses but sets a flag for the dmarc handler
         # to do the appropriate action.
         msgdata['dmarc'] = True
         if mlist.dmarc_mitigate_action is DMARCMitigateAction.discard:
             msgdata['dmarc_action'] = 'discard'
             with _.defer_translation():
                 # This will be translated at the point of use.
                 msgdata.setdefault('moderation_reasons',
                                    []).append(_('DMARC moderation'))
         elif mlist.dmarc_mitigate_action is DMARCMitigateAction.reject:
             listowner = mlist.owner_address  # noqa F841
             with _.defer_translation():
                 # This will be translated at the point of use.
                 reason = (mlist.dmarc_moderation_notice or _(
                     'You are not allowed to post to this mailing '
                     'list From: a domain which publishes a DMARC '
                     'policy of reject or quarantine, 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}.'))
             msgdata.setdefault('moderation_reasons',
                                []).append(wrap(reason))
             msgdata['dmarc_action'] = 'reject'
         else:
             return False
         msgdata['moderation_sender'] = address
         return True
     return False
Пример #12
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     if mlist.max_message_size == 0:
         return False
     assert hasattr(msg, 'original_size'), (
         'Message was not sized on initial parsing.')
     # The maximum size is specified in 1024 bytes.
     if msg.original_size / 1024.0 > mlist.max_message_size:
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 (_('The message is larger than the {} KB maximum size'),
                  mlist.max_message_size))
         return True
     return False
Пример #13
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     # Zero means any number of recipients are allowed.
     if mlist.max_num_recipients == 0:
         return False
     # Figure out how many recipients there are
     recipients = getaddresses(
         msg.get_all('to', []) + msg.get_all('cc', []))
     if len(recipients) >= mlist.max_num_recipients:
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 (_('Message has more than {} recipients'),
                  mlist.max_num_recipients))
         return True
     return False
Пример #14
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     # The list must have the administrivia check enabled.
     if not mlist.administrivia:
         return False
     # First check the Subject text.
     lines_to_check = []
     subject = str(msg.get('subject', ''))
     if subject != '':
         lines_to_check.append(subject)
     # Search only the first text/plain subpart of the message.  There's
     # really no good way to find email commands in any other content type.
     for part in typed_subpart_iterator(msg, 'text', 'plain'):
         payload = part.get_payload()
         lines = payload.splitlines()
         # Count lines without using enumerate() because blank lines in the
         # payload don't count against the maximum examined.
         lineno = 0
         for line in lines:
             line = line.strip()
             if len(line) == 0:
                 continue
             lineno += 1
             if lineno > int(config.mailman.email_commands_max_lines):
                 break
             lines_to_check.append(line)
         # Only look at the first text/plain part.
         break
     # For each line we're checking, split the line into words.  Then see
     # if it looks like a command with the min-to-max number of arguments.
     for line in lines_to_check:
         words = [word.lower() for word in line.split()]
         if words[0] not in EMAIL_COMMANDS:
             # This is not an administrivia command.
             continue
         minargs, maxargs = EMAIL_COMMANDS[words[0]]
         if minargs <= len(words) - 1 <= maxargs:
             msgdata['moderation_sender'] = msg.sender
             with _.defer_translation():
                 # This will be translated at the point of use.
                 msgdata.setdefault('moderation_reasons', []).append(
                     _('Message contains administrivia'))
             return True
     return False
Пример #15
0
def has_matching_bounce_header(mlist, msg, msgdata):
    """Does the message have a matching bounce header?

    :param mlist: The mailing list the message is destined for.
    :param msg: The email message object.
    :return: True if a header field matches a regexp in the
        bounce_matching_header mailing list variable.
    """
    for header, cre, line in _parse_matching_header_opt(mlist):
        for value in msg.get_all(header, []):
            # Convert the header value to a str because it may be an
            # email.header.Header instance.
            if cre.search(str(value)):
                msgdata['moderation_sender'] = msg.sender
                with _.defer_translation():
                    # This will be translated at the point of use.
                    msgdata.setdefault('moderation_reasons', []).append((_(
                        'Header "{}" matched a bounce_matching_header line'),
                        str(value)))
                return True
    return False
Пример #16
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     ban_manager = IBanManager(mlist)
     user_manager = getUtility(IUserManager)
     # The NonmemberModeration rule misses unconditionally if any of the
     # senders are banned.
     for sender in msg.senders:
         if ban_manager.is_banned(sender):
             return False
     if len(msg.senders) == 0:
         with _.defer_translation():
             # This will be translated at the point of use.
             reason = _('No sender was found in the message.')
         _record_action(msgdata, mlist.default_nonmember_action,
                        'No sender', reason)
         return True
     # Every sender email must be a member or nonmember directly.  If it is
     # neither, make the email a nonmembers.
     for sender in msg.senders:
         if (mlist.members.get_member(sender) is None
                 and mlist.nonmembers.get_member(sender) is None):  # noqa
             # The email must already be registered, since this happens in
             # the incoming runner itself.
             address = user_manager.get_address(sender)
             assert address is not None, (
                 'Posting address is not registered: {}'.format(sender))
             mlist.subscribe(address, MemberRole.nonmember)
     # Check to see if any of the sender emails is already a member.  If
     # so, then this rule misses.
     member = _find_sender_member(mlist, msg)
     if member is not None:
         return False
     # Do nonmember moderation check.
     for sender in msg.senders:
         nonmember = mlist.nonmembers.get_member(sender)
         assert nonmember is not None, (
             "sender didn't get subscribed as a nonmember".format(sender))
         # Check the '*_these_nonmembers' properties first.  XXX These are
         # legacy attributes from MM2.1; their database type is 'pickle' and
         # they should eventually get replaced.
         for action_name in ('accept', 'hold', 'reject', 'discard'):
             legacy_attribute_name = '{}_these_nonmembers'.format(
                 action_name)
             checklist = getattr(mlist, legacy_attribute_name)
             for addr in checklist:
                 if ((addr.startswith('^') and re.match(addr, sender))
                         or addr == sender):  # noqa: W503
                     with _.defer_translation():
                         # This will be translated at the point of use.
                         reason = (
                             _('The sender is in the nonmember {} list'),
                             action_name)
                     _record_action(msgdata, action_name, sender, reason)
                     return True
         action = (mlist.default_nonmember_action
                   if nonmember.moderation_action is None else
                   nonmember.moderation_action)
         if action is Action.defer:
             # The regular moderation rules apply.
             return False
         elif action is not None:
             # We must stringify the moderation action so that it can be
             # stored in the pending request table.
             with _.defer_translation():
                 # This will be translated at the point of use.
                 reason = _('The message is not from a list member')
             _record_action(msgdata, action.name, sender, reason)
             return True
     # The sender must be a member, so this rule does not match.
     return False