Ejemplo n.º 1
0
def bounce_message(mlist, msg, error=None):
    """Bounce the message back to the original author.

    :param mlist: The mailing list that the message was posted to.
    :type mlist: `IMailingList`
    :param msg: The original message.
    :type msg: `email.message.Message`
    :param error: Optional exception causing the bounce.  The exception
        instance must have a `.message` attribute.
    :type error: Exception
    """
    # Bounce a message back to the sender, with an error message if provided
    # in the exception argument.  .sender might be None or the empty string.
    if not msg.sender:
        # We can't bounce the message if we don't know who it's supposed to go
        # to.
        return
    subject = msg.get('subject', _('(no subject)'))
    subject = oneline(subject, mlist.preferred_language.charset)
    if error is None:
        notice = _('[No bounce details are available]')
    else:
        notice = _(error.message)
    # Currently we always craft bounces as MIME messages.
    bmsg = UserNotification(msg.sender, mlist.owner_address, subject,
                            lang=mlist.preferred_language)
    # BAW: Be sure you set the type before trying to attach, or you'll get
    # a MultipartConversionError.
    bmsg.set_type('multipart/mixed')
    txt = MIMEText(notice, _charset=mlist.preferred_language.charset)
    bmsg.attach(txt)
    bmsg.attach(MIMEMessage(msg))
    bmsg.send(mlist)
Ejemplo n.º 2
0
def bounce_message(mlist, msg, error=None):
    """Bounce the message back to the original author.

    :param mlist: The mailing list that the message was posted to.
    :type mlist: `IMailingList`
    :param msg: The original message.
    :type msg: `email.message.Message`
    :param error: Optional exception causing the bounce.  The exception
        instance must have a `.message` attribute.
    :type error: Exception
    """
    # Bounce a message back to the sender, with an error message if provided
    # in the exception argument.  .sender might be None or the empty string.
    if not msg.sender:
        # We can't bounce the message if we don't know who it's supposed to go
        # to.
        return
    subject = msg.get('subject', _('(no subject)'))
    subject = oneline(subject, mlist.preferred_language.charset)
    if error is None:
        notice = _('[No bounce details are available]')
    else:
        notice = _(error.message)
    # Currently we always craft bounces as MIME messages.
    bmsg = UserNotification(msg.sender, mlist.owner_address, subject,
                            lang=mlist.preferred_language)
    # BAW: Be sure you set the type before trying to attach, or you'll get
    # a MultipartConversionError.
    bmsg.set_type('multipart/mixed')
    txt = MIMEText(notice, _charset=mlist.preferred_language.charset)
    bmsg.attach(txt)
    bmsg.attach(MIMEMessage(msg))
    bmsg.send(mlist)
Ejemplo n.º 3
0
def send_probe(member, msg):
    """Send a VERP probe to the member.

    :param member: The member to send the probe to.  From this object, both
        the user and the mailing list can be determined.
    :type member: IMember
    :param msg: The bouncing message that caused the probe to be sent.
    :type msg:
    :return: The token representing this probe in the pendings database.
    :rtype: string
    """
    mlist = getUtility(IListManager).get_by_list_id(
        member.mailing_list.list_id)
    text = make(
        'probe.txt',
        mlist,
        member.preferred_language.code,
        listname=mlist.fqdn_listname,
        address=member.address.email,
        optionsurl=member.options_url,
        owneraddr=mlist.owner_address,
    )
    message_id = msg['message-id']
    if isinstance(message_id, bytes):
        message_id = message_id.decode('ascii')
    pendable = _ProbePendable(
        # We can only pend unicodes.
        member_id=member.member_id.hex,
        message_id=message_id,
    )
    token = getUtility(IPendings).add(pendable)
    mailbox, domain_parts = split_email(mlist.bounces_address)
    probe_sender = Template(config.mta.verp_probe_format).safe_substitute(
        bounces=mailbox,
        token=token,
        domain=DOT.join(domain_parts),
    )
    # Calculate the Subject header, in the member's preferred language.
    with _.using(member.preferred_language.code):
        subject = _('$mlist.display_name mailing list probe message')
    # Craft the probe message.  This will be a multipart where the first part
    # is the probe text and the second part is the message that caused this
    # probe to be sent.
    probe = UserNotification(member.address.email,
                             probe_sender,
                             subject,
                             lang=member.preferred_language)
    probe.set_type('multipart/mixed')
    notice = MIMEText(text, _charset=mlist.preferred_language.charset)
    probe.attach(notice)
    probe.attach(MIMEMessage(msg))
    # Probes should not have the Precedence: bulk header.
    probe.send(mlist,
               envsender=probe_sender,
               verp=False,
               probe_token=token,
               add_precedence=False)
    return token
Ejemplo n.º 4
0
def send_probe(member, msg):
    """Send a VERP probe to the member.

    :param member: The member to send the probe to.  From this object, both
        the user and the mailing list can be determined.
    :type member: IMember
    :param msg: The bouncing message that caused the probe to be sent.
    :type msg:
    :return: The token representing this probe in the pendings database.
    :rtype: string
    """
    mlist = getUtility(IListManager).get_by_list_id(
        member.mailing_list.list_id)
    text = make('probe.txt', mlist, member.preferred_language.code,
                listname=mlist.fqdn_listname,
                address=member.address.email,
                optionsurl=member.options_url,
                owneraddr=mlist.owner_address,
                )
    message_id = msg['message-id']
    if isinstance(message_id, bytes):
        message_id = message_id.decode('ascii')
    pendable = _ProbePendable(
        # We can only pend unicodes.
        member_id=member.member_id.hex,
        message_id=message_id,
        )
    token = getUtility(IPendings).add(pendable)
    mailbox, domain_parts = split_email(mlist.bounces_address)
    probe_sender = Template(config.mta.verp_probe_format).safe_substitute(
        bounces=mailbox,
        token=token,
        domain=DOT.join(domain_parts),
        )
    # Calculate the Subject header, in the member's preferred language.
    with _.using(member.preferred_language.code):
        subject = _('$mlist.display_name mailing list probe message')
    # Craft the probe message.  This will be a multipart where the first part
    # is the probe text and the second part is the message that caused this
    # probe to be sent.
    probe = UserNotification(member.address.email, probe_sender,
                             subject, lang=member.preferred_language)
    probe.set_type('multipart/mixed')
    notice = MIMEText(text, _charset=mlist.preferred_language.charset)
    probe.attach(notice)
    probe.attach(MIMEMessage(msg))
    # Probes should not have the Precedence: bulk header.
    probe.send(mlist, envsender=probe_sender, verp=False, probe_token=token,
               add_precedence=False)
    return token
Ejemplo n.º 5
0
def bounce_message(mlist, msg, error=None):
    """Bounce the message back to the original author.

    :param mlist: The mailing list that the message was posted to.
    :type mlist: `IMailingList`
    :param msg: The original message.
    :type msg: `email.message.Message`
    :param error: Optional exception causing the bounce.  The exception
        instance must have a `.message` attribute.  The exception *may* have a
        non-None `.reasons` attribute which would be a list of reasons for the
        rejection, and it may have a non-None `.substitutions` attribute.  The
        latter, along with the formatted reasons will be interpolated into the
        message (`.reasons` gets put into the `$reasons` placeholder).
    :type error: RejectMessage
    """
    # Bounce a message back to the sender, with an error message if provided
    # in the exception argument.  .sender might be None or the empty string.
    if not msg.sender:
        # We can't bounce the message if we don't know who it's supposed to go
        # to.
        return
    subject = msg.get('subject', _('(no subject)'))
    subject = oneline(subject, mlist.preferred_language.charset)
    notice = (_('[No bounce details are available]')
              if error is None else str(error))
    # Currently we always craft bounces as MIME messages.
    bmsg = UserNotification(msg.sender,
                            mlist.owner_address,
                            subject,
                            lang=mlist.preferred_language)
    # BAW: Be sure you set the type before trying to attach, or you'll get
    # a MultipartConversionError.
    bmsg.set_type('multipart/mixed')
    txt = MIMEText(notice, _charset=mlist.preferred_language.charset)
    bmsg.attach(txt)
    bmsg.attach(MIMEMessage(msg))
    bmsg.send(mlist)
Ejemplo n.º 6
0
def handle_message(mlist, id, action,
                   comment=None, preserve=False, forward=None):
    message_store = getUtility(IMessageStore)
    requestdb = IListRequests(mlist)
    key, msgdata = requestdb.get_request(id)
    # Handle the action.
    rejection = None
    message_id = msgdata['_mod_message_id']
    sender = msgdata['_mod_sender']
    subject = msgdata['_mod_subject']
    if action in (Action.defer, Action.hold):
        # Nothing to do, but preserve the message for later.
        preserve = True
    elif action is Action.discard:
        rejection = 'Discarded'
    elif action is Action.reject:
        rejection = 'Refused'
        member = mlist.members.get_member(sender)
        if member:
            language = member.preferred_language
        else:
            language = None
        _refuse(mlist, _('Posting of your message titled "$subject"'),
                sender, comment or _('[No reason given]'), language)
    elif action is Action.accept:
        # Start by getting the message from the message store.
        msg = message_store.get_message_by_id(message_id)
        # Delete moderation-specific entries from the message metadata.
        for key in msgdata.keys():
            if key.startswith('_mod_'):
                del msgdata[key]
        # Add some metadata to indicate this message has now been approved.
        msgdata['approved'] = True
        msgdata['moderator_approved'] = True
        # Calculate a new filebase for the approved message, otherwise
        # delivery errors will cause duplicates.
        if 'filebase' in msgdata:
            del msgdata['filebase']
        # Queue the file for delivery.  Trying to deliver the message directly
        # here can lead to a huge delay in web turnaround.  Log the moderation
        # and add a header.
        msg['X-Mailman-Approved-At'] = formatdate(
            time.mktime(now().timetuple()), localtime=True)
        vlog.info('held message approved, message-id: %s',
                  msg.get('message-id', 'n/a'))
        # Stick the message back in the incoming queue for further
        # processing.
        config.switchboards['pipeline'].enqueue(msg, _metadata=msgdata)
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Forward the message.
    if forward:
        # Get a copy of the original message from the message store.
        msg = message_store.get_message_by_id(message_id)
        # It's possible the forwarding address list is a comma separated list
        # of display_name/address pairs.
        addresses = [addr[1] for addr in getaddresses(forward)]
        language = mlist.preferred_language
        if len(addresses) == 1:
            # If the address getting the forwarded message is a member of
            # the list, we want the headers of the outer message to be
            # encoded in their language.  Otherwise it'll be the preferred
            # language of the mailing list.  This is better than sending a
            # separate message per recipient.
            member = mlist.members.get_member(addresses[0])
            if member:
                language = member.preferred_language
        with _.using(language.code):
            fmsg = UserNotification(
                addresses, mlist.bounces_address,
                _('Forward of moderated message'),
                lang=language)
        fmsg.set_type('message/rfc822')
        fmsg.attach(msg)
        fmsg.send(mlist)
    # Delete the message from the message store if it is not being preserved.
    if not preserve:
        message_store.delete_message(message_id)
        requestdb.delete_request(id)
    # Log the rejection
    if rejection:
        note = """%s: %s posting:
\tFrom: %s
\tSubject: %s"""
        if comment:
            note += '\n\tReason: ' + comment
        vlog.info(note, mlist.fqdn_listname, rejection, sender, subject)
Ejemplo n.º 7
0
def handle_message(mlist, id, action, comment=None, forward=None):
    message_store = getUtility(IMessageStore)
    requestdb = IListRequests(mlist)
    key, msgdata = requestdb.get_request(id)
    # Handle the action.
    rejection = None
    message_id = msgdata['_mod_message_id']
    sender = msgdata['_mod_sender']
    subject = msgdata['_mod_subject']
    keep = False
    if action in (Action.defer, Action.hold):
        # Nothing to do, but preserve the message for later.
        keep = True
    elif action is Action.discard:
        rejection = 'Discarded'
    elif action is Action.reject:
        rejection = 'Refused'
        member = mlist.members.get_member(sender)
        if member:
            language = member.preferred_language
        else:
            language = None
        send_rejection(
            mlist, _('Posting of your message titled "$subject"'),
            sender, comment or _('[No reason given]'), language)
    elif action is Action.accept:
        # Start by getting the message from the message store.
        msg = message_store.get_message_by_id(message_id)
        # Delete moderation-specific entries from the message metadata.
        for key in list(msgdata):
            if key.startswith('_mod_'):
                del msgdata[key]
        # Add some metadata to indicate this message has now been approved.
        msgdata['approved'] = True
        msgdata['moderator_approved'] = True
        # Calculate a new filebase for the approved message, otherwise
        # delivery errors will cause duplicates.
        if 'filebase' in msgdata:
            del msgdata['filebase']
        # Queue the file for delivery.  Trying to deliver the message directly
        # here can lead to a huge delay in web turnaround.  Log the moderation
        # and add a header.
        msg['X-Mailman-Approved-At'] = formatdate(
            time.mktime(now().timetuple()), localtime=True)
        vlog.info('held message approved, message-id: %s',
                  msg.get('message-id', 'n/a'))
        # Stick the message back in the incoming queue for further
        # processing.
        config.switchboards['pipeline'].enqueue(msg, _metadata=msgdata)
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Forward the message.
    if forward:
        # Get a copy of the original message from the message store.
        msg = message_store.get_message_by_id(message_id)
        # It's possible the forwarding address list is a comma separated list
        # of display_name/address pairs.
        addresses = [addr[1] for addr in getaddresses(forward)]
        language = mlist.preferred_language
        if len(addresses) == 1:
            # If the address getting the forwarded message is a member of
            # the list, we want the headers of the outer message to be
            # encoded in their language.  Otherwise it'll be the preferred
            # language of the mailing list.  This is better than sending a
            # separate message per recipient.
            member = mlist.members.get_member(addresses[0])
            if member:
                language = member.preferred_language
        with _.using(language.code):
            fmsg = UserNotification(
                addresses, mlist.bounces_address,
                _('Forward of moderated message'),
                lang=language)
        fmsg.set_type('message/rfc822')
        fmsg.attach(msg)
        fmsg.send(mlist)
    # Delete the request if it's not being kept.
    if not keep:
        requestdb.delete_request(id)
    # Log the rejection
    if rejection:
        note = """%s: %s posting:
\tFrom: %s
\tSubject: %s"""
        if comment:
            note += '\n\tReason: ' + comment
        vlog.info(note, mlist.fqdn_listname, rejection, sender, subject)
Ejemplo n.º 8
0
    def _process(self, mlist, msg, msgdata):
        """See `TerminalChainBase`."""
        # Start by decorating the message with a header that contains a list
        # of all the rules that matched.  These metadata could be None or an
        # empty list.
        rule_hits = msgdata.get('rule_hits')
        if rule_hits:
            msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits)
        rule_misses = msgdata.get('rule_misses')
        if rule_misses:
            msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses)
        # Hold the message by adding it to the list's request database.
        request_id = hold_message(mlist, msg, msgdata, None)
        # Calculate a confirmation token to send to the author of the
        # message.
        pendable = HeldMessagePendable(type=HeldMessagePendable.PEND_KEY,
                                       id=request_id)
        token = getUtility(IPendings).add(pendable)
        # Get the language to send the response in.  If the sender is a
        # member, then send it in the member's language, otherwise send it in
        # the mailing list's preferred language.
        member = mlist.members.get_member(msg.sender)
        language = (member.preferred_language
                    if member else mlist.preferred_language)
        # A substitution dictionary for the email templates.
        charset = mlist.preferred_language.charset
        original_subject = msg.get('subject')
        if original_subject is None:
            original_subject = _('(no subject)')
        else:
            original_subject = oneline(original_subject, in_unicode=True)
        substitutions = dict(
            listname    = mlist.fqdn_listname,
            subject     = original_subject,
            sender      = msg.sender,
            reasons     = _compose_reasons(msgdata),
            )
        # At this point the message is held, but now we have to craft at least
        # two responses.  The first will go to the original author of the
        # message and it will contain the token allowing them to approve or
        # discard the message.  The second one will go to the moderators of
        # the mailing list, if the list is so configured.
        #
        # Start by possibly sending a response to the message author.  There
        # are several reasons why we might not go through with this.  If the
        # message was gated from NNTP, the author may not even know about this
        # list, so don't spam them.  If the author specifically requested that
        # acknowledgments not be sent, or if the message was bulk email, then
        # we do not send the response.  It's also possible that either the
        # mailing list, or the author (if they are a member) have been
        # configured to not send such responses.
        if (not msgdata.get('fromusenet') and
            can_acknowledge(msg) and
            mlist.respond_to_post_requests and
            autorespond_to_sender(mlist, msg.sender, language)):
            # We can respond to the sender with a message indicating their
            # posting was held.
            subject = _(
              'Your message to $mlist.fqdn_listname awaits moderator approval')
            send_language_code = msgdata.get('lang', language.code)
            text = make('postheld.txt',
                        mailing_list=mlist,
                        language=send_language_code,
                        **substitutions)
            adminaddr = mlist.bounces_address
            nmsg = UserNotification(
                msg.sender, adminaddr, subject, text,
                getUtility(ILanguageManager)[send_language_code])
            nmsg.send(mlist)
        # Now the message for the list moderators.  This one should appear to
        # come from <list>-owner since we really don't need to do bounce
        # processing on it.
        if mlist.admin_immed_notify:
            # Now let's temporarily set the language context to that which the
            # administrators are expecting.
            with _.using(mlist.preferred_language.code):
                language = mlist.preferred_language
                charset = language.charset
                substitutions['subject'] = original_subject
                # We need to regenerate or re-translate a few values in the
                # substitution dictionary.
                substitutions['reasons'] = _compose_reasons(msgdata, 55)
                # craft the admin notification message and deliver it
                subject = _(
                    '$mlist.fqdn_listname post from $msg.sender requires '
                    'approval')
                nmsg = UserNotification(mlist.owner_address,
                                        mlist.owner_address,
                                        subject, lang=language)
                nmsg.set_type('multipart/mixed')
                text = MIMEText(make('postauth.txt',
                                     mailing_list=mlist,
                                     wrap=False,
                                     **substitutions),
                                _charset=charset)
                dmsg = MIMEText(wrap(_("""\
If you reply to this message, keeping the Subject: header intact, Mailman will
discard the held message.  Do this if the message is spam.  If you reply to
this message and include an Approved: header with the list password in it, the
message will be approved for posting to the list.  The Approved: header can
also appear in the first line of the body of the reply.""")),
                                _charset=language.charset)
                dmsg['Subject'] = 'confirm ' + token
                dmsg['From'] = mlist.request_address
                dmsg['Date'] = formatdate(localtime=True)
                dmsg['Message-ID'] = make_msgid()
                nmsg.attach(text)
                nmsg.attach(MIMEMessage(msg))
                nmsg.attach(MIMEMessage(dmsg))
                nmsg.send(mlist, **dict(tomoderators=True))
        # Log the held message.  Log messages are not translated, so recast
        # the reasons in the English.
        with _.using('en'):
            reasons = _compose_reasons(msgdata)
            log.info('HOLD: %s post from %s held, message-id=%s: %s',
                     mlist.fqdn_listname, msg.sender,
                     msg.get('message-id', 'n/a'), SEMISPACE.join(reasons))
        notify(HoldEvent(mlist, msg, msgdata, self))
Ejemplo n.º 9
0
    def _process(self, mlist, msg, msgdata):
        """See `TerminalChainBase`."""
        # Start by decorating the message with a header that contains a list
        # of all the rules that matched.  These metadata could be None or an
        # empty list.
        rule_hits = msgdata.get('rule_hits')
        if rule_hits:
            msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits)
        rule_misses = msgdata.get('rule_misses')
        if rule_misses:
            msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses)
        # Hold the message by adding it to the list's request database.
        request_id = hold_message(mlist, msg, msgdata, None)
        # Calculate a confirmation token to send to the author of the
        # message.
        pendable = HeldMessagePendable(id=request_id)
        token = getUtility(IPendings).add(pendable)
        # Get the language to send the response in.  If the sender is a
        # member, then send it in the member's language, otherwise send it in
        # the mailing list's preferred language.
        member = mlist.members.get_member(msg.sender)
        language = (member.preferred_language
                    if member else mlist.preferred_language)
        # A substitution dictionary for the email templates.
        charset = mlist.preferred_language.charset
        original_subject = msg.get('subject')
        if original_subject is None:
            original_subject = _('(no subject)')
        else:
            # This must be encoded to the mailing list's perferred charset,
            # ignoring incompatible characters, otherwise when creating the
            # notification messages, we could get a Unicode error.
            oneline_subject = oneline(original_subject, in_unicode=True)
            bytes_subject = oneline_subject.encode(charset, 'replace')
            original_subject = bytes_subject.decode(charset)
        substitutions = dict(
            subject=original_subject,
            sender_email=msg.sender,
            reasons=_compose_reasons(msgdata),
            # For backward compatibility.
            sender=msg.sender,
            )
        # At this point the message is held, but now we have to craft at least
        # two responses.  The first will go to the original author of the
        # message and it will contain the token allowing them to approve or
        # discard the message.  The second one will go to the moderators of
        # the mailing list, if the list is so configured.
        #
        # Start by possibly sending a response to the message author.  There
        # are several reasons why we might not go through with this.  If the
        # message was gated from NNTP, the author may not even know about this
        # list, so don't spam them.  If the author specifically requested that
        # acknowledgments not be sent, or if the message was bulk email, then
        # we do not send the response.  It's also possible that either the
        # mailing list, or the author (if they are a member) have been
        # configured to not send such responses.
        if (not msgdata.get('fromusenet') and
                can_acknowledge(msg) and
                mlist.respond_to_post_requests and
                autorespond_to_sender(mlist, msg.sender, language)):
            # We can respond to the sender with a message indicating their
            # posting was held.
            subject = _(
              'Your message to $mlist.fqdn_listname awaits moderator approval')
            send_language_code = msgdata.get('lang', language.code)
            template = getUtility(ITemplateLoader).get(
                'list:user:notice:hold', mlist,
                language=send_language_code)
            text = wrap(expand(template, mlist, dict(
                language=send_language_code,
                **substitutions)))
            adminaddr = mlist.bounces_address
            nmsg = UserNotification(
                msg.sender, adminaddr, subject, text,
                getUtility(ILanguageManager)[send_language_code])
            nmsg.send(mlist)
        # Now the message for the list moderators.  This one should appear to
        # come from <list>-owner since we really don't need to do bounce
        # processing on it.
        if mlist.admin_immed_notify:
            # Now let's temporarily set the language context to that which the
            # administrators are expecting.
            with _.using(mlist.preferred_language.code):
                language = mlist.preferred_language
                charset = language.charset
                substitutions['subject'] = original_subject
                # We need to regenerate or re-translate a few values in the
                # substitution dictionary.
                substitutions['reasons'] = _compose_reasons(msgdata, 55)
                # craft the admin notification message and deliver it
                subject = _(
                    '$mlist.fqdn_listname post from $msg.sender requires '
                    'approval')
                nmsg = UserNotification(mlist.owner_address,
                                        mlist.owner_address,
                                        subject, lang=language)
                nmsg.set_type('multipart/mixed')
                template = getUtility(ITemplateLoader).get(
                    'list:admin:action:post', mlist)
                text = MIMEText(expand(template, mlist, substitutions),
                                _charset=charset)
                dmsg = MIMEText(wrap(_("""\
If you reply to this message, keeping the Subject: header intact, Mailman will
discard the held message.  Do this if the message is spam.  If you reply to
this message and include an Approved: header with the list password in it, the
message will be approved for posting to the list.  The Approved: header can
also appear in the first line of the body of the reply.""")),
                                _charset=language.charset)
                dmsg['Subject'] = 'confirm ' + token
                dmsg['From'] = mlist.request_address
                dmsg['Date'] = formatdate(localtime=True)
                dmsg['Message-ID'] = make_msgid()
                nmsg.attach(text)
                nmsg.attach(MIMEMessage(msg))
                nmsg.attach(MIMEMessage(dmsg))
                nmsg.send(mlist, **dict(tomoderators=True))
        # Log the held message.  Log messages are not translated, so recast
        # the reasons in the English.
        with _.using('en'):
            reasons = msgdata.get('moderation_reasons', ['N/A'])
            log.info('HOLD: %s post from %s held, message-id=%s: %s',
                     mlist.fqdn_listname, msg.sender,
                     msg.get('message-id', 'n/a'), SEMISPACE.join(reasons))
        notify(HoldEvent(mlist, msg, msgdata, self))
Ejemplo n.º 10
0
def send_probe(member, msg=None, message_id=None):
    """Send a VERP probe to the member.

    :param member: The member to send the probe to.  From this object, both
        the user and the mailing list can be determined.
    :type member: IMember
    :param msg: The bouncing message that caused the probe to be sent.
    :type msg:
    :param message_id: MessageID of the bouncing message.
    :type message_id: str
    :return: The token representing this probe in the pendings database.
    :rtype: string
    """
    if (message_id or msg) is None:
        raise ValueError('Required at least one of "message_id" and "msg".')
    mlist = getUtility(IListManager).get_by_list_id(
        member.mailing_list.list_id)
    template = getUtility(ITemplateLoader).get(
        'list:user:notice:probe', mlist,
        language=member.preferred_language.code,
        # For backward compatibility.
        code=member.preferred_language.code,
        )
    text = wrap(expand(template, mlist, dict(
        sender_email=member.address.email,
        # For backward compatibility.
        address=member.address.email,
        email=member.address.email,
        owneraddr=mlist.owner_address,
        )))
    if message_id is None:
        message_id = msg['message-id']
    if isinstance(message_id, bytes):
        message_id = message_id.decode('ascii')
    pendable = _ProbePendable(
        # We can only pend unicodes.
        member_id=member.member_id.hex,
        message_id=message_id,
        )
    token = getUtility(IPendings).add(pendable)
    mailbox, domain_parts = split_email(mlist.bounces_address)
    probe_sender = Template(config.mta.verp_probe_format).safe_substitute(
        bounces=mailbox,
        token=token,
        domain=DOT.join(domain_parts),
        )
    # Calculate the Subject header, in the member's preferred language.
    with _.using(member.preferred_language.code):
        subject = _('$mlist.display_name mailing list probe message')
    # Craft the probe message.  This will be a multipart where the first part
    # is the probe text and the second part is the message that caused this
    # probe to be sent, if it provied.
    probe = UserNotification(member.address.email, probe_sender,
                             subject, lang=member.preferred_language)
    probe.set_type('multipart/mixed')
    notice = MIMEText(text, _charset=member.preferred_language.charset)
    probe.attach(notice)
    if msg is not None:
        probe.attach(MIMEMessage(msg))
    # Probes should not have the Precedence: bulk header.
    probe.send(mlist, sender=probe_sender, verp=False, probe_token=token,
               add_precedence=False)
    # When we send a probe, we reset the score.
    member.bounce_score = 0
    return token