Esempio n. 1
0
def verpdeliver(mlist, msg, msgdata, envsender, failures, conn):
    for recip in msgdata['recips']:
        # We now need to stitch together the message with its header and
        # footer.  If we're VERPIng, we have to calculate the envelope sender
        # for each recipient.  Note that the list of recipients must be of
        # length 1.
        #
        # BAW: ezmlm includes the message number in the envelope, used when
        # sending a notification to the user telling her how many messages
        # they missed due to bouncing.  Neat idea.
        msgdata['recips'] = [recip]
        # Make a copy of the message and decorate + delivery that
        msgcopy = copy.deepcopy(msg)
        Decorate.process(mlist, msgcopy, msgdata)
        # Calculate the envelope sender, which we may be VERPing
        if msgdata.get('verp'):
            bmailbox, bdomain = Utils.ParseEmail(envsender)
            rmailbox, rdomain = Utils.ParseEmail(recip)
            if rdomain is None:
                # The recipient address is not fully-qualified.  We can't
                # deliver it to this person, nor can we craft a valid verp
                # header.  I don't think there's much we can do except ignore
                # this recipient.
                syslog('smtp', 'Skipping VERP delivery to unqual recip: %s',
                       recip)
                continue
            d = {'bounces': bmailbox,
                 'mailbox': rmailbox,
                 'host'   : DOT.join(rdomain),
                 }
            envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain))
        if mlist.personalize == 2:
            # When fully personalizing, we want the To address to point to the
            # recipient, not to the mailing list
            del msgcopy['to']
            name = None
            if mlist.isMember(recip):
                name = mlist.getMemberName(recip)
            if name:
                # Convert the name to an email-safe representation.  If the
                # name is a byte string, convert it first to Unicode, given
                # the character set of the member's language, replacing bad
                # characters for which we can do nothing about.  Once we have
                # the name as Unicode, we can create a Header instance for it
                # so that it's properly encoded for email transport.
                charset = Utils.GetCharSet(mlist.getMemberLanguage(recip))
                if charset == 'us-ascii':
                    # Since Header already tries both us-ascii and utf-8,
                    # let's add something a bit more useful.
                    charset = 'iso-8859-1'
                charset = Charset(charset)
                codec = charset.input_codec or 'ascii'
                if not isinstance(name, UnicodeType):
                    name = unicode(name, codec, 'replace')
                name = Header(name, charset).encode()
                msgcopy['To'] = formataddr((name, recip))
            else:
                msgcopy['To'] = recip
        # We can flag the mail as a duplicate for each member, if they've
        # already received this message, as calculated by Message-ID.  See
        # AvoidDuplicates.py for details.
        del msgcopy['x-mailman-copy']
        if msgdata.get('add-dup-header', {}).has_key(recip):
            msgcopy['X-Mailman-Copy'] = 'yes'
        # GPG encryption
        if 'encrypted_gpg' in msgdata and msgdata['encrypted_gpg'] and mlist.encrypt_policy!=0:
            # Encryption is not forbidden in config
            try:
                keyids=mlist.getGPGKeyIDs(recip)
            except:
                keyids=None
            if enforceEncryptPolicy(mlist,msg,msgdata) and keyids==None:
                syslog('gpg','Encryption mandatory, but no keys found for %s: '\
                        'Discarding message',recip)
                failures[recip]=(550,'Encryption mandatory, but no keys found')
                return
            gh = GPGUtils.GPGHelper(mlist)
            # Extract / generate plaintext
            gpg_use_inlineformat = False # TODO: Create config setting
            if not msgcopy.is_multipart() and gpg_use_inlineformat:
                plaintext=msgcopy.get_payload()
            else:
                if not msgcopy.is_multipart():
                    plaintext = 'Content-Type: %s\n' \
                        'Content-Disposition: inline\n' \
                        % msgcopy.get('Content-Type')
                    if not msgcopy.get('Content-Transfer-Encoding') is None:
                        plaintext += 'Content-Transfer-Encoding: %s\n' \
                                % msgcopy.get('Content-Transfer-Encoding')
                    plaintext += '\n%s' % msgcopy.get_payload()
                else:
                    hp = HeaderParser()
                    tmp = msgcopy.as_string()
                    tmpmsg = hp.parsestr(tmp)
                    plaintext = 'Content-Type: %s\n' \
                        'Content-Disposition: inline\n\n%s' \
                        % (msgcopy.get('Content-Type'),tmpmsg.get_payload())
            # Do encryption, report errors
            ciphertext = None
            if not keyids is None:
                # Can encrypt.
                # No signing policy, or voluntary and original wasn't signed: just encrypt
                if mlist.sign_policy == 0 or \
                    (mlist.sign_policy==1 and not msgdata['signed_gpg']):
                    ciphertext = gh.encryptMessage(plaintext,keyids)
                else:
                    ciphertext = gh.encryptSignMessage(plaintext,keyids)
                if ciphertext==None:
                    # Must always encrypt, since if we arrived here encrypt_policy
                    # is either Mantatory or (Voluntary and incoming msg was encrypted).
                    syslog('gpg',"Can't encrypt message to %s: " \
                            "Discarding message",keyids)
                    failures[recip]=(550,'Unable to encrypt message')
                    return
            # Compile encrypted message
            if not ciphertext is None:
                if msgcopy.has_key('Content-Transfer-Encoding'):
                    msgcopy.replace_header('Content-Transfer-Encoding','7bit')
                else:
                    msgcopy.add_header('Content-Transfer-Encoding','7bit')
                if not msgcopy.is_multipart() and gpg_use_inlineformat:
                    msgcopy.set_payload(ciphertext)
                    msgcopy.set_param('x-action','pgp-encrypted')
                else:
                    msgcopy.replace_header('Content-Type','multipart/encrypted')
                    msgcopy.set_param('protocol','application/pgp-encrypted')
                    msgcopy.set_payload(None)
                    submsg = Message()
                    submsg.add_header('Content-Type','application/pgp-encrypted')
                    submsg.set_payload('Version: 1\n')
                    msgcopy.attach(submsg)
                    submsg = Message()
                    submsg.add_header('Content-Type','application/octet-stream; name="encrypted.asc"')
                    submsg.add_header('Content-Disposition','inline; filename="encrypted.asc"')
                    submsg.set_payload(ciphertext)
                    msgcopy.attach(submsg)
                syslog('gpg','Sending encrypted message to %s',recip)
            else:
                syslog('gpg','Sending unencrypted message to %s',recip)

        if 'encrypted_smime' in msgdata and msgdata['encrypted_smime'] and mlist.encrypt_policy != 0:
            # FIXME: this is as crude as can be
            sm = SMIMEUtils.SMIMEHelper(mlist)
            recipfile = sm.getSMIMEMemberCertFile(recip)

            if not recipfile:
                failures[recip]=(550,'No S/MIME key found')
                return
            else:
                plaintext=msgcopy.get_payload()
                if not msgcopy.is_multipart():
                    plaintext = msgcopy.get_payload()
                    syslog('gpg', "About to S/MIME encrypt plaintext from singlepart")
                else:
                    # message contains e.g. signature?
                    # FIXME we fetch only the first attachment.  We search for
                    # attachments only 2 levels deep.  That's suboptimal...
                    # perhaps the PGP way (invoking
                    # hp = HeaderParser()
                    # ) is better.
                    submsgs = msgcopy.get_payload()
                    submsg = submsgs[0]
                    if not submsg.is_multipart():
                        plaintext = submsg.get_payload()
                    else:
                        subsubmsgs = submsg.get_payload()
                        subsubmsg = subsubmsgs[0]
                        plaintext = subsubmsg.get_payload()

                    syslog('gpg', "About to S/MIME encrypt plaintext from multipart")

                if mlist.sign_policy == 0 or \
                    (mlist.sign_policy==1 and not msgdata['signed_smime']):
                    ciphertext = sm.encryptMessage(plaintext,recipfile)
                else:
                    ciphertext = sm.encryptSignMessage(plaintext,recipfile)

                # deal with both header and body-part of ciphertext
                (header, body) = ciphertext.split("\n\n", 1)
                for l in header.split("\n"):
                    (k, v) = l.split(": ", 1)

                    # behave sane with borken openssl like 0.9.7e (e.g. Debian's 0.9.7e-3sarge1)
                    # openssl 0.9.8a-4a0.sarge.1 is known to work OK.
                    # A borken openssl (and therefore sm.encryptMessage) returns
                    #  Content-Type: application/x-pkcs7-mime; name="smime.p7m"
                    # while we need a
                    #  Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"
                    if v == 'application/x-pkcs7-mime; name="smime.p7m"':
                        v = 'application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"'

                    try:
                        msgcopy.replace_header(k, v)
                    except KeyError:
                        msgcopy.add_header(k, v)

                msgcopy.set_payload(body)

        # For the final delivery stage, we can just bulk deliver to a party of
        # one. ;)
        bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn)