Ejemplo n.º 1
0
 def _step_get_moderator_approval(self):
     # Here's the next step in the workflow, assuming the moderator
     # approves of the subscription.  If they don't, the workflow and
     # subscription request will just be thrown away.
     self._set_token(TokenOwner.moderator)
     self.push('subscribe_from_restored')
     self.save()
     log.info('{}: held subscription request from {}'.format(
         self.mlist.fqdn_listname, self.address.email))
     # Possibly send a notification to the list moderators.
     if self.mlist.admin_immed_notify:
         subject = _(
             'New subscription request to $self.mlist.display_name '
             'from $self.address.email')
         username = formataddr(
             (self.subscriber.display_name, self.address.email))
         text = make('subauth.txt',
                     mailing_list=self.mlist,
                     username=username,
                     listname=self.mlist.fqdn_listname,
                     )
         # This message should appear to come from the <list>-owner so as
         # to avoid any useless bounce processing.
         msg = UserNotification(
             self.mlist.owner_address, self.mlist.owner_address,
             subject, text, self.mlist.preferred_language)
         msg.send(self.mlist, tomoderators=True)
     # The workflow must stop running here.
     raise StopIteration
Ejemplo n.º 2
0
def _refuse(mlist, request, recip, comment, origmsg=None, lang=None):
    # As this message is going to the requester, try to set the language to
    # his/her language choice, if they are a member.  Otherwise use the list's
    # preferred language.
    display_name = mlist.display_name
    if lang is None:
        member = mlist.members.get_member(recip)
        lang = (mlist.preferred_language
                if member is None
                else member.preferred_language)
    text = make('refuse.txt',
                mailing_list=mlist,
                language=lang.code,
                listname=mlist.fqdn_listname,
                request=request,
                reason=comment,
                adminaddr=mlist.owner_address,
                )
    with _.using(lang.code):
        # add in original message, but not wrap/filled
        if origmsg:
            text = NL.join(
                [text,
                 '---------- ' + _('Original Message') + ' ----------',
                 str(origmsg)
                 ])
        subject = _('Request to mailing list "$display_name" rejected')
    msg = UserNotification(recip, mlist.bounces_address, subject, text, lang)
    msg.send(mlist)
Ejemplo n.º 3
0
def autorespond_to_sender(mlist, sender, language=None):
    """Should Mailman automatically respond to this sender?

    :param mlist: The mailing list.
    :type mlist: `IMailingList`.
    :param sender: The sender's email address.
    :type sender: string
    :param language: Optional language.
    :type language: `ILanguage` or None
    :return: True if an automatic response should be sent, otherwise False.
        If an automatic response is not sent, a message is sent indicating
        that, er no more will be sent today.
    :rtype: bool
    """
    if language is None:
        language = mlist.preferred_language
    max_autoresponses_per_day = int(config.mta.max_autoresponses_per_day)
    if max_autoresponses_per_day == 0:
        # Unlimited.
        return True
    # Get an IAddress from an email address.
    user_manager = getUtility(IUserManager)
    address = user_manager.get_address(sender)
    if address is None:
        address = user_manager.create_address(sender)
    response_set = IAutoResponseSet(mlist)
    todays_count = response_set.todays_count(address, Response.hold)
    if todays_count < max_autoresponses_per_day:
        # This person has not reached their automatic response limit, so it's
        # okay to send a response.
        response_set.response_sent(address, Response.hold)
        return True
    elif todays_count == max_autoresponses_per_day:
        # The last one we sent was the last one we should send today.  Instead
        # of sending an automatic response, send them the "no more today"
        # message.
        log.info('hold autoresponse limit hit: %s', sender)
        response_set.response_sent(address, Response.hold)
        # Send this notification message instead.
        text = make(
            'nomoretoday.txt',
            language=language.code,
            sender=sender,
            listname=mlist.fqdn_listname,
            count=todays_count,
            owneremail=mlist.owner_address,
        )
        with _.using(language.code):
            msg = UserNotification(
                sender,
                mlist.owner_address,
                _('Last autoresponse notification for today'),
                text,
                lang=language)
        msg.send(mlist)
        return False
    else:
        # We've sent them everything we're going to send them today.
        log.info('Automatic response limit discard: %s', sender)
        return False
Ejemplo n.º 4
0
def send_rejection(mlist, request, recip, comment, origmsg=None, lang=None):
    # As this message is going to the requester, try to set the language to
    # his/her language choice, if they are a member.  Otherwise use the list's
    # preferred language.
    display_name = mlist.display_name               # noqa
    if lang is None:
        member = mlist.members.get_member(recip)
        lang = (mlist.preferred_language
                if member is None
                else member.preferred_language)
    text = make('refuse.txt',
                mailing_list=mlist,
                language=lang.code,
                listname=mlist.fqdn_listname,
                request=request,
                reason=comment,
                adminaddr=mlist.owner_address,
                )
    with _.using(lang.code):
        # add in original message, but not wrap/filled
        if origmsg:
            text = NL.join(
                [text,
                 '---------- ' + _('Original Message') + ' ----------',
                 str(origmsg)
                 ])
        subject = _('Request to mailing list "$display_name" rejected')
    msg = UserNotification(recip, mlist.bounces_address, subject, text, lang)
    msg.send(mlist)
Ejemplo n.º 5
0
def hold_subscription(mlist, address, display_name, password, mode, language):
    data = dict(when=now().isoformat(),
                address=address,
                display_name=display_name,
                password=password,
                delivery_mode=mode.name,
                language=language)
    # Now hold this request.  We'll use the address as the key.
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(
        RequestType.subscription, address, data)
    vlog.info('%s: held subscription request from %s',
              mlist.fqdn_listname, address)
    # Possibly notify the administrator in default list language
    if mlist.admin_immed_notify:
        subject = _(
            'New subscription request to $mlist.display_name from $address')
        text = make('subauth.txt',
                    mailing_list=mlist,
                    username=address,
                    listname=mlist.fqdn_listname,
                    admindb_url=mlist.script_url('admindb'),
                    )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(
            mlist.owner_address, mlist.owner_address,
            subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Ejemplo n.º 6
0
 def _step_get_moderator_approval(self):
     # Here's the next step in the workflow, assuming the moderator
     # approves of the subscription.  If they don't, the workflow and
     # subscription request will just be thrown away.
     self._set_token(TokenOwner.moderator)
     self.push('subscribe_from_restored')
     self.save()
     log.info('{}: held subscription request from {}'.format(
         self.mlist.fqdn_listname, self.address.email))
     # Possibly send a notification to the list moderators.
     if self.mlist.admin_immed_notify:
         subject = _(
             'New subscription request to $self.mlist.display_name '
             'from $self.address.email')
         username = formataddr(
             (self.subscriber.display_name, self.address.email))
         text = make('subauth.txt',
                     mailing_list=self.mlist,
                     username=username,
                     listname=self.mlist.fqdn_listname,
                     )
         # This message should appear to come from the <list>-owner so as
         # to avoid any useless bounce processing.
         msg = UserNotification(
             self.mlist.owner_address, self.mlist.owner_address,
             subject, text, self.mlist.preferred_language)
         msg.send(self.mlist, tomoderators=True)
     # The workflow must stop running here.
     raise StopIteration
Ejemplo n.º 7
0
def hold_subscription(mlist, address, display_name, password, mode, language):
    data = dict(when=now().isoformat(),
                address=address,
                display_name=display_name,
                password=password,
                delivery_mode=mode.name,
                language=language)
    # Now hold this request.  We'll use the address as the key.
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(RequestType.subscription, address,
                                         data)
    vlog.info('%s: held subscription request from %s', mlist.fqdn_listname,
              address)
    # Possibly notify the administrator in default list language
    if mlist.admin_immed_notify:
        subject = _(
            'New subscription request to $mlist.display_name from $address')
        text = make(
            'subauth.txt',
            mailing_list=mlist,
            username=address,
            listname=mlist.fqdn_listname,
            admindb_url=mlist.script_url('admindb'),
        )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(mlist.owner_address, mlist.owner_address,
                               subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Ejemplo n.º 8
0
    def test_substitutions_no_wrap(self):
        self.assertEqual(make('nowrap.txt', self.mlist, wrap=False,
                              kind='very nice',
                              howmany='a few'), """\
This is a very nice template.
It has a few substitutions.
It will not be wrapped.
""")
Ejemplo n.º 9
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.º 10
0
def autorespond_to_sender(mlist, sender, language=None):
    """Should Mailman automatically respond to this sender?

    :param mlist: The mailing list.
    :type mlist: `IMailingList`.
    :param sender: The sender's email address.
    :type sender: string
    :param language: Optional language.
    :type language: `ILanguage` or None
    :return: True if an automatic response should be sent, otherwise False.
        If an automatic response is not sent, a message is sent indicating
        that, er no more will be sent today.
    :rtype: bool
    """
    if language is None:
        language = mlist.preferred_language
    max_autoresponses_per_day = int(config.mta.max_autoresponses_per_day)
    if max_autoresponses_per_day == 0:
        # Unlimited.
        return True
    # Get an IAddress from an email address.
    user_manager = getUtility(IUserManager)
    address = user_manager.get_address(sender)
    if address is None:
        address = user_manager.create_address(sender)
    response_set = IAutoResponseSet(mlist)
    todays_count = response_set.todays_count(address, Response.hold)
    if todays_count < max_autoresponses_per_day:
        # This person has not reached their automatic response limit, so it's
        # okay to send a response.
        response_set.response_sent(address, Response.hold)
        return True
    elif todays_count == max_autoresponses_per_day:
        # The last one we sent was the last one we should send today.  Instead
        # of sending an automatic response, send them the "no more today"
        # message.
        log.info('hold autoresponse limit hit: %s', sender)
        response_set.response_sent(address, Response.hold)
        # Send this notification message instead.
        text = make('nomoretoday.txt',
                    language=language.code,
                    sender=sender,
                    listname=mlist.fqdn_listname,
                    count=todays_count,
                    owneremail=mlist.owner_address,
                    )
        with _.using(language.code):
            msg = UserNotification(
                sender, mlist.owner_address,
                _('Last autoresponse notification for today'),
                text, lang=language)
        msg.send(mlist)
        return False
    else:
        # We've sent them everything we're going to send them today.
        log.info('Automatic response limit discard: %s', sender)
        return False
Ejemplo n.º 11
0
    def test_substitutions_no_wrap(self):
        self.assertEqual(
            make('nowrap.txt',
                 self.mlist,
                 wrap=False,
                 kind='very nice',
                 howmany='a few'), """\
This is a very nice template.
It has a few substitutions.
It will not be wrapped.
""")
Ejemplo n.º 12
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.º 13
0
def delete_member(mlist, email, admin_notif=None, userack=None):
    """Delete a member right now.

    :param mlist: The mailing list to remove the member from.
    :type mlist: `IMailingList`
    :param email: The email address to unsubscribe.
    :type email: string
    :param admin_notif: Whether the list administrator should be notified that
        this member was deleted.
    :type admin_notif: bool, or None to let the mailing list's
        `admin_notify_mchange` attribute decide.
    :raises NotAMemberError: if the address is not a member of the
        mailing list.
    """
    if userack is None:
        userack = mlist.send_goodbye_message
    if admin_notif is None:
        admin_notif = mlist.admin_notify_mchanges
    # Delete a member, for which we know the approval has been made.
    member = mlist.members.get_member(email)
    if member is None:
        raise NotAMemberError(mlist, email)
    language = member.preferred_language
    member.unsubscribe()
    # And send an acknowledgement to the user...
    if userack:
        send_goodbye_message(mlist, email, language)
    # ...and to the administrator.
    if admin_notif:
        user = getUtility(IUserManager).get_user(email)
        display_name = user.display_name
        subject = _('$mlist.display_name unsubscription notification')
        text = make(
            'adminunsubscribeack.txt',
            mailing_list=mlist,
            listname=mlist.display_name,
            member=formataddr((display_name, email)),
        )
        msg = OwnerNotification(mlist,
                                subject,
                                text,
                                roster=mlist.administrators)
        msg.send(mlist)
Ejemplo n.º 14
0
def send_admin_subscription_notice(mlist, address, display_name):
    """Send the list administrators a subscription notice.

    :param mlist: The mailing list.
    :type mlist: IMailingList
    :param address: The address being subscribed.
    :type address: string
    :param display_name: The name of the subscriber.
    :type display_name: string
    """
    with _.using(mlist.preferred_language.code):
        subject = _('$mlist.display_name subscription notification')
    text = make('adminsubscribeack.txt',
                mailing_list=mlist,
                listname=mlist.display_name,
                member=formataddr((display_name, address)),
                )
    msg = OwnerNotification(mlist, subject, text, roster=mlist.administrators)
    msg.send(mlist)
Ejemplo n.º 15
0
def send_admin_subscription_notice(mlist, address, display_name):
    """Send the list administrators a subscription notice.

    :param mlist: The mailing list.
    :type mlist: IMailingList
    :param address: The address being subscribed.
    :type address: string
    :param display_name: The name of the subscriber.
    :type display_name: string
    """
    with _.using(mlist.preferred_language.code):
        subject = _('$mlist.display_name subscription notification')
    text = make(
        'adminsubscribeack.txt',
        mailing_list=mlist,
        listname=mlist.display_name,
        member=formataddr((display_name, address)),
    )
    msg = OwnerNotification(mlist, subject, text, roster=mlist.administrators)
    msg.send(mlist)
Ejemplo n.º 16
0
 def __init__(self, mlist, volume, digest_number):
     self._mlist = mlist
     self._charset = mlist.preferred_language.charset
     # This will be used in the Subject, so use $-strings.
     self._digest_id = _(
         '$mlist.display_name Digest, Vol $volume, Issue $digest_number')
     self._subject = Header(self._digest_id,
                            self._charset,
                            header_name='Subject')
     self._message = self._make_message()
     self._digest_part = self._make_digest_part()
     self._message['From'] = mlist.request_address
     self._message['Subject'] = self._subject
     self._message['To'] = mlist.posting_address
     self._message['Reply-To'] = mlist.posting_address
     self._message['Date'] = formatdate(localtime=True)
     self._message['Message-ID'] = make_msgid()
     # In the rfc1153 digest, the masthead contains the digest boilerplate
     # plus any digest header.  In the MIME digests, the masthead and
     # digest header are separate MIME subobjects.  In either case, it's
     # the first thing in the digest, and we can calculate it now, so go
     # ahead and add it now.
     self._masthead = make('masthead.txt',
                           mailing_list=mlist,
                           display_name=mlist.display_name,
                           got_list_email=mlist.posting_address,
                           got_listinfo_url=mlist.script_url('listinfo'),
                           got_request_email=mlist.request_address,
                           got_owner_email=mlist.owner_address,
                           )
     # Set things up for the table of contents.
     if mlist.digest_header_uri is not None:
         try:
             self._header = decorate(mlist, mlist.digest_header_uri)
         except URLError:
             log.exception(
                 'Digest header decorator URI not found ({}): {}'.format(
                     mlist.fqdn_listname, mlist.digest_header_uri))
             self._header = ''
     self._toc = StringIO()
     print(_("Today's Topics:\n"), file=self._toc)
Ejemplo n.º 17
0
 def __init__(self, mlist, volume, digest_number):
     self._mlist = mlist
     self._charset = mlist.preferred_language.charset
     # This will be used in the Subject, so use $-strings.
     self._digest_id = _(
         '$mlist.display_name Digest, Vol $volume, Issue $digest_number')
     self._subject = Header(self._digest_id,
                            self._charset,
                            header_name='Subject')
     self._message = self._make_message()
     self._digest_part = self._make_digest_part()
     self._message['From'] = mlist.request_address
     self._message['Subject'] = self._subject
     self._message['To'] = mlist.posting_address
     self._message['Reply-To'] = mlist.posting_address
     self._message['Date'] = formatdate(localtime=True)
     self._message['Message-ID'] = make_msgid()
     # In the rfc1153 digest, the masthead contains the digest boilerplate
     # plus any digest header.  In the MIME digests, the masthead and
     # digest header are separate MIME subobjects.  In either case, it's
     # the first thing in the digest, and we can calculate it now, so go
     # ahead and add it now.
     self._masthead = make('masthead.txt',
                           mailing_list=mlist,
                           display_name=mlist.display_name,
                           got_list_email=mlist.posting_address,
                           got_listinfo_url=mlist.script_url('listinfo'),
                           got_request_email=mlist.request_address,
                           got_owner_email=mlist.owner_address,
                           )
     # Set things up for the table of contents.
     if mlist.digest_header_uri is not None:
         try:
             self._header = decorate(mlist, mlist.digest_header_uri)
         except URLError:
             log.exception(
                 'Digest header decorator URI not found ({}): {}'.format(
                     mlist.fqdn_listname, mlist.digest_header_uri))
             self._header = ''
     self._toc = StringIO()
     print(_("Today's Topics:\n"), file=self._toc)
Ejemplo n.º 18
0
def delete_member(mlist, email, admin_notif=None, userack=None):
    """Delete a member right now.

    :param mlist: The mailing list to remove the member from.
    :type mlist: `IMailingList`
    :param email: The email address to unsubscribe.
    :type email: string
    :param admin_notif: Whether the list administrator should be notified that
        this member was deleted.
    :type admin_notif: bool, or None to let the mailing list's
        `admin_notify_mchange` attribute decide.
    :raises NotAMemberError: if the address is not a member of the
        mailing list.
    """
    if userack is None:
        userack = mlist.send_goodbye_message
    if admin_notif is None:
        admin_notif = mlist.admin_notify_mchanges
    # Delete a member, for which we know the approval has been made.
    member = mlist.members.get_member(email)
    if member is None:
        raise NotAMemberError(mlist, email)
    language = member.preferred_language
    member.unsubscribe()
    # And send an acknowledgement to the user...
    if userack:
        send_goodbye_message(mlist, email, language)
    # ...and to the administrator.
    if admin_notif:
        user = getUtility(IUserManager).get_user(email)
        display_name = user.display_name
        subject = _('$mlist.display_name unsubscription notification')
        text = make('adminunsubscribeack.txt',
                    mailing_list=mlist,
                    listname=mlist.display_name,
                    member=formataddr((display_name, email)),
                    )
        msg = OwnerNotification(mlist, subject, text,
                                roster=mlist.administrators)
        msg.send(mlist)
Ejemplo n.º 19
0
 def process(self, mlist, msg, msgdata):
     """See `IHandler`."""
     # Extract the sender's address and find them in the user database
     sender = msgdata.get('original_sender', msg.sender)
     member = mlist.members.get_member(sender)
     if member is None or not member.acknowledge_posts:
         # Either the sender is not a member, in which case we can't know
         # whether they want an acknowlegment or not, or they are a member
         # who definitely does not want an acknowlegment.
         return
     # Okay, they are a member that wants an acknowledgment of their post.
     # Give them their original subject.  BAW: do we want to use the
     # decoded header?
     original_subject = msgdata.get(
         'origsubj', msg.get('subject', _('(no subject)')))
     # Get the user's preferred language.
     language_manager = getUtility(ILanguageManager)
     language = (language_manager[msgdata['lang']]
                 if 'lang' in msgdata
                 else member.preferred_language)
     charset = language_manager[language.code].charset
     # Now get the acknowledgement template.
     display_name = mlist.display_name
     text = make('postack.txt',
                 mailing_list=mlist,
                 language=language.code,
                 wrap=False,
                 subject=oneline(original_subject, charset),
                 list_name=mlist.list_name,
                 display_name=display_name,
                 listinfo_url=mlist.script_url('listinfo'),
                 optionsurl=member.options_url,
                 )
     # Craft the outgoing message, with all headers and attributes
     # necessary for general delivery.  Then enqueue it to the outgoing
     # queue.
     subject = _('$display_name post acknowledgment')
     usermsg = UserNotification(sender, mlist.bounces_address,
                                subject, text, language)
     usermsg.send(mlist)
Ejemplo n.º 20
0
 def process(self, mlist, msg, msgdata):
     """See `IHandler`."""
     # Extract the sender's address and find them in the user database
     sender = msgdata.get('original_sender', msg.sender)
     member = mlist.members.get_member(sender)
     if member is None or not member.acknowledge_posts:
         # Either the sender is not a member, in which case we can't know
         # whether they want an acknowlegment or not, or they are a member
         # who definitely does not want an acknowlegment.
         return
     # Okay, they are a member that wants an acknowledgment of their post.
     # Give them their original subject.  BAW: do we want to use the
     # decoded header?
     original_subject = msgdata.get(
         'origsubj', msg.get('subject', _('(no subject)')))
     # Get the user's preferred language.
     language_manager = getUtility(ILanguageManager)
     language = (language_manager[msgdata['lang']]
                 if 'lang' in msgdata
                 else member.preferred_language)
     # Now get the acknowledgement template.
     display_name = mlist.display_name
     text = make('postack.txt',
                 mailing_list=mlist,
                 language=language.code,
                 wrap=False,
                 subject=oneline(original_subject, in_unicode=True),
                 list_name=mlist.list_name,
                 display_name=display_name,
                 listinfo_url=mlist.script_url('listinfo'),
                 optionsurl=member.options_url,
                 )
     # Craft the outgoing message, with all headers and attributes
     # necessary for general delivery.  Then enqueue it to the outgoing
     # queue.
     subject = _('$display_name post acknowledgment')
     usermsg = UserNotification(sender, mlist.bounces_address,
                                subject, text, language)
     usermsg.send(mlist)
Ejemplo n.º 21
0
def maybe_forward(mlist, msg):
    """Possibly forward bounce messages with no recognizable addresses.

    :param mlist: The mailing list.
    :type mlist: `IMailingList`
    :param msg: The bounce message to scan.
    :type msg: `Message`
    """
    message_id = msg['message-id']
    if (mlist.forward_unrecognized_bounces_to
            is UnrecognizedBounceDisposition.discard):
        blog.error('Discarding unrecognized bounce: {0}'.format(message_id))
        return
    # The notification is either going to go to the list's administrators
    # (owners and moderators), or to the site administrators.  Most of the
    # notification is exactly the same in either case.
    adminurl = mlist.script_url('admin') + '/bounce'
    subject = _('Uncaught bounce notification')
    text = MIMEText(
        make('unrecognized.txt', mlist, adminurl=adminurl),
        _charset=mlist.preferred_language.charset)
    attachment = MIMEMessage(msg)
    if (mlist.forward_unrecognized_bounces_to
            is UnrecognizedBounceDisposition.administrators):
        keywords = dict(roster=mlist.administrators)
    elif (mlist.forward_unrecognized_bounces_to
          is UnrecognizedBounceDisposition.site_owner):
        keywords = {}
    else:
        raise AssertionError('Invalid forwarding disposition: {0}'.format(
                             mlist.forward_unrecognized_bounces_to))
    # Create the notification and send it.
    notice = OwnerNotification(mlist, subject, **keywords)
    notice.set_type('multipart/mixed')
    notice.attach(text)
    notice.attach(attachment)
    notice.send(mlist)
Ejemplo n.º 22
0
def maybe_forward(mlist, msg):
    """Possibly forward bounce messages with no recognizable addresses.

    :param mlist: The mailing list.
    :type mlist: `IMailingList`
    :param msg: The bounce message to scan.
    :type msg: `Message`
    """
    message_id = msg['message-id']
    if (mlist.forward_unrecognized_bounces_to is
            UnrecognizedBounceDisposition.discard):
        blog.error('Discarding unrecognized bounce: {0}'.format(message_id))
        return
    # The notification is either going to go to the list's administrators
    # (owners and moderators), or to the site administrators.  Most of the
    # notification is exactly the same in either case.
    adminurl = mlist.script_url('admin') + '/bounce'
    subject = _('Uncaught bounce notification')
    text = MIMEText(make('unrecognized.txt', mlist, adminurl=adminurl),
                    _charset=mlist.preferred_language.charset)
    attachment = MIMEMessage(msg)
    if (mlist.forward_unrecognized_bounces_to is
            UnrecognizedBounceDisposition.administrators):
        keywords = dict(roster=mlist.administrators)
    elif (mlist.forward_unrecognized_bounces_to is
          UnrecognizedBounceDisposition.site_owner):
        keywords = {}
    else:
        raise AssertionError('Invalid forwarding disposition: {0}'.format(
            mlist.forward_unrecognized_bounces_to))
    # Create the notification and send it.
    notice = OwnerNotification(mlist, subject, **keywords)
    notice.set_type('multipart/mixed')
    notice.attach(text)
    notice.attach(attachment)
    notice.send(mlist)
Ejemplo n.º 23
0
def hold_unsubscription(mlist, email):
    data = dict(email=email)
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(
        RequestType.unsubscription, email, data)
    vlog.info('%s: held unsubscription request from %s',
              mlist.fqdn_listname, email)
    # Possibly notify the administrator of the hold
    if mlist.admin_immed_notify:
        subject = _(
            'New unsubscription request from $mlist.display_name by $email')
        text = make('unsubauth.txt',
                    mailing_list=mlist,
                    email=email,
                    listname=mlist.fqdn_listname,
                    admindb_url=mlist.script_url('admindb'),
                    )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(
            mlist.owner_address, mlist.owner_address,
            subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Ejemplo n.º 24
0
def hold_unsubscription(mlist, address):
    data = dict(address=address)
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(
        RequestType.unsubscription, address, data)
    vlog.info('%s: held unsubscription request from %s',
              mlist.fqdn_listname, address)
    # Possibly notify the administrator of the hold
    if mlist.admin_immed_notify:
        subject = _(
            'New unsubscription request from $mlist.display_name by $address')
        text = make('unsubauth.txt',
                    mailing_list=mlist,
                    address=address,
                    listname=mlist.fqdn_listname,
                    admindb_url=mlist.script_url('admindb'),
                    )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(
            mlist.owner_address, mlist.owner_address,
            subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Ejemplo n.º 25
0
 def process(self, args):
     """See `ICLISubCommand`."""
     language_code = args.language if args.language is not None else system_preferences.preferred_language.code
     # Make sure that the selected language code is known.
     if language_code not in getUtility(ILanguageManager).codes:
         self.parser.error(_("Invalid language code: $language_code"))
         return
     assert len(args.listname) == 1, "Unexpected positional arguments: %s" % args.listname
     # Check to see if the domain exists or not.
     fqdn_listname = args.listname[0]
     listname, at, domain = fqdn_listname.partition("@")
     domain_manager = getUtility(IDomainManager)
     if domain_manager.get(domain) is None and args.domain:
         domain_manager.add(domain)
     # Validate the owner email addresses.  The problem with doing this
     # check in create_list() is that you wouldn't be able to distinguish
     # between an InvalidEmailAddressError for the list name or the
     # owners.  I suppose we could subclass that exception though.
     if args.owners:
         validator = getUtility(IEmailValidator)
         invalid_owners = [owner for owner in args.owners if not validator.is_valid(owner)]
         if invalid_owners:
             invalid = COMMASPACE.join(sorted(invalid_owners))  # noqa
             self.parser.error(_("Illegal owner addresses: $invalid"))
             return
     try:
         mlist = create_list(fqdn_listname, args.owners)
     except InvalidEmailAddressError:
         self.parser.error(_("Illegal list name: $fqdn_listname"))
         return
     except ListAlreadyExistsError:
         self.parser.error(_("List already exists: $fqdn_listname"))
         return
     except BadDomainSpecificationError as domain:
         self.parser.error(_("Undefined domain: $domain"))
         return
     # Find the language associated with the code, then set the mailing
     # list's preferred language to that.
     language_manager = getUtility(ILanguageManager)
     with transaction():
         mlist.preferred_language = language_manager[language_code]
     # Do the notification.
     if not args.quiet:
         print(_("Created mailing list: $mlist.fqdn_listname"))
     if args.notify:
         d = dict(
             listname=mlist.fqdn_listname,  # noqa
             admin_url=mlist.script_url("admin"),  # noqa
             listinfo_url=mlist.script_url("listinfo"),  # noqa
             requestaddr=mlist.request_address,  # noqa
             siteowner=mlist.no_reply_address,  # noqa
         )
         text = make("newlist.txt", mailing_list=mlist, **d)
         # Set the I18N language to the list's preferred language so the
         # header will match the template language.  Stashing and restoring
         # the old translation context is just (healthy? :) paranoia.
         with _.using(mlist.preferred_language.code):
             msg = UserNotification(
                 args.owners,
                 mlist.no_reply_address,
                 _("Your new mailing list: $fqdn_listname"),
                 text,
                 mlist.preferred_language,
             )
             msg.send(mlist)
Ejemplo n.º 26
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.º 27
0
    def test_no_substitutions(self):
        self.assertEqual(
            make('nosub.txt', self.mlist), """\
This is a global template.  It has no substitutions.  It will be
wrapped.""")
Ejemplo n.º 28
0
 def process(self, args):
     """See `ICLISubCommand`."""
     language_code = (args.language
                      if args.language is not None
                      else system_preferences.preferred_language.code)
     # Make sure that the selected language code is known.
     if language_code not in getUtility(ILanguageManager).codes:
         self.parser.error(_('Invalid language code: $language_code'))
         return
     assert len(args.listname) == 1, (
         'Unexpected positional arguments: %s' % args.listname)
     # Check to see if the domain exists or not.
     fqdn_listname = args.listname[0]
     listname, at, domain = fqdn_listname.partition('@')
     domain_manager = getUtility(IDomainManager)
     if domain_manager.get(domain) is None and args.domain:
         domain_manager.add(domain)
     # Validate the owner email addresses.  The problem with doing this
     # check in create_list() is that you wouldn't be able to distinguish
     # between an InvalidEmailAddressError for the list name or the
     # owners.  I suppose we could subclass that exception though.
     if args.owners:
         validator = getUtility(IEmailValidator)
         invalid_owners = [owner for owner in args.owners
                           if not validator.is_valid(owner)]
         if invalid_owners:
             invalid = COMMASPACE.join(sorted(invalid_owners))   # noqa
             self.parser.error(_('Illegal owner addresses: $invalid'))
             return
     try:
         mlist = create_list(fqdn_listname, args.owners)
     except InvalidEmailAddressError:
         self.parser.error(_('Illegal list name: $fqdn_listname'))
         return
     except ListAlreadyExistsError:
         self.parser.error(_('List already exists: $fqdn_listname'))
         return
     except BadDomainSpecificationError as domain:
         self.parser.error(_('Undefined domain: $domain'))
         return
     # Find the language associated with the code, then set the mailing
     # list's preferred language to that.
     language_manager = getUtility(ILanguageManager)
     with transaction():
         mlist.preferred_language = language_manager[language_code]
     # Do the notification.
     if not args.quiet:
         print(_('Created mailing list: $mlist.fqdn_listname'))
     if args.notify:
         d = dict(
             listname        = mlist.fqdn_listname,          # noqa
             admin_url       = mlist.script_url('admin'),    # noqa
             listinfo_url    = mlist.script_url('listinfo'), # noqa
             requestaddr     = mlist.request_address,        # noqa
             siteowner       = mlist.no_reply_address,       # noqa
             )
         text = make('newlist.txt', mailing_list=mlist, **d)
         # Set the I18N language to the list's preferred language so the
         # header will match the template language.  Stashing and restoring
         # the old translation context is just (healthy? :) paranoia.
         with _.using(mlist.preferred_language.code):
             msg = UserNotification(
                 args.owners, mlist.no_reply_address,
                 _('Your new mailing list: $fqdn_listname'),
                 text, mlist.preferred_language)
             msg.send(mlist)
Ejemplo n.º 29
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.
        # XXX How to calculate the reason?
        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,
            reason='XXX',  #reason,
            confirmurl='{0}/{1}'.format(mlist.script_url('confirm'), token),
            admindb_url=mlist.script_url('admindb'),
        )
        # 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
                # We need to regenerate or re-translate a few values in the
                # substitution dictionary.
                #d['reason'] = _(reason) # XXX reason
                substitutions['subject'] = original_subject
                # 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
        # XXX reason
        reason = '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'), reason)
        notify(HoldEvent(mlist, msg, msgdata, self))
Ejemplo n.º 30
0
    def test_substitutions(self):
        self.assertEqual(make('subs.txt', self.mlist,
                              kind='very nice',
                              howmany='a few'), """\
This is a very nice template.  It has a few substitutions.  It will be
wrapped.""")
Ejemplo n.º 31
0
    def test_no_substitutions(self):
        self.assertEqual(make('nosub.txt', self.mlist), """\
This is a global template.  It has no substitutions.  It will be
wrapped.""")
Ejemplo n.º 32
0
    def test_substitutions(self):
        self.assertEqual(
            make('subs.txt', self.mlist, kind='very nice', howmany='a few'),
            """\
This is a very nice template.  It has a few substitutions.  It will be
wrapped.""")