def do_exclude(mlist, msg, msgdata, recips):
    # regular_exclude_lists are the other mailing lists on this mailman
    # installation whose members are excluded from the regular (non-digest)
    # delivery of this list if those list addresses appear in To: or Cc:
    # headers.
    if not mlist.regular_exclude_lists:
        return recips
    recips = set(recips)
    destinations = email.utils.getaddresses(
        msg.get_all('to', []) + msg.get_all('cc', []))
    destinations = [y.lower() for x, y in destinations]
    for listname in mlist.regular_exclude_lists:
        listname = listname.lower()
        if listname not in destinations:
            continue
        listlhs, hostname = listname.split('@')
        if listlhs == mlist.internal_name():
            syslog('error', 'Exclude list %s is a self reference.', listname)
            continue
        try:
            slist = MailList(listlhs, lock=False)
        except MMUnknownListError:
            syslog('error', 'Exclude list %s not found.', listname)
            continue
        if not mm_cfg.ALLOW_CROSS_DOMAIN_SIBLING \
           and slist.host_name != hostname:
            syslog('error', 'Exclude list %s is not in the same domain.',
                   listname)
            continue
        if mlist.regular_exclude_ignore:
            for sender in msg.get_senders():
                if slist.isMember(sender):
                    break
                for sender in Utils.check_eq_domains(sender,
                                                     slist.equivalent_domains):
                    if slist.isMember(sender):
                        break
                if slist.isMember(sender):
                    break
            else:
                continue
        srecips = set([
            slist.getMemberCPAddress(m) for m in slist.getRegularMemberKeys()
            if slist.getDeliveryStatus(m) == ENABLED
        ])
        recips -= srecips
    return list(recips)
示例#2
0
def process(mlist, msg, msgdata):
    if msgdata.get('approved'):
        return
    # Is the poster a member or not?
    for sender in msg.get_senders():
        if mlist.isMember(sender):
            break
        for sender in Utils.check_eq_domains(sender, mlist.equivalent_domains):
            if mlist.isMember(sender):
                break
        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 mlist.GetPattern(sender,
                        mlist.accept_these_nonmembers,
                        at_list='accept_these_nonmembers'):
        return
    if mlist.GetPattern(sender,
                        mlist.hold_these_nonmembers,
                        at_list='hold_these_nonmembers'):
        Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonMemberPost)
        # No return
    if mlist.GetPattern(sender,
                        mlist.reject_these_nonmembers,
                        at_list='reject_these_nonmembers'):
        do_reject(mlist)
        # No return
    if mlist.GetPattern(sender,
                        mlist.discard_these_nonmembers,
                        at_list='discard_these_nonmembers'):
        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)
示例#3
0
def process(mlist, msg, msgdata):
    if msgdata.get('approved'):
        return
    # Deal with encrypted messages

    encrypted_gpg = False
    encrypted_smime = False
    signed = False
    key_ids = []
    signedByMember = False
    # To record with which properties we received this message.
    # This will be important later when distributing it: we want
    # to be able to support policies like "was incoming signed?
    # then distribute signed."
    msgdata['encrypted_gpg'] = False
    msgdata['encrypted_smime'] = False
    msgdata['signed_gpg'] = False
    msgdata['signed_smime'] = False

    # legal values are:
    #    0 = "No"
    #    1 = "Voluntary"
    #    2 = "Mandatory"
    if mlist.encrypt_policy!=0:
        # if msg is encrypted, we should decrypt. Try both supported types.
        (encrypted_gpg, signed, key_ids) = decryptGpg(mlist, msg, msgdata)
        (encrypted_smime, signedByMember) = decryptSmime(mlist, msg, msgdata)
        if encrypted_gpg:
            msgdata['encrypted_gpg'] = True
        if encrypted_smime:
            msgdata['encrypted_smime'] = True

        if mlist.encrypt_policy==2 and not encrypted_gpg and not encrypted_smime:
            syslog('gpg','Throwing RejectMessage exception: Message has to be GPG encrypted')
            raise Errors.RejectMessage, "Message has to be encrypted!"

    if mlist.sign_policy!=0 and not signed:
        # PGP signature matters, we have not checked while decrypting
        gh = GPGUtils.GPGHelper(mlist)
        payload = ''
        payloadmsg = None
        signatures = []
        if msg.get_content_type()=='multipart/signed' and msg.get_param('protocol')=='application/pgp-signature' and msg.is_multipart():
            # handle detached signatures, these look like:
            #
            # Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="x0ZPnva+gsdVsg/k"
            # Content-Disposition: inline
            #
            #
            # --x0ZPnva+gsdVsg/k
            # Content-Type: text/plain; charset=us-ascii
            # Content-Disposition: inline
            #
            # hello
            #
            # --x0ZPnva+gsdVsg/k
            # Content-Type: application/pgp-signature; name="signature.asc"
            # Content-Description: Digital signature
            # Content-Disposition: inline
            #
            # -----BEGIN PGP SIGNATURE-----
            # Version: GnuPG v1.2.5 (GNU/Linux)
            #
            # iD8DBQFCQDTGPSnqOAwU/4wRAsoZAKDtN6Pn1dXjC/DAQhqOLHNI6VfNigCfaDPs
            # FRJlhlGvyhkpx4soGR+CLxE=
            # =AmS5
            # -----END PGP SIGNATURE-----
            #
            # --x0ZPnva+gsdVsg/k--
            #
            # for verification, use payload INCLUDING MIME header:
            #
            # 'Content-Type: text/plain; charset=us-ascii
            #  Content-Disposition: inline
            #
            #  hello
            # '
            # Thanks Wessel Dankers for hint.

            for submsg in msg.get_payload():
                if submsg.get_content_type()=='application/pgp-signature':
                    signatures.append(submsg.get_payload())
                else:
                    if not payload:
                        # yes, including headers
                        payload = submsg.as_string()
                    else:
                        # we only deal with exactly one payload part and one or more signatures parts
                        syslog('gpg','multipart/signed message with more than one body')
                        do_discard(mlist, msg)
        elif msg.get_content_type()=='text/plain' and not msg.is_multipart():
             # handle inline signature; message looks like e.g.
             #
             # Content-Type: text/plain; charset=iso-8859-1
             # Content-Disposition: inline
             # Content-Transfer-Encoding: 8bit
             # MIME-Version: 1.0
             #
             # -----BEGIN PGP SIGNED MESSAGE-----
             # Hash: SHA1
             #
             # blah blah
             #
             # -----BEGIN PGP SIGNATURE-----
             # Version: GnuPG v1.4.0 (GNU/Linux)
             #
             # iD8DBQFCPtWXW5ql+IAeqTIRAirPAK....
             # -----END PGP SIGNATURE-----
             signatures = [None]
             payload = msg.get_payload(decode=True)
             payloadmsg = msg
        elif msg.get_content_type()=='multipart/alternative' and msg.is_multipart():
            #GPG signed plaintext with HTML version
            for submsg in msg.get_payload():
                if submsg.get_content_type()=='text/plain':
                    if not payload:
                        # text without headers
                        signatures = [None]
                        payload = submsg.get_payload(decode=True)
                        payloadmsg = submsg
                    else:
                        # we only deal with exactly one payload part
                        Utils.report_submission(msg['Message-ID'],'Confused by MIME message structure, discarding.')
                        syslog('gpg','multipart/alternative message with more than one plaintext')                        
                        do_discard(mlist, msg)
        elif msg.get_content_type()=='multipart/mixed' and msg.is_multipart():
            #GPG signed plaintext with attachments. Use first plaintext part (more text attachments are perfectly valid here)
            #TODO submsg may be multipart/alternative itself or whatever structure - is that used in the wild anywhere?
            for submsg in msg.get_payload():
                if submsg.get_content_type()=='text/plain':
                    # text without headers
                    payload = submsg.get_payload(decode=True)
                    payloadmsg = submsg
                    if payload.lstrip().startswith('-----BEGIN PGP '):
                        signatures = [None]
                        break
                elif submsg.get_content_type() in set(['application/pgp-encrypted', 'application/pgp']):
                    signatures = [None]
                    payload = submsg.get_payload(decode=True)
                    payloadmsg = submsg
                    submsg.set_type('text/plain; charset="utf-8"')
                    break
                elif submsg.get_content_type()=='multipart/alternative' and submsg.is_multipart():
                    #GPG signed plaintext with HTML version
                    for subsubmsg in submsg.get_payload():
                        if subsubmsg.get_content_type()=='text/plain':
                            if not payload:
                                # text without headers
                                payload = subsubmsg.get_payload(decode=True)
                                if payload.lstrip().startswith('-----BEGIN PGP '):
                                    signatures = [None]
                                    payloadmsg = subsubmsg
                            else:
                                # we only deal with exactly one payload part
                                syslog('gpg','multipart/alternative message with more than one plaintext')
                                Utils.report_submission(msg['Message-ID'],'Confused by MIME message structure, discarding.')
                                do_discard(mlist, msg)
                    if len(signatures) == 0:
                        payload = None
                        payloadmsg = None
                    elif payload:
                        break

        #TODO S/MIME broken atm
        #for signature in signatures:
        if signatures:
             syslog('gpg', "gonna verify payload with signature '%s'", signatures[0])
             key_ids.extend(gh.verifyMessage(payload, signatures[0],
                                             decrypted_checksum=mm_cfg.SCRUBBER_ADD_PAYLOAD_HASH_FILENAME))
        else:
            Utils.report_submission(msg['Message-ID'],'No clearsigned text part found, discarding.')


    if mlist.sign_policy!=0 and not signedByMember:
        # S/MIME signature matters, we have not checked while decrypting
        sm = SMIMEUtils.SMIMEHelper(mlist)
        payload = ''
        signature = ''

        syslog('gpg', "gonna verify SMIME message")
        signedByMember = sm.verifyMessage(msg)
        # raise Errors.NotYetImplemented, "SMIMEUtils doesn't yet do verifyMessage"

    # By now we know whether we have any valid signatures on the message.
    if signedByMember:
        msgdata['signed_smime'] = True
    if key_ids:
        msgdata['signed_gpg'] = True
        if payloadmsg and mm_cfg.SCRUBBER_ADD_PAYLOAD_HASH_FILENAME:
            sha = key_ids.pop(0)
            msgfrom = key_ids[0]
            #Kill the message if such text+signature was already posted.
            #Payload(spaces, newlines) is normalized by gpg decryption before hashing.
            if ospath.exists(ospath.join(mlist.archive_dir(),'attachments','links', msgfrom + '_' + sha)):
                Utils.report_submission(msg['Message-ID'],'Detected attempt to resubmit duplicate clearsigned text, discarding.')
                syslog('gpg','Attempt to pass clearsigned duplicate fp: %s sha1: %s' % (msgfrom, sha))
                do_discard(mlist, msg)

            payloadmsg.add_header(mm_cfg.SCRUBBER_SHA1SUM_HEADER, sha)
            payloadmsg.add_header(mm_cfg.SCRUBBER_SIGNEDBY_HEADER, msgfrom)


    if mlist.sign_policy!=0:
        if not key_ids and not signedByMember and mlist.sign_policy==2:
            Utils.report_submission(msg['Message-ID'],'Signature verification on clearsigned text failed, discarding. Review the message in your sent mail folder for wordwrap or similar mutilations of clearsigned text.')
            syslog('gpg','No valid signatures on message')
            do_discard(mlist, msg)

        if key_ids:
            gh = GPGUtils.GPGHelper(mlist)
            senderMatchesKey = False
            for key_id in key_ids:
                key_addrs = gh.getMailaddrs(key_id)
                for sender in msg.get_senders():
                    for key_addr in key_addrs:
                        if sender==key_addr:
                            senderMatchesKey = True
                            break
            if not senderMatchesKey:
                syslog('gpg','Message signed by key %s which does not match message sender %s, passing anyway' %(key_ids,msg.get_senders()))
                #temp fix
                #do_discard(mlist, msg)
        #we use gpg keyring in lieu of memberlist
        signedByMember = True
#         for user in mlist.getMembers():
#             syslog('gpg','Checking signature: listmember %s',user)
#             for key_id in key_ids:
#                 syslog('gpg','Checking signature: key_id %s',key_id)
#                 try:
#                     ks=mlist.getGPGKeyIDs(user)
#                 except:
#                     ks=None
#                 if ks:
#                     for k in mlist.getGPGKeyIDs(user):
#                         syslog('gpg','Checking signature: keyid of listmember is %s',k)
#                         if k==key_id:
#                             signedByMember = True
#                             break

    # done dealing with most of gpg stuff

    # Is the poster a member or not?
    for sender in msg.get_senders():
        if mlist.isMember(sender):
            break
        for sender in Utils.check_eq_domains(sender,
                          mlist.equivalent_domains):
            if mlist.isMember(sender):
                break
        if mlist.isMember(sender):
            break
    else:
        sender = None
    if sender:
        # If posts need to be PGP signed, process signature.
        if mlist.sign_policy==2:
            if signedByMember==True:
                syslog('gpg','Message properly signed: distribute')
                return
            else:
                do_discard(mlist, msg)

        # 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)