Example #1
0
 def add_message(self, msg, count):
     """Add the message to the digest."""
     if count > 1:
         print(self._separator30, file=self._text)
         print(file=self._text)
     # Each message section contains a few headers.
     for header in config.digests.plain_digest_keep_headers.split():
         if header in msg:
             value = oneline(msg[header], in_unicode=True)
             value = wrap('{}: {}'.format(header, value))
             value = '\n\t'.join(value.split('\n'))
             print(value, file=self._text)
     print(file=self._text)
     # Add the payload.  If the decoded payload is empty, this may be a
     # multipart message.  In that case, just stringify it.
     payload = msg.get_payload(decode=True)
     if not payload:
         payload = msg.as_string().split('\n\n', 1)[1]
     if isinstance(payload, bytes):
         try:
             # Do the decoding inside the try/except so that if the charset
             # conversion fails, we'll just drop back to ascii.
             charset = msg.get_content_charset('us-ascii')
             payload = payload.decode(charset, 'replace')
         except (LookupError, TypeError):
             # Unknown or empty charset.
             payload = payload.decode('us-ascii', 'replace')
     print(payload, file=self._text)
     if not payload.endswith('\n'):
         print(file=self._text)
Example #2
0
    def test_dont_honor_ws(self):
        text = """\
This is a single
paragraph.  It consists
of several sentences
none of
which are
very long.

    This paragraph is
    indented but we don't
    honor whitespace so it
    will be filled.

And here is a second paragraph which
also consists
of several sentences. None of
these are very long
either.
"""
        self.assertEqual(
            wrap(text, honor_leading_ws=False),
            """\
This is a single paragraph.  It consists of several sentences none of
which are very long.

    This paragraph is indented but we don't honor whitespace so it
    will be filled.

And here is a second paragraph which also consists of several
sentences.  None of these are very long either.""",
        )
Example #3
0
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     if mlist.dmarc_mitigate_action is DMARCMitigateAction.no_mitigation:
         # Don't bother to check if we're not going to do anything.
         return False
     dn, addr = parseaddr(msg.get('from'))
     if maybe_mitigate(mlist, addr):
         # If dmarc_mitigate_action is discard or reject, this rule fires
         # and jumps to the 'moderation' chain to do the actual discard.
         # Otherwise, the rule misses but sets a flag for the dmarc handler
         # to do the appropriate action.
         msgdata['dmarc'] = True
         if mlist.dmarc_mitigate_action is DMARCMitigateAction.discard:
             msgdata['moderation_action'] = 'discard'
             msgdata['moderation_reasons'] = [_('DMARC moderation')]
         elif mlist.dmarc_mitigate_action is DMARCMitigateAction.reject:
             listowner = mlist.owner_address  # noqa F841
             reason = (mlist.dmarc_moderation_notice or _(
                 'You are not allowed to post to this mailing '
                 'list From: a domain which publishes a DMARC '
                 'policy of reject or quarantine, and your message'
                 ' has been automatically rejected.  If you think '
                 'that your messages are being rejected in error, '
                 'contact the mailing list owner at ${listowner}.'))
             msgdata['moderation_reasons'] = [wrap(reason)]
             msgdata['moderation_action'] = 'reject'
         else:
             return False
         msgdata['moderation_sender'] = addr
         return True
     return False
Example #4
0
    def test_honor_ws(self):
        text = """\
This is a single
paragraph.  It consists
of several sentences
none of
which are
very long.

    This paragraph is
    indented so it
    won't be filled.

And here is a second paragraph which
also consists
of several sentences. None of
these are very long
either.
"""
        self.assertEqual(
            wrap(text), """\
This is a single paragraph.  It consists of several sentences none of
which are very long.

    This paragraph is
    indented so it
    won't be filled.

And here is a second paragraph which also consists of several
sentences.  None of these are very long either.""")
Example #5
0
 def add_to_toc(self, msg, count):
     """Add a message to the table of contents."""
     subject = msg.get('subject', _('(no subject)'))
     subject = oneline(subject, in_unicode=True)
     # Don't include the redundant subject prefix in the toc
     mo = re.match(
         '(re:? *)?({0})'.format(re.escape(self._mlist.subject_prefix)),
         subject, re.IGNORECASE)
     if mo:
         subject = subject[:mo.start(2)] + subject[mo.end(2):]
     # Take only the first author we find.
     username = ''
     addresses = getaddresses(
         [oneline(msg.get('from', ''), in_unicode=True)])
     if addresses:
         username = addresses[0][0]
         if not username:
             username = addresses[0][1]
     if username:
         username = '******'.format(username)
     lines = wrap('{:2}. {}'.format(count, subject), 65).split('\n')
     # See if the user's name can fit on the last line
     if len(lines[-1]) + len(username) > 70:
         lines.append(username)
     else:
         lines[-1] += username
     # Add this subject to the accumulating topics
     first = True
     for line in lines:
         if first:
             print(' ', line, file=self._toc)
             first = False
         else:
             print('     ', line.lstrip(), file=self._toc)
Example #6
0
 def add_message(self, msg, count):
     """Add the message to the digest."""
     if count > 1:
         print >> self._text, self._separator30
         print >> self._text
     # Each message section contains a few headers.
     for header in config.digests.plain_digest_keep_headers.split():
         if header in msg:
             value = oneline(msg[header], in_unicode=True)
             value = wrap('{0}: {1}'.format(header, value))
             value = '\n\t'.join(value.split('\n'))
             print >> self._text, value
     print >> self._text
     # Add the payload.  If the decoded payload is empty, this may be a
     # multipart message.  In that case, just stringify it.
     payload = msg.get_payload(decode=True)
     payload = (payload if payload else msg.as_string().split('\n\n', 1)[1])
     try:
         charset = msg.get_content_charset('us-ascii')
         payload = unicode(payload, charset, 'replace')
     except (LookupError, TypeError):
         # Unknown or empty charset.
         payload = unicode(payload, 'us-ascii', 'replace')
     print >> self._text, payload
     if not payload.endswith('\n'):
         print >> self._text
Example #7
0
def wrap_message(mlist, msg, msgdata):
    # Create a wrapper message around the original.
    #
    # There are various headers in msg that we don't want, so we basically
    # make a copy of the message, then delete almost everything and set/copy
    # what we want.
    original_msg = copy.deepcopy(msg)
    for key in msg:
        keep = False
        for keeper in KEEPERS:
            if re.match(keeper, key, re.IGNORECASE):
                keep = True
                break
        if not keep:
            del msg[key]
    msg['MIME-Version'] = '1.0'
    msg['Message-ID'] = make_msgid()
    for key, value in munged_headers(mlist, original_msg, msgdata):
        msg[key] = value
    # Are we including dmarc_wrapped_message_text?
    if len(mlist.dmarc_wrapped_message_text) > 0:
        part1 = MIMEText(wrap(mlist.dmarc_wrapped_message_text), 'plain',
                         mlist.preferred_language.charset)
        part1['Content-Disposition'] = 'inline'
        part2 = MIMEMessage(original_msg)
        part2['Content-Disposition'] = 'inline'
        msg['Content-Type'] = 'multipart/mixed'
        msg.set_payload([part1, part2])
    else:
        msg['Content-Type'] = 'message/rfc822'
        msg['Content-Disposition'] = 'inline'
        msg.set_payload([original_msg])
    return
Example #8
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.
     template = getUtility(ITemplateLoader).get(
         'list:member:digest:masthead', mlist)
     self._masthead = wrap(expand(template, mlist, dict(
         # For backward compatibility.
         got_list_email=mlist.posting_address,
         got_request_email=mlist.request_address,
         got_owner_email=mlist.owner_address,
         )))
     # Set things up for the table of contents.
     self._header = decorate('list:member:digest:header', mlist)
     self._toc = StringIO()
     print(_("Today's Topics:\n"), file=self._toc)
Example #9
0
    def test_dont_honor_ws(self):
        text = """\
This is a single
paragraph.  It consists
of several sentences
none of
which are
very long.

    This paragraph is
    indented but we don't
    honor whitespace so it
    will be filled.

And here is a second paragraph which
also consists
of several sentences. None of
these are very long
either.
"""
        self.assertEqual(
            wrap(text, honor_leading_ws=False), """\
This is a single paragraph.  It consists of several sentences none of
which are very long.

    This paragraph is indented but we don't honor whitespace so it
    will be filled.

And here is a second paragraph which also consists of several
sentences.  None of these are very long either.""")
Example #10
0
    def test_honor_ws(self):
        text = """\
This is a single
paragraph.  It consists
of several sentences
none of
which are
very long.

    This paragraph is
    indented so it
    won't be filled.

And here is a second paragraph which
also consists
of several sentences. None of
these are very long
either.
"""
        self.assertEqual(
            wrap(text),
            """\
This is a single paragraph.  It consists of several sentences none of
which are very long.

    This paragraph is
    indented so it
    won't be filled.

And here is a second paragraph which also consists of several
sentences.  None of these are very long either.""",
        )
Example #11
0
 def add_to_toc(self, msg, count):
     """Add a message to the table of contents."""
     subject = msg.get('subject', _('(no subject)'))
     subject = oneline(subject, in_unicode=True)
     # Don't include the redundant subject prefix in the toc
     mo = re.match('(re:? *)?({0})'.format(
         re.escape(self._mlist.subject_prefix)),
                   subject, re.IGNORECASE)
     if mo:
         subject = subject[:mo.start(2)] + subject[mo.end(2):]
     # Take only the first author we find.
     username = ''
     addresses = getaddresses(
         [oneline(msg.get('from', ''), in_unicode=True)])
     if addresses:
         username = addresses[0][0]
         if not username:
             username = addresses[0][1]
     if username:
         username = '******'.format(username)
     lines = wrap('{0:2}. {1}'. format(count, subject), 65).split('\n')
     # See if the user's name can fit on the last line
     if len(lines[-1]) + len(username) > 70:
         lines.append(username)
     else:
         lines[-1] += username
     # Add this subject to the accumulating topics
     first = True
     for line in lines:
         if first:
             print(' ', line, file=self._toc)
             first = False
         else:
             print('     ', line.lstrip(), file=self._toc)
Example #12
0
 def add_message(self, msg, count):
     """Add the message to the digest."""
     if count > 1:
         print(self._separator30, file=self._text)
         print(file=self._text)
     # Each message section contains a few headers.
     for header in config.digests.plain_digest_keep_headers.split():
         if header in msg:
             value = oneline(msg[header], in_unicode=True)
             value = wrap('{0}: {1}'.format(header, value))
             value = '\n\t'.join(value.split('\n'))
             print(value, file=self._text)
     print(file=self._text)
     # Add the payload.  If the decoded payload is empty, this may be a
     # multipart message.  In that case, just stringify it.
     payload = msg.get_payload(decode=True)
     if not payload:
         payload = msg.as_string().split('\n\n', 1)[1]
     if isinstance(payload, bytes):
         try:
             # Do the decoding inside the try/except so that if the charset
             # conversion fails, we'll just drop back to ascii.
             charset = msg.get_content_charset('us-ascii')
             payload = payload.decode(charset, 'replace')
         except (LookupError, TypeError):
             # Unknown or empty charset.
             payload = payload.decode('us-ascii', 'replace')
     print(payload, file=self._text)
     if not payload.endswith('\n'):
         print(file=self._text)
Example #13
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: F841
    if lang is None:
        member = mlist.members.get_member(recip)
        lang = (mlist.preferred_language
                if member is None else member.preferred_language)
    template = getUtility(ITemplateLoader).get('list:user:notice:refuse',
                                               mlist)
    text = wrap(
        expand(
            template,
            mlist,
            dict(
                language=lang.code,
                reason=comment,
                # For backward compatibility.
                request=request,
                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)
Example #14
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')
        template = getUtility(ITemplateLoader).get(
            'list:admin:action:unsubscribe', mlist)
        text = wrap(
            expand(
                template,
                mlist,
                dict(
                    # For backward compatibility.
                    mailing_list=mlist,
                    member=email,
                    email=email,
                )))
        # 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)
    return request_id
Example #15
0
 def process(self, mlist, msg, msgdata, arguments, results):
     """See `IEmailCommand`."""
     # With no argument, print the command and a short description, which
     # is contained in the short_description attribute.
     if len(arguments) == 0:
         length = max(len(command) for command in config.commands)
         format = '{{0: <{0}s}} - {{1}}'.format(length)
         for command_name in sorted(config.commands):
             command = config.commands[command_name]
             short_description = getattr(command, 'short_description',
                                         _('n/a'))
             print(format.format(command.name, short_description),
                   file=results)
         return ContinueProcessing.yes
     elif len(arguments) == 1:
         command_name = arguments[0]
         command = config.commands.get(command_name)
         if command is None:
             print(_('$self.name: no such command: $command_name'),
                   file=results)
             return ContinueProcessing.no
         print('{} {}'.format(command.name, command.argument_description),
               file=results)
         print(command.short_description, file=results)
         if command.short_description != command.description:
             print(wrap(command.description), file=results)
         return ContinueProcessing.yes
     else:
         printable_arguments = SPACE.join(arguments)  # noqa: F841
         print(_('$self.name: too many arguments: $printable_arguments'),
               file=results)
         return ContinueProcessing.no
Example #16
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))
         template = getUtility(ITemplateLoader).get(
             'list:admin:action:subscribe', self.mlist)
         text = wrap(expand(template, self.mlist, dict(member=username, )))
         # 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)
     # The workflow must stop running here.
     raise StopIteration
Example #17
0
 def process(self, mlist, msg, msgdata, arguments, results):
     """See `IEmailCommand`."""
     # With no argument, print the command and a short description, which
     # is contained in the short_description attribute.
     if len(arguments) == 0:
         length = max(len(command) for command in config.commands)
         format = '{{0: <{0}s}} - {{1}}'.format(length)
         for command_name in sorted(config.commands):
             command = config.commands[command_name]
             short_description = getattr(
                 command, 'short_description', _('n/a'))
             print(format.format(command.name, short_description),
                   file=results)
         return ContinueProcessing.yes
     elif len(arguments) == 1:
         command_name = arguments[0]
         command = config.commands.get(command_name)
         if command is None:
             print(_('$self.name: no such command: $command_name'),
                   file=results)
             return ContinueProcessing.no
         print('{0} {1}'.format(command.name, command.argument_description),
               file=results)
         print(command.short_description, file=results)
         if command.short_description != command.description:
             print(wrap(command.description), file=results)
         return ContinueProcessing.yes
     else:
         printable_arguments = SPACE.join(arguments)
         print(_('$self.name: too many arguments: $printable_arguments'),
               file=results)
         return ContinueProcessing.no
Example #18
0
def create(ctx, language, owners, notify, quiet, create_domain, fqdn_listname):
    language_code = (language if 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:
        ctx.fail(_('Invalid language code: $language_code'))
    # Check to see if the domain exists or not.
    listname, at, domain = fqdn_listname.partition('@')
    domain_manager = getUtility(IDomainManager)
    if domain_manager.get(domain) is None and create_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 len(owners) > 0:
        validator = getUtility(IEmailValidator)
        invalid_owners = [
            owner for owner in owners if not validator.is_valid(owner)
        ]
        if invalid_owners:
            invalid = COMMASPACE.join(sorted(invalid_owners))  # noqa: F841
            ctx.fail(_('Illegal owner addresses: $invalid'))
    try:
        mlist = create_list(fqdn_listname, owners)
    except InvalidEmailAddressError:
        ctx.fail(_('Illegal list name: $fqdn_listname'))
    except ListAlreadyExistsError:
        ctx.fail(_('List already exists: $fqdn_listname'))
    except BadDomainSpecificationError as domain:  # noqa: F841
        ctx.fail(_('Undefined domain: $domain'))
    # 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 quiet:
        print(_('Created mailing list: $mlist.fqdn_listname'))
    if notify:
        template = getUtility(ITemplateLoader).get(
            'domain:admin:notice:new-list', mlist)
        text = wrap(
            expand(
                template,
                mlist,
                dict(
                    # For backward compatibility.
                    requestaddr=mlist.request_address,
                    siteowner=mlist.no_reply_address,
                )))
        # 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(owners, mlist.no_reply_address,
                                   _('Your new mailing list: $fqdn_listname'),
                                   text, mlist.preferred_language)
            msg.send(mlist)
Example #19
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)
    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.subscriber.email,
        # For backward compatibility.
        address=member.address.email,
        email=member.address.email,
        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
Example #20
0
def send_welcome_message(mlist, address, language, delivery_mode, text=''):
    """Send a welcome message to a subscriber.

    Prepending to the standard welcome message template is the mailing list's
    welcome message, if there is one.

    :param mlist: the mailing list
    :type mlist: IMailingList
    :param address: The address to respond to
    :type address: string
    :param language: the language of the response
    :type language: ILanguage
    :param delivery_mode: the type of delivery the subscriber is getting
    :type delivery_mode: DeliveryMode
    """
    if mlist.welcome_message_uri:
        try:
            uri = expand(mlist.welcome_message_uri, dict(
                listname=mlist.fqdn_listname,
                language=language.code,
                ))
            welcome_message = getUtility(ITemplateLoader).get(uri)
        except URLError:
            log.exception('Welcome message URI not found ({0}): {1}'.format(
                mlist.fqdn_listname, mlist.welcome_message_uri))
            welcome = ''
        else:
            welcome = wrap(welcome_message)
    else:
        welcome = ''
    # Find the IMember object which is subscribed to the mailing list, because
    # from there, we can get the member's options url.
    member = mlist.members.get_member(address)
    user_name = member.user.display_name
    options_url = member.options_url
    # Get the text from the template.
    text = expand(welcome, dict(
        fqdn_listname=mlist.fqdn_listname,
        list_name=mlist.display_name,
        listinfo_uri=mlist.script_url('listinfo'),
        list_requests=mlist.request_address,
        user_name=user_name,
        user_address=address,
        user_options_uri=options_url,
        ))
    if delivery_mode is not DeliveryMode.regular:
        digmode = _(' (Digest mode)')
    else:
        digmode = ''
    msg = UserNotification(
        formataddr((user_name, address)),
        mlist.request_address,
        _('Welcome to the "$mlist.display_name" mailing list${digmode}'),
        text, language)
    msg['X-No-Archive'] = 'yes'
    msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
Example #21
0
    def test_simple_wrap(self):
        text = """\
This is a single
paragraph.  It consists
of several sentences
none of
which are
very long.
"""
        self.assertEqual(wrap(text), """\
This is a single paragraph.  It consists of several sentences none of
which are very long.""")
Example #22
0
    def test_simple_wrap(self):
        text = """\
This is a single
paragraph.  It consists
of several sentences
none of
which are
very long.
"""
        self.assertEqual(wrap(text), """\
This is a single paragraph.  It consists of several sentences none of
which are very long.""")
Example #23
0
    def process(self, mlist, msg, msgdata):
        """See `IHandler`."""
        # Short circuit if we've already calculated the recipients list,
        # regardless of whether the list is empty or not.
        if 'recipients' in msgdata:
            return
        # Should the original sender should be included in the recipients list?
        include_sender = True
        member = mlist.members.get_member(msg.sender)
        if member and not member.receive_own_postings:
            include_sender = False
        # Support for urgent messages, which bypasses digests and disabled
        # delivery and forces an immediate delivery to all members Right Now.
        # We are specifically /not/ allowing the site admins password to work
        # here because we want to discourage the practice of sending the site
        # admin password through email in the clear. (see also Approve.py)
        #
        # XXX This is broken.
        missing = object()
        password = msg.get('urgent', missing)
        if password is not missing:
            if mlist.Authenticate((config.AuthListModerator,
                                   config.AuthListAdmin),
                                  password):
                recipients = mlist.getMemberCPAddresses(
                    mlist.getRegularMemberKeys() +
                    mlist.getDigestMemberKeys())
                msgdata['recipients'] = recipients
                return
            else:
                # Bad Urgent: password, so reject it instead of passing it on.
                # I think it's better that the sender know they screwed up
                # than to deliver it normally.
                text = _("""\
Your urgent message to the $mlist.display_name mailing list was not authorized
for delivery.  The original message as received by Mailman is attached.
""")
                raise RejectMessage(wrap(text))
        # Calculate the regular recipients of the message
        recipients = set(member.address.email
                         for member in mlist.regular_members.members
                         if member.delivery_status == DeliveryStatus.enabled)
        # Remove the sender if they don't want to receive their own posts
        if not include_sender and member.address.email in recipients:
            recipients.remove(member.address.email)
        # Handle topic classifications
        # XXX: Disabled for now until we fix it properly
        #
        # do_topic_filters(mlist, msg, msgdata, recipients)
        #
        # Bookkeeping
        msgdata['recipients'] = recipients
Example #24
0
    def test_indentation_boundary(self):
        text = """\
This is a single paragraph
that consists of one sentence.
    And another one that breaks
    because it is indented.
Followed by one more paragraph.
"""
        self.assertEqual(wrap(text), """\
This is a single paragraph that consists of one sentence.
    And another one that breaks
    because it is indented.
Followed by one more paragraph.""")
Example #25
0
    def test_indentation_boundary(self):
        text = """\
This is a single paragraph
that consists of one sentence.
    And another one that breaks
    because it is indented.
Followed by one more paragraph.
"""
        self.assertEqual(wrap(text), """\
This is a single paragraph that consists of one sentence.
    And another one that breaks
    because it is indented.
Followed by one more paragraph.""")
 def check(self, mlist, msg, msgdata):
     """See `IRule`."""
     if not as_boolean(config.mailman.hold_digest):
         return False
     # Convert the header value to a str because it may be an
     # email.header.Header instance.
     subject = str(msg.get('subject', '')).strip()
     if DIGRE.search(subject):
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('Message has a digest subject'))
         return True
         # Get the masthead, but without emails.
     mastheadtxt = getUtility(ITemplateLoader).get(
         'list:member:digest:masthead', mlist)
     mastheadtxt = wrap(
         expand(
             mastheadtxt, mlist,
             dict(
                 display_name=mlist.display_name,
                 listname='',
                 list_id=mlist.list_id,
                 request_email='',
                 owner_email='',
             )))
     msgtext = ''
     for part in msg.walk():
         if part.get_content_maintype() == 'text':
             cset = part.get_content_charset('utf-8')
             msgtext += part.get_payload(decode=True).decode(
                 cset, errors='replace')
     matches = 0
     lines = mastheadtxt.splitlines()
     for line in lines:
         line = line.strip()
         if not line:
             continue
         if msgtext.find(line) >= 0:
             matches += 1
     if matches >= int(config.mailman.masthead_threshold):
         msgdata['moderation_sender'] = msg.sender
         with _.defer_translation():
             # This will be translated at the point of use.
             msgdata.setdefault('moderation_reasons', []).append(
                 _('Message quotes digest boilerplate'))
         return True
     return False
Example #27
0
def _get_message(uri_template, mlist, language):
    if not uri_template:
        return ''
    try:
        uri = expand(uri_template, dict(
            listname=mlist.fqdn_listname,
            language=language.code,
            ))
        message = getUtility(ITemplateLoader).get(uri)
    except URLError:
        log.exception('Message URI not found ({0}): {1}'.format(
            mlist.fqdn_listname, uri_template))
        return ''
    else:
        return wrap(message)
Example #28
0
def _get_message(uri_template, mlist, language):
    if not uri_template:
        return ''
    try:
        uri = expand(
            uri_template,
            dict(
                listname=mlist.fqdn_listname,
                language=language.code,
            ))
        message = getUtility(ITemplateLoader).get(uri)
    except URLError:
        log.exception('Message URI not found ({0}): {1}'.format(
            mlist.fqdn_listname, uri_template))
        return ''
    else:
        return wrap(message)
def send_welcome_message(mlist, member, language, text=''):
    """Send a welcome message to a subscriber.

    Prepending to the standard welcome message template is the mailing list's
    welcome message, if there is one.

    :param mlist: The mailing list.
    :type mlist: IMailingList
    :param member: The member to send the welcome message to.
    :param address: IMember
    :param language: The language of the response.
    :type language: ILanguage
    """
    welcome_message = wrap(
        getUtility(ITemplateLoader).get('list:user:notice:welcome',
                                        mlist,
                                        language=language.code))
    display_name = member.display_name
    # Get the text from the template.
    text = expand(
        welcome_message,
        mlist,
        dict(
            user_name=display_name,
            user_email=member.address.email,
            # For backward compatibility.
            user_address=member.address.email,
            fqdn_listname=mlist.fqdn_listname,
            list_name=mlist.display_name,
            list_requests=mlist.request_address,
        ))
    digmode = (
        ''  # noqa: F841
        if member.delivery_mode is DeliveryMode.regular else
        _(' (Digest mode)'))
    msg = UserNotification(
        formataddr(
            (display_name, member.address.email)), mlist.request_address,
        _('Welcome to the "$mlist.display_name" mailing list${digmode}'), text,
        language)
    msg['X-No-Archive'] = 'yes'
    msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
Example #30
0
    def test_two_paragraphs(self):
        text = """\
This is a single
paragraph.  It consists
of several sentences
none of
which are
very long.

And here is a second paragraph which
also consists
of several sentences. None of
these are very long
either.
"""
        self.assertEqual(wrap(text), """\
This is a single paragraph.  It consists of several sentences none of
which are very long.

And here is a second paragraph which also consists of several
sentences.  None of these are very long either.""")
Example #31
0
    def test_two_paragraphs(self):
        text = """\
This is a single
paragraph.  It consists
of several sentences
none of
which are
very long.

And here is a second paragraph which
also consists
of several sentences. None of
these are very long
either.
"""
        self.assertEqual(wrap(text), """\
This is a single paragraph.  It consists of several sentences none of
which are very long.

And here is a second paragraph which also consists of several
sentences.  None of these are very long either.""")
def send_user_disable_warning(mlist, address, language):
    """Sends a warning mail to the user reminding the person to
    reenable its DeliveryStatus.

    :param mlist: The mailing list
    :type mlist: IMailingList
    :param address: The address of the member
    :type address: string.
    :param language: member's preferred language
    :type language: ILanguage
    """
    warning_message = wrap(
        getUtility(ITemplateLoader).get('list:user:notice:warning',
                                        mlist,
                                        language=language.code))
    warning_message_text = expand(warning_message, mlist,
                                  dict(sender_email=address))
    msg = UserNotification(
        address, mlist.bounces_address,
        _('Your subscription for ${mlist.display_name} mailing list'
          ' has been disabled'), warning_message_text, language)
    msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
def send_goodbye_message(mlist, address, language):
    """Send a goodbye message to a subscriber.

    Prepending to the standard goodbye message template is the mailing list's
    goodbye message, if there is one.

    :param mlist: the mailing list
    :type mlist: IMailingList
    :param address: The address to respond to
    :type address: string
    :param language: the language of the response
    :type language: ILanguage
    """
    goodbye_message = wrap(
        getUtility(ITemplateLoader).get('list:user:notice:goodbye',
                                        mlist,
                                        language=language.code))
    msg = UserNotification(
        address, mlist.bounces_address,
        _('You have been unsubscribed from the $mlist.display_name '
          'mailing list'), goodbye_message, language)
    msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
Example #34
0
 def _step_get_moderator_approval(self):
     self._set_token(TokenOwner.moderator)
     self.push('unsubscribe_from_restored')
     self.save()
     log.info('{}: held unsubscription request from {}'.format(
         self.mlist.fqdn_listname, self.address.email))
     if self.mlist.admin_immed_notify:
         subject = _(
             'New unsubscription request to $self.mlist.display_name '
             'from $self.address.email')
         username = formataddr(
             (self.subscriber.display_name, self.address.email))
         template = getUtility(ITemplateLoader).get(
             'list:admin:action:unsubscribe', self.mlist)
         text = wrap(expand(template, self.mlist, dict(member=username, )))
         # 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
Example #35
0
def send_goodbye_message(mlist, address, language):
    """Send a goodbye message to a subscriber.

    Prepending to the standard goodbye message template is the mailing list's
    goodbye message, if there is one.

    :param mlist: the mailing list
    :type mlist: IMailingList
    :param address: The address to respond to
    :type address: string
    :param language: the language of the response
    :type language: string
    """
    if mlist.goodbye_msg:
        goodbye = wrap(mlist.goodbye_msg) + '\n'
    else:
        goodbye = ''
    msg = UserNotification(
        address, mlist.bounces_address,
        _('You have been unsubscribed from the $mlist.display_name '
          'mailing list'),
        goodbye, language)
    msg.send(mlist, verp=as_boolean(config.mta.verp_personalized_deliveries))
Example #36
0
 def add_message(self, msg, count):
     """Add the message to the digest."""
     if count > 1:
         print(self._separator30, file=self._text)
         print(file=self._text)
     # Each message section contains a few headers.
     # add the Message: n header first.
     print('Message: {}'.format(count), file=self._text)
     # Then the others.
     for header in config.digests.plain_digest_keep_headers.split():
         if header in msg:
             value = oneline(msg[header], in_unicode=True)
             value = wrap('{}: {}'.format(header, value))
             value = '\n\t'.join(value.split('\n'))
             print(value, file=self._text)
     print(file=self._text)
     # Get the scrubbed payload.  This is the original payload with all
     # non text/plain parts replaced by notes that they've been removed.
     payload = scrub(msg)
     # Add the payload.
     print(payload, file=self._text)
     if not payload.endswith('\n'):
         print(file=self._text)
Example #37
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))
Example #38
0
def do_list_categories(mlist, k, subcat, outfp):
    info = mlist.GetConfigInfo(k, subcat)
    label, gui = mlist.GetConfigCategories()[k]
    if info is None:
        return
    charset = mlist.preferred_language.charset
    print >> outfp, '##', k.capitalize(), _('options')
    print >> outfp, '#'
    # First, massage the descripton text, which could have obnoxious
    # leading whitespace on second and subsequent lines due to
    # triple-quoted string nonsense in the source code.
    desc = NL.join([s.lstrip() for s in info[0].splitlines()])
    # Print out the category description
    desc = wrap(desc)
    for line in desc.splitlines():
        print >> outfp, '#', line
    print >> outfp
    for data in info[1:]:
        if not isinstance(data, tuple):
            continue
        varname = data[0]
        # Variable could be volatile
        if varname[0] == '_':
            continue
        vtype = data[1]
        # First, massage the descripton text, which could have
        # obnoxious leading whitespace on second and subsequent lines
        # due to triple-quoted string nonsense in the source code.
        desc = NL.join([s.lstrip() for s in data[-1].splitlines()])
        # Now strip out all HTML tags
        desc = re.sub('<.*?>', '', desc)
        # And convert &lt;/&gt; to <>
        desc = re.sub('&lt;', '<', desc)
        desc = re.sub('&gt;', '>', desc)
        # Print out the variable description.
        desc = wrap(desc)
        for line in desc.split('\n'):
            print >> outfp, '#', line
        # munge the value based on its type
        value = None
        if hasattr(gui, 'getValue'):
            value = gui.getValue(mlist, vtype, varname, data[2])
        if value is None and not varname.startswith('_'):
            value = getattr(mlist, varname)
        if vtype in (config.String, config.Text, config.FileUpload):
            print >> outfp, varname, '=',
            lines = value.splitlines()
            if not lines:
                print >> outfp, "''"
            elif len(lines) == 1:
                if charset != 'us-ascii' and nonasciipat.search(lines[0]):
                    # This is more readable for non-english list.
                    print >> outfp, '"' + lines[0].replace('"', '\\"') + '"'
                else:
                    print >> outfp, repr(lines[0])
            else:
                if charset == 'us-ascii' and nonasciipat.search(value):
                    # Normally, an english list should not have non-ascii char.
                    print >> outfp, repr(NL.join(lines))
                else:
                    outfp.write(' """')
                    outfp.write(NL.join(lines).replace('"', '\\"'))
                    outfp.write('"""\n')
        elif vtype in (config.Radio, config.Toggle):
            print >> outfp, '#'
            print >> outfp, '#', _('legal values are:')
            # TBD: This is disgusting, but it's special cased
            # everywhere else anyway...
            if varname == 'subscribe_policy' and \
                   not config.ALLOW_OPEN_SUBSCRIBE:
                i = 1
            else:
                i = 0
            for choice in data[2]:
                print >> outfp, '#   ', i, '= "%s"' % choice
                i += 1
            print >> outfp, varname, '=', repr(value)
        else:
            print >> outfp, varname, '=', repr(value)
        print >> outfp
Example #39
0
 def process(self, mlist, msg, msgdata):
     """See `IHandler`."""
     # There are several cases where the replybot is short-circuited:
     # * the original message has an "X-Ack: No" header
     # * the message has a Precedence header with values bulk, junk, or
     #   list, and there's no explicit "X-Ack: yes" header
     # * the message metadata has a true 'noack' key
     ack = msg.get('x-ack', '').lower()
     if ack == 'no' or msgdata.get('noack'):
         return
     precedence = msg.get('precedence', '').lower()
     if ack != 'yes' and precedence in ('bulk', 'junk', 'list'):
         return
     # Check to see if the list is even configured to autorespond to this
     # email message.  Note: the incoming message processors should set the
     # destination key in the message data.
     if msgdata.get('to_owner'):
         if mlist.autorespond_owner is ResponseAction.none:
             return
         response_type = Response.owner
         response_text = mlist.autoresponse_owner_text
     elif msgdata.get('to_request'):
         if mlist.autorespond_requests is ResponseAction.none:
             return
         response_type = Response.command
         response_text = mlist.autoresponse_request_text
     elif msgdata.get('to_list'):
         if mlist.autorespond_postings is ResponseAction.none:
             return
         response_type = Response.postings
         response_text = mlist.autoresponse_postings_text
     else:
         # There are no automatic responses for any other destination.
         return
     # Now see if we're in the grace period for this sender.  grace_period
     # = 0 means always automatically respond, as does an "X-Ack: yes"
     # header (useful for debugging).
     response_set = IAutoResponseSet(mlist)
     user_manager = getUtility(IUserManager)
     address = user_manager.get_address(msg.sender)
     if address is None:
         address = user_manager.create_address(msg.sender)
     grace_period = mlist.autoresponse_grace_period
     if grace_period > ALWAYS_REPLY and ack != 'yes':
         last = response_set.last_response(address, response_type)
         if last is not None and last.date_sent + grace_period > today():
             return
     # Okay, we know we're going to respond to this sender, craft the
     # message, send it, and update the database.
     display_name = mlist.display_name
     subject = _(
         'Auto-response for your message to the "$display_name" '
         'mailing list')
     # Do string interpolation into the autoresponse text
     d = dict(list_name = mlist.list_name,
              display_name = display_name,
              listurl = mlist.script_url('listinfo'),
              requestemail = mlist.request_address,
              owneremail = mlist.owner_address,
              )
     # Interpolation and Wrap the response text.
     text = wrap(expand(response_text, d))
     outmsg = UserNotification(msg.sender, mlist.bounces_address,
                               subject, text, mlist.preferred_language)
     outmsg['X-Mailer'] = _('The Mailman Replybot')
     # prevent recursions and mail loops!
     outmsg['X-Ack'] = 'No'
     outmsg.send(mlist)
     response_set.response_sent(address, response_type)
Example #40
0
 def process(self, mlist, msg, msgdata):
     """See `IHandler`."""
     # There are several cases where the replybot is short-circuited:
     # * the original message has an "X-Ack: No" header
     # * the message has a Precedence header with values bulk, junk, or
     #   list, and there's no explicit "X-Ack: yes" header
     # * the message metadata has a true 'noack' key
     ack = msg.get('x-ack', '').lower()
     if ack == 'no' or msgdata.get('noack'):
         return
     precedence = msg.get('precedence', '').lower()
     if ack != 'yes' and precedence in ('bulk', 'junk', 'list'):
         return
     # Check to see if the list is even configured to autorespond to this
     # email message.  Note: the incoming message processors should set the
     # destination key in the message data.
     if msgdata.get('to_owner'):
         if mlist.autorespond_owner is ResponseAction.none:
             return
         response_type = Response.owner
         response_text = mlist.autoresponse_owner_text
     elif msgdata.get('to_request'):
         if mlist.autorespond_requests is ResponseAction.none:
             return
         response_type = Response.command
         response_text = mlist.autoresponse_request_text
     elif msgdata.get('to_list'):
         if mlist.autorespond_postings is ResponseAction.none:
             return
         response_type = Response.postings
         response_text = mlist.autoresponse_postings_text
     else:
         # There are no automatic responses for any other destination.
         return
     # Now see if we're in the grace period for this sender.  grace_period
     # = 0 means always automatically respond, as does an "X-Ack: yes"
     # header (useful for debugging).
     response_set = IAutoResponseSet(mlist)
     user_manager = getUtility(IUserManager)
     address = user_manager.get_address(msg.sender)
     if address is None:
         address = user_manager.create_address(msg.sender)
     grace_period = mlist.autoresponse_grace_period
     if grace_period > ALWAYS_REPLY and ack != 'yes':
         last = response_set.last_response(address, response_type)
         if last is not None and last.date_sent + grace_period > today():
             return
     # Okay, we know we're going to respond to this sender, craft the
     # message, send it, and update the database.
     display_name = mlist.display_name
     subject = _('Auto-response for your message to the "$display_name" '
                 'mailing list')
     # Do string interpolation into the autoresponse text
     d = dict(
         list_name=mlist.list_name,
         display_name=display_name,
         requestemail=mlist.request_address,
         owneremail=mlist.owner_address,
     )
     # Interpolation and Wrap the response text.
     text = wrap(expand(response_text, mlist, d))
     outmsg = UserNotification(msg.sender, mlist.bounces_address, subject,
                               text, mlist.preferred_language)
     outmsg['X-Mailer'] = _('The Mailman Replybot')
     # prevent recursions and mail loops!
     outmsg['X-Ack'] = 'No'
     outmsg.send(mlist)
     response_set.response_sent(address, response_type)
Example #41
0
def do_list_categories(mlist, k, subcat, outfp):
    info = mlist.GetConfigInfo(k, subcat)
    label, gui = mlist.GetConfigCategories()[k]
    if info is None:
        return
    charset = mlist.preferred_language.charset
    print >> outfp, '##', k.capitalize(), _('options')
    print >> outfp, '#'
    # First, massage the descripton text, which could have obnoxious
    # leading whitespace on second and subsequent lines due to
    # triple-quoted string nonsense in the source code.
    desc = NL.join([s.lstrip() for s in info[0].splitlines()])
    # Print out the category description
    desc = wrap(desc)
    for line in desc.splitlines():
        print >> outfp, '#', line
    print >> outfp
    for data in info[1:]:
        if not isinstance(data, tuple):
            continue
        varname = data[0]
        # Variable could be volatile
        if varname[0] == '_':
            continue
        vtype = data[1]
        # First, massage the descripton text, which could have
        # obnoxious leading whitespace on second and subsequent lines
        # due to triple-quoted string nonsense in the source code.
        desc = NL.join([s.lstrip() for s in data[-1].splitlines()])
        # Now strip out all HTML tags
        desc = re.sub('<.*?>', '', desc)
        # And convert &lt;/&gt; to <>
        desc = re.sub('&lt;', '<', desc)
        desc = re.sub('&gt;', '>', desc)
        # Print out the variable description.
        desc = wrap(desc)
        for line in desc.split('\n'):
            print >> outfp, '#', line
        # munge the value based on its type
        value = None
        if hasattr(gui, 'getValue'):
            value = gui.getValue(mlist, vtype, varname, data[2])
        if value is None and not varname.startswith('_'):
            value = getattr(mlist, varname)
        if vtype in (config.String, config.Text, config.FileUpload):
            print >> outfp, varname, '=',
            lines = value.splitlines()
            if not lines:
                print >> outfp, "''"
            elif len(lines) == 1:
                if charset != 'us-ascii' and nonasciipat.search(lines[0]):
                    # This is more readable for non-english list.
                    print >> outfp, '"' + lines[0].replace('"', '\\"') + '"'
                else:
                    print >> outfp, repr(lines[0])
            else:
                if charset == 'us-ascii' and nonasciipat.search(value):
                    # Normally, an english list should not have non-ascii char.
                    print >> outfp, repr(NL.join(lines))
                else:
                    outfp.write(' """')
                    outfp.write(NL.join(lines).replace('"', '\\"'))
                    outfp.write('"""\n')
        elif vtype in (config.Radio, config.Toggle):
            print >> outfp, '#'
            print >> outfp, '#', _('legal values are:')
            # TBD: This is disgusting, but it's special cased
            # everywhere else anyway...
            if varname == 'subscribe_policy' and \
                   not config.ALLOW_OPEN_SUBSCRIBE:
                i = 1
            else:
                i = 0
            for choice in data[2]:
                print >> outfp, '#   ', i, '= "%s"' % choice
                i += 1
            print >> outfp, varname, '=', repr(value)
        else:
            print >> outfp, varname, '=', repr(value)
        print >> outfp
Example #42
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))
Example #43
0
def _compose_reasons(msgdata, column=66):
    # Rules can add reasons to the metadata.
    reasons = msgdata.get('moderation_reasons', [_('N/A')])
    return NL.join(
        [(SPACE * 4) + wrap(_(reason), column=column)
         for reason in reasons])
Example #44
0
def _compose_reasons(msgdata, column=66):
    # Rules can add reasons to the metadata.
    reasons = msgdata.get('moderation_reasons', [_('N/A')])
    return NL.join(
        [(SPACE * 4) + wrap(_(reason), column=column)
         for reason in reasons])
Example #45
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.
        template = getUtility(ITemplateLoader).get(
            'list:user:notice:no-more-today', mlist,
            language=language.code)
        text = wrap(expand(template, mlist, dict(
            language=language.code,
            count=todays_count,
            sender_email=sender,
            # For backward compatibility.
            sender=sender,
            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
Example #46
0
 def test_wrap_blank_paragraph(self):
     self.assertEqual(string.wrap('\n\n'), '\n\n')
Example #47
0
 def test_wrap_blank_paragraph(self):
     self.assertEqual(string.wrap('\n\n'), '\n\n')