示例#1
0
文件: decorate.py 项目: bksim/mailman
def decorate(mlist, uri, extradict=None):
    """Expand the decoration template."""
    if uri is None:
        return ''
    # Get the decorator template.
    loader = getUtility(ITemplateLoader)
    template_uri = expand(uri, dict(
        listname=mlist.fqdn_listname,
        language=mlist.preferred_language.code,
        ))
    template = loader.get(template_uri)
    # Create a dictionary which includes the default set of interpolation
    # variables allowed in headers and footers.  These will be augmented by
    # any key/value pairs in the extradict.
    substitutions = dict(
        fqdn_listname = mlist.fqdn_listname,
        list_name     = mlist.list_name,
        host_name     = mlist.mail_host,
        display_name  = mlist.display_name,
        listinfo_uri  = mlist.script_url('listinfo'),
        list_requests = mlist.request_address,
        description   = mlist.description,
        info          = mlist.info,
        )
    if extradict is not None:
        substitutions.update(extradict)
    text = expand(template, substitutions)
    # Turn any \r\n line endings into just \n
    return re.sub(r' *\r?\n', r'\n', text)
示例#2
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))
示例#3
0
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 = _get_message(mlist.welcome_message_uri, mlist, language)
    options_url = member.options_url
    # Get the text from the template.
    display_name = ('' if member.user is None else member.user.display_name)
    text = expand(welcome_message, dict(
        fqdn_listname=mlist.fqdn_listname,
        list_name=mlist.display_name,
        listinfo_uri=mlist.script_url('listinfo'),
        list_requests=mlist.request_address,
        user_name=display_name,
        user_address=member.address.email,
        user_options_uri=options_url,
        ))
    digmode = ('' 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))
示例#4
0
 def list_url(self, mlist):
     """See `IArchiver`."""
     # XXX What about private MHonArc archives?
     return expand(
         self.base_url,
         dict(listname=mlist.fqdn_listname, hostname=mlist.domain.url_host, fqdn_listname=mlist.fqdn_listname),
     )
示例#5
0
 def get(self, store, name, context, **kws):
     """See `ITemplateManager`."""
     template = store.query(Template).filter(
         Template.name == name, Template.context == context).one_or_none()
     if template is None:
         return None
     actual_uri = expand(template.uri, None, kws)
     cache_mgr = getUtility(ICacheManager)
     contents = cache_mgr.get(actual_uri)
     if contents is None:
         # It's likely that the cached contents have expired.
         auth = {}
         if template.username is not None:
             auth['auth'] = (template.username, template.password)
         try:
             contents = protocols.get(actual_uri, **auth)
         except HTTPError as error:
             # 404/NotFound errors are interpreted as missing templates,
             # for which we'll return the default (i.e. the empty string).
             # All other exceptions get passed up the chain.
             if error.response.status_code != 404:
                 raise
             log.exception('Cannot retrieve template at {} ({})'.format(
                 actual_uri, auth.get('auth', '<no authorization>')))
             return ''
         # We don't need to cache mailman: contents since those are already
         # on the file system.
         if urlparse(actual_uri).scheme != 'mailman':
             cache_mgr.add(actual_uri, contents)
     return contents
示例#6
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)
示例#7
0
    def __init__(self, name, slice=None):
        """Create a runner.

        :param slice: The slice number for this runner.  This is passed
            directly to the underlying `ISwitchboard` object.  This is ignored
            for runners that don't manage a queue.
        :type slice: int or None
        """
        # Grab the configuration section.
        self.name = name
        section = getattr(config, 'runner.' + name)
        substitutions = config.paths
        substitutions['name'] = name
        numslices = int(section.instances)
        # Check whether the runner is queue runner or not; non-queue runner
        # should not have queue_directory or switchboard instance.
        if self.is_queue_runner:
            self.queue_directory = expand(section.path, substitutions)
            self.switchboard = Switchboard(
                name, self.queue_directory, slice, numslices, True)
        else:
            self.queue_directory = None
            self.switchboard= None
        self.sleep_time = as_timedelta(section.sleep_time)
        # sleep_time is a timedelta; turn it into a float for time.sleep().
        self.sleep_float = (86400 * self.sleep_time.days +
                            self.sleep_time.seconds +
                            self.sleep_time.microseconds / 1.0e6)
        self.max_restarts = int(section.max_restarts)
        self.start = as_boolean(section.start)
        self._stop = False
        self.status = 0
示例#8
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)
示例#9
0
文件: verp.py 项目: omunroe-com/mlman
    def _get_sender(self, mlist, msg, msgdata):
        """Return the recipient's address VERP encoded in the sender.

        :param mlist: The mailing list being delivered to.
        :type mlist: `IMailingList`
        :param msg: The original message being delivered.
        :type msg: `Message`
        :param msgdata: Additional message metadata for this delivery.
        :type msgdata: dictionary
        """
        sender = super()._get_sender(mlist, msg, msgdata)
        if msgdata.get('verp', False):
            log.debug('VERPing %s', msg.get('message-id'))
            recipient = msgdata['recipient']
            sender_mailbox, sender_domain = split_email(sender)
            # Encode the recipient's address for VERP.
            recipient_mailbox, recipient_domain = split_email(recipient)
            if recipient_domain is None:
                # The recipient address is not fully-qualified.  We can't
                # deliver it to this person, nor can we craft a valid verp
                # header.  I don't think there's much we can do except ignore
                # this recipient.
                log.info('Skipping VERP delivery to unqual recip: %s',
                         recipient)
                return sender
            return '{0}@{1}'.format(
                expand(
                    config.mta.verp_format, mlist,
                    dict(bounces=sender_mailbox,
                         local=recipient_mailbox,
                         domain=DOT.join(recipient_domain))),
                DOT.join(sender_domain))
        else:
            return sender
示例#10
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
示例#11
0
    def _get_sender(self, mlist, msg, msgdata):
        """Return the recipient's address VERP encoded in the sender.

        :param mlist: The mailing list being delivered to.
        :type mlist: `IMailingList`
        :param msg: The original message being delivered.
        :type msg: `Message`
        :param msgdata: Additional message metadata for this delivery.
        :type msgdata: dictionary
        """
        sender = super(VERPMixin, self)._get_sender(mlist, msg, msgdata)
        if msgdata.get("verp", False):
            log.debug("VERPing %s", msg.get("message-id"))
            recipient = msgdata["recipient"]
            sender_mailbox, sender_domain = split_email(sender)
            # Encode the recipient's address for VERP.
            recipient_mailbox, recipient_domain = split_email(recipient)
            if recipient_domain is None:
                # The recipient address is not fully-qualified.  We can't
                # deliver it to this person, nor can we craft a valid verp
                # header.  I don't think there's much we can do except ignore
                # this recipient.
                log.info("Skipping VERP delivery to unqual recip: %s", recipient)
                return sender
            return "{0}@{1}".format(
                expand(
                    config.mta.verp_format,
                    dict(bounces=sender_mailbox, local=recipient_mailbox, domain=DOT.join(recipient_domain)),
                ),
                DOT.join(sender_domain),
            )
        else:
            return sender
示例#12
0
    def __init__(self, name, slice=None):
        """Create a runner.

        :param slice: The slice number for this runner.  This is passed
            directly to the underlying `ISwitchboard` object.  This is ignored
            for runners that don't manage a queue.
        :type slice: int or None
        """
        # Grab the configuration section.
        self.name = name
        section = getattr(config, 'runner.' + name)
        substitutions = config.paths
        substitutions['name'] = name
        numslices = int(section.instances)
        # Check whether the runner is queue runner or not; non-queue runner
        # should not have queue_directory or switchboard instance.
        if self.is_queue_runner:
            self.queue_directory = expand(section.path, None, substitutions)
            self.switchboard = Switchboard(name, self.queue_directory, slice,
                                           numslices, True)
        else:
            self.queue_directory = None
            self.switchboard = None
        self.sleep_time = as_timedelta(section.sleep_time)
        # sleep_time is a timedelta; turn it into a float for time.sleep().
        self.sleep_float = (86400 * self.sleep_time.days +
                            self.sleep_time.seconds +
                            self.sleep_time.microseconds / 1.0e6)
        self.max_restarts = int(section.max_restarts)
        self.start = as_boolean(section.start)
        self._stop = False
        self.status = 0
示例#13
0
文件: base.py 项目: adam-iris/mailman
 def initialize(self, debug=None):
     """See `IDatabase`."""
     # Calculate the engine url.
     url = expand(config.database.url, config.paths)
     self._prepare(url)
     log.debug('Database url: %s', url)
     # XXX By design of SQLite, database file creation does not honor
     # umask.  See their ticket #1193:
     # http://www.sqlite.org/cvstrac/tktview?tn=1193,31
     #
     # This sucks for us because the mailman.db file /must/ be group
     # writable, however even though we guarantee our umask is 002 here, it
     # still gets created without the necessary g+w permission, due to
     # SQLite's policy.  This should only affect SQLite engines because its
     # the only one that creates a little file on the local file system.
     # This kludges around their bug by "touch"ing the database file before
     # SQLite has any chance to create it, thus honoring the umask and
     # ensuring the right permissions.  We only try to do this for SQLite
     # engines, and yes, we could have chmod'd the file after the fact, but
     # half dozen and all...
     self.url = url
     self.engine = create_engine(url)
     session = sessionmaker(bind=self.engine)
     self.store = session()
     self.store.commit()
示例#14
0
文件: base.py 项目: omunroe-com/mlman
 def initialize(self, debug=None):
     """See `IDatabase`."""
     # Calculate the engine url.
     url = expand(config.database.url, None, config.paths)
     self._prepare(url)
     log.debug('Database url: %s', url)
     # XXX By design of SQLite, database file creation does not honor
     # umask.  See their ticket #1193:
     # http://www.sqlite.org/cvstrac/tktview?tn=1193,31
     #
     # This sucks for us because the mailman.db file /must/ be group
     # writable, however even though we guarantee our umask is 002 here, it
     # still gets created without the necessary g+w permission, due to
     # SQLite's policy.  This should only affect SQLite engines because its
     # the only one that creates a little file on the local file system.
     # This kludges around their bug by "touch"ing the database file before
     # SQLite has any chance to create it, thus honoring the umask and
     # ensuring the right permissions.  We only try to do this for SQLite
     # engines, and yes, we could have chmod'd the file after the fact, but
     # half dozen and all...
     self.url = url
     self.engine = create_engine(url,
                                 isolation_level='READ UNCOMMITTED',
                                 pool_pre_ping=True)
     session = sessionmaker(bind=self.engine)
     self.store = session()
     self.store.commit()
示例#15
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
示例#16
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)
示例#17
0
文件: mhonarc.py 项目: bksim/mailman
 def list_url(mlist):
     """See `IArchiver`."""
     # XXX What about private MHonArc archives?
     return expand(config.archiver.mhonarc.base_url,
                   dict(listname=mlist.fqdn_listname,
                        hostname=mlist.domain.url_host,
                        fqdn_listname=mlist.fqdn_listname,
                        ))
示例#18
0
 def list_url(self, mlist):
     """See `IArchiver`."""
     # XXX What about private MHonArc archives?
     return expand(self.base_url,
                   dict(listname=mlist.fqdn_listname,
                        hostname=mlist.domain.url_host,
                        fqdn_listname=mlist.fqdn_listname,
                        ))
示例#19
0
 def list_url(self, mlist):
     """See `IArchiver`."""
     # XXX What about private MHonArc archives?
     return expand(self.base_url, mlist, dict(
         # For backward compatibility.
         hostname=mlist.domain.mail_host,
         fqdn_listname=mlist.fqdn_listname,
         ))
示例#20
0
def decorate(mlist, uri, extradict=None):
    """Expand the decoration template from its URI."""
    if uri is None:
        return ""
    # Get the decorator template.
    loader = getUtility(ITemplateLoader)
    template_uri = expand(uri, dict(listname=mlist.fqdn_listname, language=mlist.preferred_language.code))
    template = loader.get(template_uri)
    return decorate_template(mlist, template, extradict)
示例#21
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
示例#22
0
 def initialize():
     """Initialize the global switchboards for input/output."""
     for conf in config.runner_configs:
         name = conf.name.split('.')[-1]
         assert name not in config.switchboards, (
             'Duplicate runner name: {0}'.format(name))
         substitutions = config.paths
         substitutions['name'] = name
         path = expand(conf.path, substitutions)
         config.switchboards[name] = Switchboard(name, path)
示例#23
0
def decorate(mlist, uri, extradict=None):
    """Expand the decoration template from its URI."""
    if uri is None:
        return ''
    # Get the decorator template.
    loader = getUtility(ITemplateLoader)
    template_uri = expand(uri, dict(
        listname=mlist.fqdn_listname,
        language=mlist.preferred_language.code,
        ))
    template = loader.get(template_uri)
    return decorate_template(mlist, template, extradict)
示例#24
0
def handle_ConfigurationUpdatedEvent(event):
    """Initialize the global switchboards for input/output."""
    if not isinstance(event, ConfigurationUpdatedEvent):
        return
    config = event.config
    for conf in config.runner_configs:
        name = conf.name.split('.')[-1]
        assert name not in config.switchboards, (
            'Duplicate runner name: {0}'.format(name))
        substitutions = config.paths
        substitutions['name'] = name
        path = expand(conf.path, substitutions)
        config.switchboards[name] = Switchboard(name, path)
示例#25
0
 def archive_message(self, mlist, msg):
     """See `IArchiver`."""
     substitutions = config.__dict__.copy()
     substitutions["listname"] = mlist.fqdn_listname
     command = expand(self.command, substitutions)
     proc = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True, shell=True)
     stdout, stderr = proc.communicate(msg.as_string())
     if proc.returncode != 0:
         log.error("%s: mhonarc subprocess had non-zero exit code: %s" % (msg["message-id"], proc.returncode))
     log.info(stdout)
     log.error(stderr)
     # Can we get more information, such as the url to the message just
     # archived, out of MHonArc?
     return None
示例#26
0
文件: mhonarc.py 项目: bksim/mailman
 def archive_message(mlist, msg):
     """See `IArchiver`."""
     substitutions = config.__dict__.copy()
     substitutions['listname'] = mlist.fqdn_listname
     command = expand(config.archiver.mhonarc.command, substitutions)
     proc = subprocess.Popen(
         command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
         shell=True)
     stdout, stderr = proc.communicate(msg.as_string())
     if proc.returncode != 0:
         log.error('%s: mhonarc subprocess had non-zero exit code: %s' %
                   (msg['message-id'], proc.returncode))
     log.info(stdout)
     log.error(stderr)
示例#27
0
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 = _get_message(mlist.welcome_message_uri, mlist, language)
    options_url = member.options_url
    # Try to find a non-empty display name.  We first look at the directly
    # subscribed record, which will either be the address or the user.  That's
    # handled automatically by going through member.subscriber.  If that
    # doesn't give us something useful, try whatever user is linked to the
    # subscriber.
    if member.subscriber.display_name:
        display_name = member.subscriber.display_name
    # If an unlinked address is subscribed tehre will be no .user.
    elif member.user is not None and member.user.display_name:
        display_name = member.user.display_name
    else:
        display_name = ''
    # Get the text from the template.
    text = expand(
        welcome_message,
        dict(
            fqdn_listname=mlist.fqdn_listname,
            list_name=mlist.display_name,
            listinfo_uri=mlist.script_url('listinfo'),
            list_requests=mlist.request_address,
            user_name=display_name,
            user_address=member.address.email,
            user_options_uri=options_url,
        ))
    digmode = (
        ''  # noqa
        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))
示例#28
0
文件: env.py 项目: adam-iris/mailman
def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL and not an Engine,
    though an Engine is acceptable here as well.  By skipping the Engine
    creation we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the script
    output.
    """
    url = expand(config.database.url, config.paths)
    context.configure(url=url, target_metadata=Model.metadata)
    with context.begin_transaction():
        context.run_migrations()
 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
示例#30
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)
示例#31
0
def handle_ConfigurationUpdatedEvent(event):
    """Initialize the global switchboards for input/output."""
    if not isinstance(event, ConfigurationUpdatedEvent):
        return
    config = event.config
    for conf in config.runner_configs:
        name = conf.name.split(".")[-1]
        assert name not in config.switchboards, "Duplicate runner name: {0}".format(name)
        # Path is empty for non-queue runners.  Check for path and create
        # switchboard instances only for queue runners.
        if conf.path:
            substitutions = config.paths
            substitutions["name"] = name
            path = expand(conf.path, substitutions)
            config.switchboards[name] = Switchboard(name, path)
示例#32
0
文件: env.py 项目: adam-iris/mailman
def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine and associate a
    connection with the context.
    """
    url = expand(config.database.url, config.paths)
    engine = create_engine(url)

    connection = engine.connect()
    with closing(connection):
        context.configure(
            connection=connection, target_metadata=Model.metadata)
        with context.begin_transaction():
            context.run_migrations()
示例#33
0
 def set(self, store, name, context, uri, username=None, password=''):
     """See `ITemplateManager`."""
     # Just record the fact that we have a template set.  Make sure that if
     # there is an existing template with the same context and name, we
     # override any of its settings (and evict the cache).
     template = store.query(Template).filter(
         Template.name == name, Template.context == context).one_or_none()
     if template is None:
         template = Template(name, context, uri, username, password)
         store.add(template)
     else:
         template.reset(uri, username, password)
         # Now, evict the cache for the previous template.
         cache_mgr = getUtility(ICacheManager)
         actual_uri = expand(uri, None)
         cache_mgr.evict(actual_uri)
示例#34
0
 def archive_message(self, mlist, msg):
     """See `IArchiver`."""
     substitutions = config.__dict__.copy()
     substitutions['listname'] = mlist.fqdn_listname
     command = expand(self.command, substitutions)
     proc = subprocess.Popen(command,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             universal_newlines=True,
                             shell=True)
     stdout, stderr = proc.communicate(msg.as_string())
     if proc.returncode != 0:
         log.error('%s: mhonarc subprocess had non-zero exit code: %s' %
                   (msg['message-id'], proc.returncode))
     log.info(stdout)
     log.error(stderr)
示例#35
0
def handle_ConfigurationUpdatedEvent(event):
    """Initialize the global switchboards for input/output."""
    if not isinstance(event, ConfigurationUpdatedEvent):
        return
    config = event.config
    for conf in config.runner_configs:
        name = conf.name.split('.')[-1]
        assert name not in config.switchboards, (
            'Duplicate runner name: {0}'.format(name))
        # Path is empty for non-queue runners.  Check for path and create
        # switchboard instances only for queue runners.
        if conf.path:
            substitutions = config.paths
            substitutions['name'] = name
            path = expand(conf.path, substitutions)
            config.switchboards[name] = Switchboard(name, path)
示例#36
0
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 = _get_message(mlist.welcome_message_uri, mlist, language)
    options_url = member.options_url
    # Try to find a non-empty display name.  We first look at the directly
    # subscribed record, which will either be the address or the user.  That's
    # handled automatically by going through member.subscriber.  If that
    # doesn't give us something useful, try whatever user is linked to the
    # subscriber.
    if member.subscriber.display_name:
        display_name = member.subscriber.display_name
    # If an unlinked address is subscribed tehre will be no .user.
    elif member.user is not None and member.user.display_name:
        display_name = member.user.display_name
    else:
        display_name = ''
    # Get the text from the template.
    text = expand(welcome_message, dict(
        fqdn_listname=mlist.fqdn_listname,
        list_name=mlist.display_name,
        listinfo_uri=mlist.script_url('listinfo'),
        list_requests=mlist.request_address,
        user_name=display_name,
        user_address=member.address.email,
        user_options_uri=options_url,
        ))
    digmode = (''                                   # noqa
               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))
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 = expand(
        getUtility(ITemplateLoader).get('list:admin:notice:subscribe', mlist),
        mlist, dict(member=formataddr((display_name, address)), ))
    msg = OwnerNotification(mlist, subject, text, roster=mlist.administrators)
    msg.send(mlist)
示例#38
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)
示例#39
0
 def archive_message(self, mlist, msg):
     """See `IArchiver`."""
     substitutions = config.__dict__.copy()
     substitutions['listname'] = mlist.fqdn_listname
     command = expand(self.command, mlist, substitutions)
     proc = Popen(
         command,
         stdin=PIPE, stdout=PIPE, stderr=PIPE,
         universal_newlines=True, shell=True)
     stdout, stderr = proc.communicate(msg.as_string())
     if proc.returncode != 0:
         log.error('%s: mhonarc subprocess had non-zero exit code: %s' %
                   (msg['message-id'], proc.returncode))
     log.info(stdout)
     log.error(stderr)
     # Can we get more information, such as the url to the message just
     # archived, out of MHonArc?
     return None
示例#40
0
def make(template_file,
         mlist=None,
         language=None,
         wrap=True,
         _trace=False,
         **kw):
    """Locate and 'make' a template file.

    The template file is located as with `find()`, and the resulting text is
    optionally wrapped and interpolated with the keyword argument dictionary.

    :param template_file: The name of the template file to search for.
    :type template_file: string
    :param mlist: Optional mailing list used as the context for
        searching for the template file.  The list's preferred language will
        influence the search, as will the list's data directory.
    :type mlist: `IMailingList`
    :param language: Optional language code, which influences the search.
    :type language: string
    :param wrap: When True, wrap the text.
    :type wrap: bool
    :param _trace: Passed through to ``find()``, this enables printing of
        debugging information during template search.
    :type _trace: bool
    :param **kw: Keyword arguments for template interpolation.
    :return: The interpolated text.
    :rtype: string
    :raises TemplateNotFoundError: when the template could not be found.
    """
    path, fp = find(template_file, mlist, language, _trace)
    try:
        # XXX Removing the trailing newline is a hack carried over from
        # Mailman 2.  The (stripped) template text is then passed through the
        # translation catalog.  This ensures that the translated text is
        # unicode, and also allows for volunteers to translate the templates
        # into the language catalogs.
        template = _(fp.read()[:-1])
    finally:
        fp.close()
    assert isinstance(template, str), 'Translated template is not a string'
    text = expand(template, kw)
    if wrap:
        return wrap_text(text)
    return text
示例#41
0
 def test_substitutions(self):
     test_text = ('UNIT TESTING %(real_name)s mailing list\n'
                  '%(real_name)s@%(host_name)s\n'
                  '%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s')
     expected_text = ('UNIT TESTING $display_name mailing list\n'
                      '$fqdn_listname\n'
                      '$listinfo_uri')
     for oldvar, newvar in self._conf_mapping.items():
         self._pckdict[str(oldvar)] = str(test_text)
         import_config_pck(self._mlist, self._pckdict)
         newattr = getattr(self._mlist, newvar)
         template_uri = expand(newattr, dict(
             listname=self._mlist.fqdn_listname,
             language=self._mlist.preferred_language.code,
             ))
         loader = getUtility(ITemplateLoader)
         text = loader.get(template_uri)
         self.assertEqual(text, expected_text,
                 'Old variables were not converted for %s' % newvar)
def send_admin_disable_notice(mlist, address, display_name):
    """Send the list administrators a membership disabled by-bounce notice.

    :param mlist: The mailing list
    :type mlist: IMailingList
    :param address: The address of the member
    :type address: string
    :param display_name: The name of the subscriber
    :type display_name: string
    """
    member = formataddr((display_name, address))
    data = {'member': member}
    with _.using(mlist.preferred_language.code):
        subject = _('$member\'s subscription disabled on $mlist.display_name')
    text = expand(
        getUtility(ITemplateLoader).get('list:admin:notice:disable', mlist),
        mlist, data)
    msg = OwnerNotification(mlist, subject, text, roster=mlist.administrators)
    msg.send(mlist)
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))
def send_admin_removal_notice(mlist, address, display_name):
    """Send the list administrators a membership removed due to bounce notice.

    :param mlist: The mailing list.
    :type mlist: IMailingList
    :param address: The address of the member
    :type address: string
    :param display_name: The name of the subscriber
    :type display_name: string
    """
    member = formataddr((display_name, address))
    data = {'member': member, 'mlist': mlist.display_name}
    with _.using(mlist.preferred_language.code):
        subject = _('$member unsubscribed from ${mlist.display_name} '
                    'mailing list due to bounces')
    text = expand(
        getUtility(ITemplateLoader).get('list:admin:notice:removal', mlist),
        mlist, data)
    msg = OwnerNotification(mlist, subject, text, roster=mlist.administrators)
    msg.send(mlist)
示例#45
0
def decorate_template(mlist, template, extradict=None):
    """Expand the decoration template."""
    # Create a dictionary which includes the default set of interpolation
    # variables allowed in headers and footers.  These will be augmented by
    # any key/value pairs in the extradict.
    substitutions = dict(
        fqdn_listname = mlist.fqdn_listname,
        list_name     = mlist.list_name,
        host_name     = mlist.mail_host,
        display_name  = mlist.display_name,
        listinfo_uri  = mlist.script_url('listinfo'),
        list_requests = mlist.request_address,
        description   = mlist.description,
        info          = mlist.info,
        )
    if extradict is not None:
        substitutions.update(extradict)
    text = expand(template, substitutions)
    # Turn any \r\n line endings into just \n
    return re.sub(r' *\r?\n', r'\n', text)
示例#46
0
def decorate_template(mlist, template, extradict=None):
    """Expand the decoration template."""
    # Create a dictionary which includes the default set of interpolation
    # variables allowed in headers and footers.  These will be augmented by
    # any key/value pairs in the extradict.
    substitutions = dict(
        fqdn_listname = mlist.fqdn_listname,
        list_name     = mlist.list_name,
        host_name     = mlist.mail_host,
        display_name  = mlist.display_name,
        listinfo_uri  = mlist.script_url('listinfo'),
        list_requests = mlist.request_address,
        description   = mlist.description,
        info          = mlist.info,
        )
    if extradict is not None:
        substitutions.update(extradict)
    text = expand(template, substitutions)
    # Turn any \r\n line endings into just \n
    return re.sub(r' *\r?\n', r'\n', text)
示例#47
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 = expand(
            getUtility(ITemplateLoader).get('list:admin:notice:unsubscribe',
                                            mlist), mlist,
            dict(member=formataddr((display_name, email)), ))
        msg = OwnerNotification(mlist,
                                subject,
                                text,
                                roster=mlist.administrators)
        msg.send(mlist)
def decorate_template(mlist, template, extradict=None):
    """Expand the decoration template."""
    # Create a dictionary which includes the default set of interpolation
    # variables allowed in headers and footers.  These will be augmented by
    # any key/value pairs in the extradict.
    substitutions = {
        key: getattr(mlist, key)
        for key in ('fqdn_listname',
                    'list_name',
                    'mail_host',
                    'display_name',
                    'request_address',
                    'description',
                    'info',
                    )
        }
    if extradict is not None:
        substitutions.update(extradict)
    text = expand(template, mlist, substitutions)
    # Turn any \r\n line endings into just \n
    return re.sub(r' *\r?\n', r'\n', text)
示例#49
0
def make(template_file, mlist=None, language=None, wrap=True,
         _trace=False, **kw):
    """Locate and 'make' a template file.

    The template file is located as with `find()`, and the resulting text is
    optionally wrapped and interpolated with the keyword argument dictionary.

    :param template_file: The name of the template file to search for.
    :type template_file: string
    :param mlist: Optional mailing list used as the context for
        searching for the template file.  The list's preferred language will
        influence the search, as will the list's data directory.
    :type mlist: `IMailingList`
    :param language: Optional language code, which influences the search.
    :type language: string
    :param wrap: When True, wrap the text.
    :type wrap: bool
    :param _trace: Passed through to ``find()``, this enables printing of
        debugging information during template search.
    :type _trace: bool
    :param **kw: Keyword arguments for template interpolation.
    :return: The interpolated text.
    :rtype: string
    :raises TemplateNotFoundError: when the template could not be found.
    """
    path, fp = find(template_file, mlist, language, _trace)
    try:
        # XXX Removing the trailing newline is a hack carried over from
        # Mailman 2.  The (stripped) template text is then passed through the
        # translation catalog.  This ensures that the translated text is
        # unicode, and also allows for volunteers to translate the templates
        # into the language catalogs.
        template = _(fp.read()[:-1])
    finally:
        fp.close()
    assert isinstance(template, str), 'Translated template is not a string'
    text = expand(template, kw)
    if wrap:
        return wrap_text(text)
    return text
示例#50
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  # noqa: F841
     template = getUtility(ITemplateLoader).get('list:user:notice:post',
                                                mlist,
                                                language=language.code)
     text = expand(
         template,
         mlist,
         dict(
             subject=oneline(original_subject, in_unicode=True),
             # For backward compatibility.
             list_name=mlist.list_name,
         ))
     # 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)
示例#51
0
 def test_substitutions(self):
     test_text = ('UNIT TESTING %(real_name)s mailing list\n'
                  '%(real_name)s@%(host_name)s\n'
                  '%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s')
     expected_text = ('UNIT TESTING $display_name mailing list\n'
                      '$fqdn_listname\n'
                      '$listinfo_uri')
     for oldvar, newvar in self._conf_mapping.items():
         self._pckdict[str(oldvar)] = str(test_text)
         import_config_pck(self._mlist, self._pckdict)
         newattr = getattr(self._mlist, newvar)
         template_uri = expand(
             newattr,
             dict(
                 listname=self._mlist.fqdn_listname,
                 language=self._mlist.preferred_language.code,
             ))
         loader = getUtility(ITemplateLoader)
         text = loader.get(template_uri)
         self.assertEqual(
             text, expected_text,
             'Old variables were not converted for %s' % newvar)
示例#52
0
    def __init__(self, name, slice=None):
        """Create a runner.

        :param slice: The slice number for this runner.  This is passed
            directly to the underlying `ISwitchboard` object.  This is ignored
            for runners that don't manage a queue.
        :type slice: int or None
        """
        # Grab the configuration section.
        self.name = name
        section = getattr(config, "runner." + name)
        substitutions = config.paths
        substitutions["name"] = name
        self.queue_directory = expand(section.path, substitutions)
        numslices = int(section.instances)
        self.switchboard = Switchboard(name, self.queue_directory, slice, numslices, True)
        self.sleep_time = as_timedelta(section.sleep_time)
        # sleep_time is a timedelta; turn it into a float for time.sleep().
        self.sleep_float = 86400 * self.sleep_time.days + self.sleep_time.seconds + self.sleep_time.microseconds / 1.0e6
        self.max_restarts = int(section.max_restarts)
        self.start = as_boolean(section.start)
        self._stop = False
示例#53
0
def decorate_template(mlist, template, extradict=None):
    """Expand the decoration template."""
    # Create a dictionary which includes the default set of interpolation
    # variables allowed in headers and footers.  These will be augmented by
    # any key/value pairs in the extradict.
    substitutions = {
        key: getattr(mlist, key)
        for key in ('fqdn_listname',
                    'list_name',
                    'mail_host',
                    'display_name',
                    'request_address',
                    'description',
                    'info',
                    )
        }
    # This must eventually go away.
    substitutions['listinfo_uri'] = mlist.script_url('listinfo')
    if extradict is not None:
        substitutions.update(extradict)
    text = expand(template, substitutions)
    # Turn any \r\n line endings into just \n
    return re.sub(r' *\r?\n', r'\n', text)
示例#54
0
def deliver(mlist, msg, msgdata):
    """Deliver a message to the outgoing mail server."""
    # If there are no recipients, there's nothing to do.
    recipients = msgdata.get('recipients')
    if not recipients:
        # Could be None, could be an empty sequence.
        return
    # Which delivery agent should we use?  Several situations can cause us to
    # use individual delivery.  If not specified, use bulk delivery.  See the
    # to-outgoing handler for when the 'verp' key is set in the metadata.
    if msgdata.get('verp', False):
        agent = Deliver()
    elif mlist.personalize != Personalization.none:
        agent = Deliver()
    else:
        agent = BulkDelivery(int(config.mta.max_recipients))
    log.debug('Using agent: %s', agent)
    # Keep track of the original recipients and the original sender for
    # logging purposes.
    original_recipients = msgdata['recipients']
    original_sender = msgdata.get('original-sender', msg.sender)
    # Let the agent attempt to deliver to the recipients.  Record all failures
    # for re-delivery later.
    t0 = time.time()
    refused = agent.deliver(mlist, msg, msgdata)
    t1 = time.time()
    # Log this posting.
    size = getattr(msg, 'original_size', msgdata.get('original_size'))
    if size is None:
        size = len(msg.as_string())
    substitutions = dict(
        msgid       = msg.get('message-id', 'n/a'),
        listname    = mlist.fqdn_listname,
        sender      = original_sender,
        recip       = len(original_recipients),
        size        = size,
        time        = t1 - t0,
        refused     = len(refused),
        smtpcode    = 'n/a',
        smtpmsg     = 'n/a',
        )
    template = config.logging.smtp.every
    if template.lower() != 'no':
        log.info('%s', expand(template, substitutions))
    if refused:
        template = config.logging.smtp.refused
        if template.lower() != 'no':
            log.info('%s', expand(template, substitutions))
    else:
        # Log the successful post, but if it was not destined to the mailing
        # list (e.g. to the owner or admin), print the actual recipients
        # instead of just the number.
        if not msgdata.get('tolist', False):
            recips = msg.get_all('to', [])
            recips.extend(msg.get_all('cc', []))
            substitutions['recips'] = COMMA.join(recips)
        template = config.logging.smtp.success
        if template.lower() != 'no':
            log.info('%s', expand(template, substitutions))
    # Process any failed deliveries.
    temporary_failures = []
    permanent_failures = []
    for recipient, (code, smtp_message) in refused.items():
        # RFC 5321, $4.5.3.1.10 says:
        #
        #   RFC 821 [1] incorrectly listed the error where an SMTP server
        #   exhausts its implementation limit on the number of RCPT commands
        #   ("too many recipients") as having reply code 552.  The correct
        #   reply code for this condition is 452.  Clients SHOULD treat a 552
        #   code in this case as a temporary, rather than permanent, failure
        #   so the logic below works.
        #
        if code >= 500 and code != 552:
            # A permanent failure
            permanent_failures.append(recipient)
        else:
            # Deal with persistent transient failures by queuing them up for
            # future delivery.  TBD: this could generate lots of log entries!
            temporary_failures.append(recipient)
        template = config.logging.smtp.failure
        if template.lower() != 'no':
            substitutions.update(
                recip       = recipient,
                smtpcode    = code,
                smtpmsg     = smtp_message,
                )
            log.info('%s', expand(template, substitutions))
    # Return the results
    if temporary_failures or permanent_failures:
        raise SomeRecipientsFailed(temporary_failures, permanent_failures)
示例#55
0
 def confirm_address(self, cookie):
     """See `IMailingList`."""
     local_part = expand(config.mta.verp_confirm_format, dict(
         address = '{0}-confirm'.format(self.list_name),
         cookie  = cookie))
     return '{0}@{1}'.format(local_part, self.mail_host)
示例#56
0
 def setUp(cls):
     # Set up the basic configuration stuff.  Turn off path creation until
     # we've pushed the testing config.
     config.create_paths = False
     initialize.initialize_1(INHIBIT_CONFIG_FILE)
     assert cls.var_dir is None, 'Layer already set up'
     # Calculate a temporary VAR_DIR directory so that run-time artifacts
     # of the tests won't tread on the installation's data.  This also
     # makes it easier to clean up after the tests are done, and insures
     # isolation of test suite runs.
     cls.var_dir = tempfile.mkdtemp()
     # We need a test configuration both for the foreground process and any
     # child processes that get spawned.  lazr.config would allow us to do
     # it all in a string that gets pushed, and we'll do that for the
     # foreground, but because we may be spawning processes (such as
     # runners) we'll need a file that we can specify to the with the -C
     # option.  Craft the full test configuration string here, push it, and
     # also write it out to a temp file for -C.
     #
     # Create a dummy postfix.cfg file so that the test suite doesn't try
     # to run the actual postmap command, which may not exist anyway.
     postfix_cfg = os.path.join(cls.var_dir, 'postfix.cfg')
     with open(postfix_cfg, 'w') as fp:
         print(dedent("""
         [postfix]
         postmap_command: true
         """), file=fp)
     test_config = dedent("""
     [mailman]
     layout: testing
     [paths.testing]
     var_dir: {0}
     [devmode]
     testing: yes
     [mta]
     configuration: {1}
     """.format(cls.var_dir, postfix_cfg))
     # Read the testing config and push it.
     more = resource_bytes('mailman.testing', 'testing.cfg')
     test_config += more.decode('utf-8')
     config.create_paths = True
     config.push('test config', test_config)
     # Initialize everything else.
     initialize.initialize_2(testing=True)
     initialize.initialize_3()
     # When stderr debugging is enabled, subprocess root loggers should
     # also be more verbose.
     if cls.stderr:
         test_config += dedent("""
         [logging.root]
         level: debug
         """)
     # Enable log message propagation and reset the log paths so that the
     # doctests can check the output.
     for logger_config in config.logger_configs:
         sub_name = logger_config.name.split('.')[-1]
         if sub_name == 'root':
             continue
         logger_name = 'mailman.' + sub_name
         log = logging.getLogger(logger_name)
         log.propagate = cls.stderr
         # Reopen the file to a new path that tests can get at.  Instead of
         # using the configuration file path though, use a path that's
         # specific to the logger so that tests can find expected output
         # more easily.
         path = os.path.join(config.LOG_DIR, sub_name)
         get_handler(sub_name).reopen(path)
         log.setLevel(logging.DEBUG)
         # If stderr debugging is enabled, make sure subprocesses are also
         # more verbose.
         if cls.stderr:
             test_config += expand(dedent("""
             [logging.$name]
             propagate: yes
             level: debug
             """), dict(name=sub_name, path=path))
     # The root logger will already have a handler, but it's not the right
     # handler.  Remove that and set our own.
     if cls.stderr:
         console = logging.StreamHandler(sys.stderr)
         formatter = logging.Formatter(config.logging.root.format,
                                       config.logging.root.datefmt)
         console.setFormatter(formatter)
         root = logging.getLogger()
         del root.handlers[:]
         root.addHandler(console)
     # Write the configuration file for subprocesses and set up the config
     # object to pass that properly on the -C option.
     config_file = os.path.join(cls.var_dir, 'test.cfg')
     with open(config_file, 'w') as fp:
         fp.write(test_config)
         print(file=fp)
     config.filename = config_file
示例#57
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)
示例#58
0
 def setUp(cls):
     # Set up the basic configuration stuff.  Turn off path creation until
     # we've pushed the testing config.
     config.create_paths = False
     initialize.initialize_1(INHIBIT_CONFIG_FILE)
     assert cls.var_dir is None, "Layer already set up"
     # Calculate a temporary VAR_DIR directory so that run-time artifacts
     # of the tests won't tread on the installation's data.  This also
     # makes it easier to clean up after the tests are done, and insures
     # isolation of test suite runs.
     cls.var_dir = tempfile.mkdtemp()
     # We need a test configuration both for the foreground process and any
     # child processes that get spawned.  lazr.config would allow us to do
     # it all in a string that gets pushed, and we'll do that for the
     # foreground, but because we may be spawning processes (such as
     # runners) we'll need a file that we can specify to the with the -C
     # option.  Craft the full test configuration string here, push it, and
     # also write it out to a temp file for -C.
     test_config = dedent(
         """
     [mailman]
     layout: testing
     [passwords]
     password_scheme: cleartext
     [paths.testing]
     var_dir: %s
     [devmode]
     testing: yes
     """
         % cls.var_dir
     )
     # Read the testing config and push it.
     test_config += resource_string("mailman.testing", "testing.cfg")
     config.create_paths = True
     config.push("test config", test_config)
     # Initialize everything else.
     initialize.initialize_2()
     initialize.initialize_3()
     # When stderr debugging is enabled, subprocess root loggers should
     # also be more verbose.
     if cls.stderr:
         test_config += dedent(
             """
         [logging.root]
         propagate: yes
         level: debug
         """
         )
     # Enable log message propagation and reset the log paths so that the
     # doctests can check the output.
     for logger_config in config.logger_configs:
         sub_name = logger_config.name.split(".")[-1]
         if sub_name == "root":
             continue
         logger_name = "mailman." + sub_name
         log = logging.getLogger(logger_name)
         log.propagate = True
         # Reopen the file to a new path that tests can get at.  Instead of
         # using the configuration file path though, use a path that's
         # specific to the logger so that tests can find expected output
         # more easily.
         path = os.path.join(config.LOG_DIR, sub_name)
         get_handler(sub_name).reopen(path)
         log.setLevel(logging.DEBUG)
         # If stderr debugging is enabled, make sure subprocesses are also
         # more verbose.
         if cls.stderr:
             test_config += expand(
                 dedent(
                     """
             [logging.$name]
             propagate: yes
             level: debug
             """
                 ),
                 dict(name=sub_name, path=path),
             )
     # zope.testing sets up logging before we get to our own initialization
     # function.  This messes with the root logger, so explicitly set it to
     # go to stderr.
     if cls.stderr:
         console = logging.StreamHandler(sys.stderr)
         formatter = logging.Formatter(config.logging.root.format, config.logging.root.datefmt)
         console.setFormatter(formatter)
         logging.getLogger().addHandler(console)
     # Write the configuration file for subprocesses and set up the config
     # object to pass that properly on the -C option.
     config_file = os.path.join(cls.var_dir, "test.cfg")
     with open(config_file, "w") as fp:
         fp.write(test_config)
         print(file=fp)
     config.filename = config_file
示例#59
0
__all__ = [
    'run_migrations_offline',
    'run_migrations_online',
    ]


from alembic import context
from contextlib import closing
from mailman.core.initialize import initialize_1
from mailman.config import config
from mailman.database.model import Model
from mailman.utilities.string import expand
from sqlalchemy import create_engine

try:
    url = expand(config.database.url, config.paths)
except AttributeError:
    # Initialize config object for external alembic calls
    initialize_1()
    url = expand(config.database.url, config.paths)


def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL and not an Engine,
    though an Engine is acceptable here as well.  By skipping the Engine
    creation we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the script
    output.