def process(mlist, msg, msgdata): if msgdata.get('approved'): return # Before anything else, check DMARC if necessary. msgdata['from_is_list'] = 0 dn, addr = parseaddr(msg.get('from')) if addr and mlist.dmarc_moderation_action > 0: if Utils.IsDMARCProhibited(mlist, addr): # Note that for dmarc_moderation_action, 0 = Accept, # 1 = Munge, 2 = Wrap, 3 = Reject, 4 = Discard if mlist.dmarc_moderation_action == 1: msgdata['from_is_list'] = 1 elif mlist.dmarc_moderation_action == 2: msgdata['from_is_list'] = 2 elif mlist.dmarc_moderation_action == 3: # Reject text = mlist.dmarc_moderation_notice if text: text = Utils.wrap(text) else: text = Utils.wrap( _("""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)s.""")) raise Errors.RejectMessage, text elif mlist.dmarc_moderation_action == 4: raise Errors.DiscardMessage # Then, is the poster a member or not? for sender in msg.get_senders(): if mlist.isMember(sender): break else: sender = None if sender: # If the member's moderation flag is on, then perform the moderation # action. if mlist.getMemberOption(sender, mm_cfg.Moderate): # Note that for member_moderation_action, 0==Hold, 1=Reject, # 2==Discard if mlist.member_moderation_action == 0: # Hold. BAW: WIBNI we could add the member_moderation_notice # to the notice sent back to the sender? msgdata['sender'] = sender Hold.hold_for_approval(mlist, msg, msgdata, ModeratedMemberPost) elif mlist.member_moderation_action == 1: # Reject text = mlist.member_moderation_notice if text: text = Utils.wrap(text) else: # Use the default RejectMessage notice string text = None raise Errors.RejectMessage, text elif mlist.member_moderation_action == 2: # Discard. BAW: Again, it would be nice if we could send a # discard notice to the sender raise Errors.DiscardMessage else: assert 0, 'bad member_moderation_action' # Should we do anything explict to mark this message as getting past # this point? No, because further pipeline handlers will need to do # their own thing. return else: sender = msg.get_sender() # From here on out, we're dealing with non-members. listname = mlist.internal_name() if matches_p(sender, mlist.accept_these_nonmembers, listname): return if matches_p(sender, mlist.hold_these_nonmembers, listname): Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonMemberPost) # No return if matches_p(sender, mlist.reject_these_nonmembers, listname): do_reject(mlist) # No return if matches_p(sender, mlist.discard_these_nonmembers, listname): do_discard(mlist, msg) # No return # Okay, so the sender wasn't specified explicitly by any of the non-member # moderation configuration variables. Handle by way of generic non-member # action. assert 0 <= mlist.generic_nonmember_action <= 4 if mlist.generic_nonmember_action == 0 or msgdata.get('fromusenet'): # Accept return elif mlist.generic_nonmember_action == 1: Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonMemberPost) elif mlist.generic_nonmember_action == 2: do_reject(mlist) elif mlist.generic_nonmember_action == 3: do_discard(mlist, msg)
def process(mlist, msg, msgdata): # Before anything else, check DMARC if necessary. We do this as early # as possible so reject/discard actions trump other holds/approvals and # wrap/munge actions get flagged even for approved messages. # But not for owner mail which should not be subject to DMARC reject or # discard actions. if not msgdata.get('toowner'): msgdata['from_is_list'] = 0 dn, addr = parseaddr(msg.get('from')) if addr and mlist.dmarc_moderation_action > 0: if Utils.IsDMARCProhibited(mlist, addr): # Note that for dmarc_moderation_action, 0 = Accept, # 1 = Munge, 2 = Wrap, 3 = Reject, 4 = Discard if mlist.dmarc_moderation_action == 1: msgdata['from_is_list'] = 1 elif mlist.dmarc_moderation_action == 2: msgdata['from_is_list'] = 2 elif mlist.dmarc_moderation_action == 3: # Reject text = mlist.dmarc_moderation_notice if text: text = Utils.wrap(text) else: listowner = mlist.GetOwnerEmail() text = Utils.wrap( _("""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)s.""")) raise Errors.RejectMessage, text elif mlist.dmarc_moderation_action == 4: raise Errors.DiscardMessage # Get member address if any. for sender in msg.get_senders(): if mlist.isMember(sender): break else: sender = msg.get_sender() if (mlist.member_verbosity_threshold > 0 and Utils.IsVerboseMember(mlist, sender)): mlist.setMemberOption(sender, mm_cfg.Moderate, 1) syslog('vette', '%s: Automatically Moderated %s for verbose postings.', mlist.real_name, sender) if msgdata.get('approved'): return # First do site hard coded header spam checks for header, regex in mm_cfg.KNOWN_SPAMMERS: cre = re.compile(regex, re.IGNORECASE) for value in msg.get_all(header, []): mo = cre.search(value) if mo: # we've detected spam, so throw the message away raise SpamDetected # Now do header_filter_rules # TK: Collect headers in sub-parts because attachment filename # extension may be a clue to possible virus/spam. headers = u'' # Get the character set of the lists preferred language for headers lcset = Utils.GetCharSet(mlist.preferred_language) for p in msg.walk(): headers += getDecodedHeaders(p, lcset) for patterns, action, empty in mlist.header_filter_rules: if action == mm_cfg.DEFER: continue for pattern in patterns.splitlines(): if pattern.startswith('#'): continue # ignore 'empty' patterns if not pattern.strip(): continue pattern = Utils.xml_to_unicode(pattern, lcset) pattern = normalize(mm_cfg.NORMALIZE_FORM, pattern) try: mo = re.search(pattern, headers, re.IGNORECASE | re.MULTILINE | re.UNICODE) except (re.error, TypeError): syslog('error', 'ignoring header_filter_rules invalid pattern: %s', pattern) if mo: if action == mm_cfg.DISCARD: raise Errors.DiscardMessage if action == mm_cfg.REJECT: if msgdata.get('toowner'): # Don't send rejection notice if addressed to '-owner' # because it may trigger a loop of notices if the # sender address is forged. We just discard it here. raise Errors.DiscardMessage raise Errors.RejectMessage( _('Message rejected by filter rule match')) if action == mm_cfg.HOLD: if msgdata.get('toowner'): # Don't hold '-owner' addressed message. We just # pass it here but list-owner can set this to be # discarded on the GUI if he wants. return hold_for_approval(mlist, msg, msgdata, HeaderMatchHold(pattern)) if action == mm_cfg.ACCEPT: return