예제 #1
0
    def process(self, baton):
        message = util.dict_get_path(baton, self.message_path)

        for key, value in self.headers.items():
            if key in message:
                message.replace_header(key, value)
            else:
                message.add_header(key, value)

        return baton
예제 #2
0
def encrypt_all_payloads(message, gpg_to_cmdline):
    encrypted_payloads = list()
    if type(message.get_payload()) == str:
        if (
            cfg.has_key("default")
            and cfg["default"].has_key("mime_conversion")
            and cfg["default"]["mime_conversion"] == "yes"
        ):
            # Convert a plain text email into PGP/MIME attachment style.  Modeled after enigmail.
            submsg1 = email.message.Message()
            submsg1.set_payload("Version: 1\n")
            submsg1.set_type("application/pgp-encrypted")
            submsg1.set_param("PGP/MIME version identification", "", "Content-Description")

            submsg2 = email.message.Message()
            submsg2.set_type("application/octet-stream")
            submsg2.set_param("name", "encrypted.asc")
            submsg2.set_param("OpenPGP encrypted message", "", "Content-Description")
            submsg2.set_param("inline", "", "Content-Disposition")
            submsg2.set_param("filename", "encrypted.asc", "Content-Disposition")

            # WTF!  It seems to swallow the first line.  Not sure why.  Perhaps
            # it's skipping an imaginary blank line someplace. (ie skipping a header)
            # Workaround it here by prepending a blank line.
            submsg2.set_payload("\n" + message.get_payload())

            message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)"

            # Use this just to generate a MIME boundary string.
            junk_msg = MIMEMultipart()
            junk_str = junk_msg.as_string()  # WTF!  Without this, get_boundary() will return 'None'!
            boundary = junk_msg.get_boundary()

            # This also modifies the boundary in the body of the message, ie it gets parsed.
            if message.has_key("Content-Type"):
                message.replace_header(
                    "Content-Type",
                    'multipart/encrypted; protocol="application/pgp-encrypted";\nboundary="%s"\n' % boundary,
                )
            else:
                message["Content-Type"] = (
                    'multipart/encrypted; protocol="application/pgp-encrypted";\nboundary="%s"\n' % boundary
                )

            return [submsg1, encrypt_payload(submsg2, gpg_to_cmdline)]
        else:
            # Do a simple in-line PGP conversion of a plain text email.
            return encrypt_payload(message, gpg_to_cmdline).get_payload()

    for payload in message.get_payload():
        if type(payload.get_payload()) == list:
            encrypted_payloads.extend(encrypt_all_payloads(payload, gpg_to_cmdline))
        else:
            encrypted_payloads.append(encrypt_payload(payload, gpg_to_cmdline))
    return encrypted_payloads
예제 #3
0
def encrypt_all_payloads_mime(message, gpg_to_cmdline):

    # Convert a plain text email into PGP/MIME attachment style.  Modeled after enigmail.
    submsg1 = email.message.Message()
    submsg1.set_payload("Version: 1\n")
    submsg1.set_type("application/pgp-encrypted")
    submsg1.set_param('PGP/MIME version identification', "",
                      'Content-Description')

    submsg2 = email.message.Message()
    submsg2.set_type("application/octet-stream")
    submsg2.set_param('name', "encrypted.asc")
    submsg2.set_param('OpenPGP encrypted message', "", 'Content-Description')
    submsg2.set_param('inline', "", 'Content-Disposition')
    submsg2.set_param('filename', "encrypted.asc", 'Content-Disposition')

    if type(message.get_payload()) == str:
        # WTF!  It seems to swallow the first line.  Not sure why.  Perhaps
        # it's skipping an imaginary blank line someplace. (ie skipping a header)
        # Workaround it here by prepending a blank line.
        # This happens only on text only messages.
        submsg2.set_payload("\n" + message.get_payload())
        check_nested = True
    else:
        processed_payloads = generate_message_from_payloads(message)
        submsg2.set_payload(processed_payloads.as_string())
        check_nested = False

    message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)"

    # Use this just to generate a MIME boundary string.
    junk_msg = MIMEMultipart()
    junk_str = junk_msg.as_string(
    )  # WTF!  Without this, get_boundary() will return 'None'!
    boundary = junk_msg.get_boundary()

    # This also modifies the boundary in the body of the message, ie it gets parsed.
    if message.has_key('Content-Type'):
        message.replace_header(
            'Content-Type',
            "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n"
            % boundary)
    else:
        message[
            'Content-Type'] = "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary

    return [submsg1, encrypt_payload(submsg2, gpg_to_cmdline, check_nested)]
예제 #4
0
def encrypt_all_payloads_mime( message, gpg_to_cmdline ):

	# Convert a plain text email into PGP/MIME attachment style.  Modeled after enigmail.
	submsg1 = email.message.Message()
	submsg1.set_payload("Version: 1\n")
	submsg1.set_type("application/pgp-encrypted")
	submsg1.set_param('PGP/MIME version identification', "", 'Content-Description' )

	submsg2 = email.message.Message()
	submsg2.set_type("application/octet-stream")
	submsg2.set_param('name', "encrypted.asc")
	submsg2.set_param('OpenPGP encrypted message', "", 'Content-Description' )
	submsg2.set_param('inline', "",                'Content-Disposition' )
	submsg2.set_param('filename', "encrypted.asc", 'Content-Disposition' )

	if type ( message.get_payload() ) == str:
		# WTF!  It seems to swallow the first line.  Not sure why.  Perhaps
		# it's skipping an imaginary blank line someplace. (ie skipping a header)
		# Workaround it here by prepending a blank line.
		# This happens only on text only messages.
		submsg2.set_payload("\n" + message.get_payload())
		check_nested = True
	else:
		processed_payloads = generate_message_from_payloads(message)
		submsg2.set_payload(processed_payloads.as_string())
		check_nested = False

	message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)"

	# Use this just to generate a MIME boundary string.
	junk_msg = MIMEMultipart()
	junk_str = junk_msg.as_string()  # WTF!  Without this, get_boundary() will return 'None'!
	boundary = junk_msg.get_boundary()

    # This also modifies the boundary in the body of the message, ie it gets parsed.
	if message.has_key('Content-Type'):
		message.replace_header('Content-Type', "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary)
	else:
		message['Content-Type'] = "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary

	return [ submsg1, encrypt_payload(submsg2, gpg_to_cmdline, check_nested) ]
예제 #5
0
파일: gmail.py 프로젝트: Mortal/mailtk
def construct_gmail_message(payload):
    message = email.message.Message()
    for header in payload['headers']:
        message[header['name']] = header['value']
    if message.get_content_maintype() == 'multipart':
        message.set_payload(
            [construct_gmail_message(part) for part in payload['parts']])
    else:
        cte = message.get('Content-Transfer-Encoding')
        if cte is not None:
            del message['Content-Transfer-Encoding']
            message['X-Original-Content-Transfer-Encoding'] = cte
        try:
            external_id = payload['body']['attachmentId']
            ct = message.get_content_type()
            message.replace_header('Content-Type', 'text/plain')
            message.set_payload(
                'Attachment with type %s, ID %r omitted; retrieve separately' %
                (ct, external_id))
        except KeyError:
            body = payload['body']['data']
            body += '=' * (4 - len(body) % 4)
            message.set_payload(base64.urlsafe_b64decode(body))
    return message
    def forward(self, email_message, recipient):
        message = email.message_from_string(email_message)

        # replace headers (could do other processing here)
        message.replace_header("From", self.email_address)
        message.replace_header("To", recipient)
        message.replace_header("Subject", "Fwd: " + message["subject"])

        #header = 'To:' + recipient + '\r\n' + 'From: ' + self.email_address + '\r\n' + 'Subject:' + "Fwd: " + message["subject"] + '\r\n'

        # open authenticated SMTP connection and send message with
        # specified envelope from and to addresses
        smtp = smtplib.SMTP(self.smtp_host, self.smtp_port)
        smtp.starttls()
        smtp.login(self.email_address, self.password)
        #smtp.sendmail(self.email_address, recipient, header + '\r\n' + message.as_string())
        smtp.sendmail(self.email_address, recipient, message.as_string())
        smtp.quit()
예제 #7
0
    def _process(self, message, rec=0):
        """
        Recursively scan and filter a MIME message.
        
        _process will scan the passed message part for invalid headers
        as well as mailman signatures and modify them according to
        the global settings.
        
        Generic modifications include:
        
            * fixing of broken **References:** and **In-Reply-To** headers
            
            * generic header filtering (see :meth:`FuCore._filter_headers`)
            
            * removal of Mailman or Mailman-like headers (see :meth:`_mutate_part`)
        
        Args:
            message: a :class:`email.message` object containing a set of MIME parts
            rec:  Recursion level used to prettify log messages.
            
        Returns:
            A (probably) filtered / modified :class:`email.message` object. 
        """
        mm_parts = 0
        text_parts = 0
        mailman_sig = Reactor.MAILMAN_SIG

        self._log('>>> processing {0}', message.get_content_type(), rec=rec)

        if self._conf.complex_footer:
            self._log('--- using complex mailman filter', rec=rec)
            mailman_sig = Reactor.MAILMAN_COMPLEX

        if message.is_multipart():
            parts = message._payload
        else:
            parts = [
                message,
            ]

        list_tag = self._find_list_tag(message, rec)
        reference = message.get('References', None)
        in_reply = message.get('In-Reply-To', None)
        x_mailman = message.get('X-Mailman-Version', None)

        message._headers = self._filter_headers(list_tag, message._headers,
                                                self._conf.outlook_hacks,
                                                self._conf.fix_dateline, rec)

        if in_reply and not reference and rec == 0:
            # set References: to In-Reply-To: if where in toplevel
            # and References was not set properly
            self._log('--- set References: {0}', in_reply, rec=rec)
            try:
                # uncertain this will ever happen..
                message.replace_header('References', in_reply)
            except KeyError:
                message._headers.append(('References', in_reply))

        for i in xrange(len(parts) - 1, -1, -1):
            # again, crude since we mutate the list while iterating it..
            # the whole reason is the deeply nested structure of email.message
            p = parts[i]

            ct = p.get_content_maintype()
            cs = p.get_content_subtype()
            ce = p.get('Content-Transfer-Encoding', None)
            cb = p.get_boundary()

            self._log('-- [ct = {0}, cs = {1}, ce = <{2}>]',
                      ct,
                      cs,
                      ce,
                      rec=rec)

            if ct == 'text':
                text_parts += 1

                payload = p.get_payload(decode=True)
                self._log('--- scan: """{0}"""', payload, rec=rec, verbosity=3)

                if mailman_sig[0].search(payload) and \
                   mailman_sig[1].match(payload.split('\n')[0]):

                    self._log('*** removing this part', rec=rec)
                    self._log('--- """{0}"""', payload, rec=rec, verbosity=2)

                    message._payload.remove(p)
                    text_parts -= 1
                    mm_parts += 1

                elif mailman_sig[0].search(payload):
                    self._log('--- trying to mutate..', rec=rec)

                    (use, mutation) = self._mutate_part(payload, rec)
                    if use:
                        self._log('*** mutated this part', rec=rec)
                        self._log('--- """{0}"""',
                                  payload,
                                  rec=rec,
                                  verbosity=2)

                        payload = mutation
                        mm_parts += 1

                    # if it was encoded we need to re-encode it
                    # to keep SMIME happy

                    if ce == 'base64':
                        payload = payload.encode('base64')

                    elif ce == 'quoted-printable':
                        payload = quopri.encodestring(payload)

                    p.set_payload(payload)

                elif ct == 'message' or \
                     (ct == 'multipart' and cs in ['alternative', 'mixed']):
                    p = self._process(p, rec + 1)

                else:
                    self._log('--- what about {0}?',
                              p.get_content_type(),
                              rec=rec)

        if rec == 0:
            self._log('--- [mm_parts: {0}, text_parts: {1}, x_mailman: {0}]',
                      mm_parts,
                      text_parts,
                      x_mailman,
                      rec=rec)

            if x_mailman and mm_parts and not text_parts:
                # if we have
                # - modified the content
                # - no text parts left in outer message
                # - a valid X-Mailmann-Version:
                # --> remove outer message
                self._log('!!! beheading this one..', rec=rec)

                mm = message._payload[0]
                for h in message._headers:
                    if h[0] == 'Content-Type':
                        continue

                    try:
                        mm.replace_header(h[0], h[1])
                    except KeyError:
                        mm._headers.append(h)

                return mm

        return message
예제 #8
0
def send_message(request):
    if request.is_ajax():
        # Get the mailbox you want to record this in

        # Message form values
        subject = request.POST.get('subject', '')
        folder_id = request.POST.get('folder_id', '')
        folder_id = '"{}"'.format(folder_id)

        message = request.POST.get('message', '').encode("utf-8")
        message_id = request.POST.get('message_id', '')
        recipents = request.POST.get('to', '')
        attachments = request.POST.getlist('attachments[]')
        cc_recipents = request.POST.get('CC', '')
        bcc_recipents = request.POST.get('BCC', '')
        compose = request.POST.get('compose', '')

        # Split into recipents array
        recipents = recipents.split(",")
        cc_recipents = cc_recipents.split(",") or None
        bcc_recipents = bcc_recipents.split(",") or None

        # Create a new cnnection
        conn = UserMailbox.objects.get_mailbox(user=request.user)
        connection = conn.get_SMTP_connection()

        matachments = MessageAttachment.objects.filter(pk__in=attachments)

        # Create an e-mail
        email_message = EmailMultiAlternatives(
            subject=subject,
            body=message,
            from_email=conn.username,
            to=recipents,
            bcc=bcc_recipents,  # ['*****@*****.**'],
            cc=cc_recipents,
            headers={'Reply-To': request.user.person.email},
            connection=connection
        )

        email_message.attach_alternative(message, "text/html")

        for a in matachments:
            email_message.attach(a.name, a.document.read())

        if conn.imap_server.use_ssl:
            m = imaplib.IMAP4_SSL(conn.imap_server.host)
        else:
            m = imaplib.IMAP4(conn.imap_server.host)

        m.login(conn.username, conn.password)

        recipents_str = ','.join(recipents)

        if compose == "forward":
            m.select(folder_id)
            status, data = m.fetch(message_id, "(RFC822)")
            email_data = data[0][1]
            m.close()
            m.logout()

            # create a Message instance from the email data
            message = email.message_from_string(email_data)

            # replace headers (could do other processing here)
            message.replace_header("From", conn.username)
            message.replace_header("To", recipents_str)

            # open authenticated SMTP connection and send message with
            # specified envelope from and to addresses
            smtp = smtplib.SMTP_SSL(conn.smtp_server.host, conn.smtp_server.port)
            smtp.login(conn.username, conn.password)
            smtp.sendmail(conn.username, recipents_str, message.as_string())
            smtp.quit()

        if compose == "send":

            email_message.send()

            # Here we have to use plain imaplib :(

            # create the message
            msg = email.message.Message()
            msg['Subject'] = subject
            msg['From'] = conn.username
            msg['To'] = recipents_str
            msg.set_payload(message)

            m.append("INBOX.Sent",
                     'Seen',
                     imaplib.Time2Internaldate(time.time()),
                     str(msg)
                     )

        elif compose == "draft":

            # Here we have to use plain imaplib :(
            if conn.imap_server.use_ssl:
                m = imaplib.IMAP4_SSL(conn.imap_server.host)
            else:
                m = imaplib.IMAP4(conn.imap_server.host)

            m.login(conn.username, conn.password)

            recipents_str = ','.join(recipents)
            # create the message
            msg = email.message.Message()
            msg['Subject'] = subject
            msg['From'] = conn.username
            msg['To'] = recipents_str
            msg.set_payload(message)

            m.append("INBOX.Drafts",
                     '(\Draft)',
                     imaplib.Time2Internaldate(time.time()),
                     str(msg)
                     )


        elif compose == "reply":

            pass

    matachments.delete()
    return HttpResponse('[]', status=200)
예제 #9
0
 def _process(self, message, rec=0):
     """
     Recursively scan and filter a MIME message.
     
     _process will scan the passed message part for invalid headers
     as well as mailman signatures and modify them according to
     the global settings.
     
     Generic modifications include:
     
         * fixing of broken **References:** and **In-Reply-To** headers
         
         * generic header filtering (see :meth:`FuCore._filter_headers`)
         
         * removal of Mailman or Mailman-like headers (see :meth:`_mutate_part`)
     
     Args:
         message: a :class:`email.message` object containing a set of MIME parts
         rec:  Recursion level used to prettify log messages.
         
     Returns:
         A (probably) filtered / modified :class:`email.message` object. 
     """
     mm_parts    = 0
     text_parts  = 0
     mailman_sig = Reactor.MAILMAN_SIG
     
     self._log('>>> processing {0}', message.get_content_type(), rec=rec)
     
     if self._conf.complex_footer:
         self._log('--- using complex mailman filter', rec=rec)
         mailman_sig = Reactor.MAILMAN_COMPLEX
         
     if message.is_multipart():
         parts = message._payload
     else:
         parts = [message,]
         
     list_tag  = self._find_list_tag(message, rec)
     reference = message.get('References', None)
     in_reply  = message.get('In-Reply-To', None)
     x_mailman = message.get('X-Mailman-Version', None)
     
     message._headers = self._filter_headers(list_tag, message._headers,
                                             self._conf.outlook_hacks, 
                                             self._conf.fix_dateline, rec)
     
     if in_reply and not reference and rec == 0:
         # set References: to In-Reply-To: if where in toplevel
         # and References was not set properly
         self._log('--- set References: {0}', in_reply, rec=rec)
         try:
             # uncertain this will ever happen..
             message.replace_header('References', in_reply)
         except KeyError:
             message._headers.append(('References', in_reply))
             
     for i in xrange(len(parts) - 1, -1, -1):
         # again, crude since we mutate the list while iterating it..
         # the whole reason is the deeply nested structure of email.message
         p = parts[i]
         
         ct = p.get_content_maintype()
         cs = p.get_content_subtype()
         ce = p.get('Content-Transfer-Encoding', None)
         cb = p.get_boundary()
         
         self._log('-- [ct = {0}, cs = {1}, ce = <{2}>]', ct, cs, ce,
                   rec=rec)
         
         if ct == 'text':
             text_parts += 1
             
             payload = p.get_payload(decode=True)
             self._log('--- scan: """{0}"""', payload, rec=rec, verbosity=3)
             
             if mailman_sig[0].search(payload) and \
                mailman_sig[1].match(payload.split('\n')[0]):
                 
                 self._log('*** removing this part', rec=rec)
                 self._log('--- """{0}"""', payload, rec=rec, verbosity=2)
                 
                 message._payload.remove(p)
                 text_parts -= 1
                 mm_parts   += 1
                 
             elif mailman_sig[0].search(payload):
                 self._log('--- trying to mutate..', rec=rec)
                 
                 (use, mutation) = self._mutate_part(payload, rec)
                 if use:
                     self._log('*** mutated this part', rec=rec)
                     self._log('--- """{0}"""', payload, rec=rec, verbosity=2)
                     
                     payload   = mutation
                     mm_parts += 1
                     
                 # if it was encoded we need to re-encode it
                 # to keep SMIME happy
                 
                 if ce == 'base64':
                     payload = payload.encode('base64')
                     
                 elif ce == 'quoted-printable':
                     payload = quopri.encodestring(payload)
                     
                 p.set_payload(payload)
                 
             elif ct == 'message' or \
                  (ct == 'multipart' and cs in ['alternative', 'mixed']):
                 p = self._process(p, rec + 1)
                  
             else:
                 self._log('--- what about {0}?', p.get_content_type(), rec=rec)
     
     if rec == 0:
         self._log('--- [mm_parts: {0}, text_parts: {1}, x_mailman: {0}]',
                   mm_parts, text_parts, x_mailman, rec=rec)
         
         if x_mailman and mm_parts and not text_parts:
             # if we have
             # - modified the content
             # - no text parts left in outer message
             # - a valid X-Mailmann-Version:
             # --> remove outer message
             self._log('!!! beheading this one..', rec=rec)
             
             mm = message._payload[0]
             for h in message._headers:
                 if h[0] == 'Content-Type':
                     continue
                     
                 try:
                     mm.replace_header(h[0], h[1])
                 except KeyError:
                     mm._headers.append(h)
                     
             return mm
             
     return message
예제 #10
0
    def _apply_blacklist(self, message, mode, rec=0):
        """
        Apply the global blacklist to this message.

        This method will modify the headers of the message or decide to
        discard the whole message based on the global blacklist.

        :param messag: A :class:`email.message` object.
        :param mode: One of :attr:`FUCore.BLACKLIST_MODES`
        :returns: The modified message or None if the message should be dropped.
        """
        if not mode.lower() in FUCore.BLACKLIST_MODES:
            self._log('!!! Invalid blacklist mode "{0}", not modifying message.', mode, rec=rec)
            return message
        
        mfrom = message.get('From', '').split('<')[-1].split('>')[0]
        sender = message.get('Sender', '').split('<')[-1].split('>')[0]
        
        if not mfrom and not msender:
            self._log('!!! Message has neiter "From:" nor "Sender:" headers!', rec=rec)
            return message
        
        list_entry = self._blacklist.get(mfrom.lower(), self._blacklist.get(sender.lower(), None))
        if not list_entry:
            return message
        
        self._log('--- applying blacklist rule {0} to message', str(list_entry), rec=rec)
        
        if not list_entry['action'].lower() in ['d', 'n', 'e', 'ne', 'en']:
            # invalid
            self._log('!!! unsupported blacklist rule "{0}"', list_entry['action'], rec=rec)
            return message
        
        add_expires=False
        add_xnay=False
    
        if list_entry['action'].lower() == 'd' and not mode.lower() == 'reactor':
            self._log('--- blacklist rule "drop"', rec=rec, verbosity=2)
            return None
            
        elif mode.lower() == 'news2mail':
            #D=N=E=NE/EN=drop
            self._log('--- blacklist rule D=N=E=NE/EN "drop"', rec=rec, verbosity=2)
            return None

        elif mode.lower() == 'mail2news':
            if list_entry['action'].lower() in ['ne', 'en','n']:
                add_xnay = True
            if list_entry['action'].lower() in ['ne', 'en', 'e']:
                add_expires = True

        elif mode.lower() == 'reactor':
            if list_entry['action'].lower() in ['ne','en','n','d']:
                add_xnay = True
            if list_entry['action'].lower() in ['ne','en','e']:
                add_expires = True

        if add_xnay:
            self._log('--- blacklist rule "xnay"', rec=rec, verbosity=2)
            try:
                message.replace_header('X-No-Archive', 'yes')
            except KeyError:
                message._headers.append(('X-No-Archive', 'yes'))
                
        if add_expires:
            if not list_entry.get('param', None):
                self._log('!!! blacklist rule "expires" missing a parameter', rec=rec)
            else:
                try:
                    delay=long(list_entry['param'])
                    expires=time.strftime(r"%d %b %Y %H:%M:%S %z", time.localtime(time.time() + (86400*delay)))
                    self._log('--- blacklist rule "expires" => {0}', expires, rec=rec, verbosity=2)
                    try:
                        message.replace_header('Expires', expires)
                    except KeyError:
                        message._headers.append(('Expires', expires))
                except ValueError:
                    self.log('!!! blacklist rule "expires" needs a *numeric* parameter.')

        return message
예제 #11
0
    def _apply_blacklist(self, message, mode, rec=0):
        """
        Apply the global blacklist to this message.

        This method will modify the headers of the message or decide to
        discard the whole message based on the global blacklist.

        :param messag: A :class:`email.message` object.
        :param mode: One of :attr:`FUCore.BLACKLIST_MODES`
        :returns: The modified message or None if the message should be dropped.
        """
        if not mode.lower() in FUCore.BLACKLIST_MODES:
            self._log(
                '!!! Invalid blacklist mode "{0}", not modifying message.',
                mode,
                rec=rec)
            return message

        mfrom = message.get('From', '').split('<')[-1].split('>')[0]
        sender = message.get('Sender', '').split('<')[-1].split('>')[0]

        if not mfrom and not msender:
            self._log('!!! Message has neiter "From:" nor "Sender:" headers!',
                      rec=rec)
            return message

        list_entry = self._blacklist.get(
            mfrom.lower(), self._blacklist.get(sender.lower(), None))
        if not list_entry:
            return message

        self._log('--- applying blacklist rule {0} to message',
                  str(list_entry),
                  rec=rec)

        if not list_entry['action'].lower() in ['d', 'n', 'e', 'ne', 'en']:
            # invalid
            self._log('!!! unsupported blacklist rule "{0}"',
                      list_entry['action'],
                      rec=rec)
            return message

        add_expires = False
        add_xnay = False

        if list_entry['action'].lower(
        ) == 'd' and not mode.lower() == 'reactor':
            self._log('--- blacklist rule "drop"', rec=rec, verbosity=2)
            return None

        elif mode.lower() == 'news2mail':
            #D=N=E=NE/EN=drop
            self._log('--- blacklist rule D=N=E=NE/EN "drop"',
                      rec=rec,
                      verbosity=2)
            return None

        elif mode.lower() == 'mail2news':
            if list_entry['action'].lower() in ['ne', 'en', 'n']:
                add_xnay = True
            if list_entry['action'].lower() in ['ne', 'en', 'e']:
                add_expires = True

        elif mode.lower() == 'reactor':
            if list_entry['action'].lower() in ['ne', 'en', 'n', 'd']:
                add_xnay = True
            if list_entry['action'].lower() in ['ne', 'en', 'e']:
                add_expires = True

        if add_xnay:
            self._log('--- blacklist rule "xnay"', rec=rec, verbosity=2)
            try:
                message.replace_header('X-No-Archive', 'yes')
            except KeyError:
                message._headers.append(('X-No-Archive', 'yes'))

        if add_expires:
            if not list_entry.get('param', None):
                self._log('!!! blacklist rule "expires" missing a parameter',
                          rec=rec)
            else:
                try:
                    delay = long(list_entry['param'])
                    expires = time.strftime(
                        r"%d %b %Y %H:%M:%S %z",
                        time.localtime(time.time() + (86400 * delay)))
                    self._log('--- blacklist rule "expires" => {0}',
                              expires,
                              rec=rec,
                              verbosity=2)
                    try:
                        message.replace_header('Expires', expires)
                    except KeyError:
                        message._headers.append(('Expires', expires))
                except ValueError:
                    self.log(
                        '!!! blacklist rule "expires" needs a *numeric* parameter.'
                    )

        return message