Exemplo n.º 1
0
 def __init__(self):
     """Create a basic deliverer."""
     self._connection = Connection(
         config.mta.smtp_host, int(config.mta.smtp_port),
         int(config.mta.max_sessions_per_connection),
         config.mta.smtp_user if config.mta.smtp_user else None,
         config.mta.smtp_pass if config.mta.smtp_pass else None,
         as_SecureMode(config.mta.smtp_secure_mode),
         as_boolean(config.mta.smtp_verify_cert),
         as_boolean(config.mta.smtp_verify_hostname),
         )
Exemplo n.º 2
0
def do_interactive(ctx, banner):
    global m, r
    overrides = dict(
        m=m,
        commit=config.db.commit,
        abort=config.db.abort,
        config=config,
        getUtility=getUtility
        )
    # Bootstrap some useful names into the namespace, mostly to make
    # the component architecture and interfaces easily available.
    for module_name in sys.modules:
        if not module_name.startswith('mailman.interfaces.'):
            continue
        module = sys.modules[module_name]
        for name in module.__all__:
            overrides[name] = getattr(module, name)
    banner = config.shell.banner + '\n' + (
        banner if isinstance(banner, str) else '')
    try:
        use_ipython = as_boolean(config.shell.use_ipython)
    except ValueError:
        if config.shell.use_ipython == 'debug':
            use_ipython = True
            debug = True
        else:
            print(_('Invalid value for [shell]use_python: {}').format(
                config.shell.use_ipython), file=sys.stderr)
            return
    else:
        debug = False
    if use_ipython:
        start_ipython(overrides, banner, debug)
    else:
        start_python(overrides, banner)
Exemplo n.º 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))
Exemplo n.º 4
0
def etag(resource):
    """Calculate the etag and return a JSON representation.

    The input is a dictionary representing the resource.  This
    dictionary must not contain an `http_etag` key.  This function
    calculates the etag by using the sha1 hexdigest of the
    pretty-printed (and thus key-sorted and predictable) representation
    of the dictionary.  It then inserts this value under the `http_etag`
    key, and returns the JSON representation of the modified dictionary.

    :param resource: The original resource representation.
    :type resource: dictionary
    :return: JSON representation of the modified dictionary.
    :rtype string
    """
    assert 'http_etag' not in resource, 'Resource already etagged'
    # Calculate the tag from a predictable (i.e. sorted) representation of the
    # dictionary.  The actual details aren't so important.  pformat() is
    # guaranteed to sort the keys, however it returns a str and the hash
    # library requires a bytes.  Use the safest possible encoding.
    hashfood = pformat(resource).encode('raw-unicode-escape')
    etag = hashlib.sha1(hashfood).hexdigest()
    resource['http_etag'] = '"{}"'.format(etag)
    return json.dumps(resource,
                      cls=ExtendedEncoder,
                      sort_keys=as_boolean(config.devmode.enabled))
Exemplo n.º 5
0
def info(output, verbose):
    """See `ICLISubCommand`."""
    print(MAILMAN_VERSION_FULL, file=output)
    print('Python', sys.version, file=output)
    print('config file:', config.filename, file=output)
    print('db url:', config.db.url, file=output)
    print('devmode:',
          'ENABLED' if as_boolean(config.devmode.enabled) else 'DISABLED',
          file=output)
    api = (API30 if config.webservice.api_version == '3.0' else API31)
    print('REST root url:', api.path_to('/'), file=output)
    print('REST credentials: {}:{}'.format(config.webservice.admin_user,
                                           config.webservice.admin_pass),
          file=output)
    if verbose:
        print('File system paths:', file=output)
        longest = 0
        paths = {}
        for attribute in dir(config):
            if attribute.endswith('_DIR') or attribute.endswith('_FILE'):
                paths[attribute] = getattr(config, attribute)
            longest = max(longest, len(attribute))
        for attribute in sorted(paths):
            print('    {0:{2}} = {1}'.format(attribute, paths[attribute],
                                             longest))
Exemplo n.º 6
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
Exemplo n.º 7
0
 def process(self, args):
     """See `ICLISubCommand`."""
     if args.output is None:
         output = sys.stdout
     else:
         # We don't need to close output because that will happen
         # automatically when the script exits.
         output = open(args.output, 'w')
     print(MAILMAN_VERSION_FULL, file=output)
     print('Python', sys.version, file=output)
     print('config file:', config.filename, file=output)
     print('db url:', config.db.url, file=output)
     print('devmode:',
           'ENABLED' if as_boolean(config.devmode.enabled) else 'DISABLED',
           file=output)
     print('REST root url:',
           path_to('/', config.webservice.api_version),
           file=output)
     print('REST credentials: {0}:{1}'.format(config.webservice.admin_user,
                                              config.webservice.admin_pass),
           file=output)
     if args.verbose:
         print('File system paths:', file=output)
         longest = 0
         paths = {}
         for attribute in dir(config):
             if attribute.endswith('_DIR') or attribute.endswith('_FILE'):
                 paths[attribute] = getattr(config, attribute)
             longest = max(longest, len(attribute))
         for attribute in sorted(paths):
             print('    {0:{2}} = {1}'.format(attribute, paths[attribute],
                                              longest))
Exemplo n.º 8
0
def etag(resource):
    """Calculate the etag and return a JSON representation.

    The input is a dictionary representing the resource.  This
    dictionary must not contain an `http_etag` key.  This function
    calculates the etag by using the sha1 hexdigest of the
    pretty-printed (and thus key-sorted and predictable) representation
    of the dictionary.  It then inserts this value under the `http_etag`
    key, and returns the JSON representation of the modified dictionary.

    :param resource: The original resource representation.
    :type resource: dictionary
    :return: JSON representation of the modified dictionary.
    :rtype string
    """
    assert 'http_etag' not in resource, 'Resource already etagged'
    # Calculate the tag from a predictable (i.e. sorted) representation of the
    # dictionary.  The actual details aren't so important.  pformat() is
    # guaranteed to sort the keys, however it returns a str and the hash
    # library requires a bytes.  Use the safest possible encoding.
    hashfood = pformat(resource).encode('raw-unicode-escape')
    etag = hashlib.sha1(hashfood).hexdigest()
    resource['http_etag'] = '"{}"'.format(etag)
    return json.dumps(resource, cls=ExtendedEncoder,
                      sort_keys=as_boolean(config.devmode.enabled))
Exemplo n.º 9
0
 def sendmail(self, envsender, recipients, msgtext):
     """Mimic `smtplib.SMTP.sendmail`."""
     if as_boolean(config.devmode.enabled):
         # Force the recipients to the specified address, but still deliver
         # to the same number of recipients.
         recipients = [config.devmode.recipient] * len(recipients)
     if self._connection is None:
         self._connect()
     # smtplib.SMTP.sendmail requires the message string to be pure ascii.
     # We have seen malformed messages with non-ascii unicodes, so ensure
     # we have pure ascii.
     msgtext = msgtext.encode('ascii', 'replace').decode('ascii')
     try:
         log.debug('envsender: %s, recipients: %s, size(msgtext): %s',
                   envsender, recipients, len(msgtext))
         results = self._connection.sendmail(envsender, recipients, msgtext)
     except smtplib.SMTPException:
         # For safety, close this connection.  The next send attempt will
         # automatically re-open it.  Pass the exception on up.
         self.quit()
         raise
     # This session has been successfully completed.
     self._session_count -= 1
     # By testing exactly for equality to 0, we automatically handle the
     # case for SMTP_MAX_SESSIONS_PER_CONNECTION <= 0 meaning never close
     # the connection.  We won't worry about wraparound <wink>.
     if self._session_count == 0:
         self.quit()
     return results
Exemplo n.º 10
0
def dispose(mlist, msg, msgdata, why):
    if mlist.filter_action is FilterAction.reject:
        # Bounce the message to the original author.
        raise RejectMessage(why)
    elif mlist.filter_action is FilterAction.forward:
        # Forward it on to the list moderators.
        text = _("""\
The attached message matched the $mlist.display_name mailing list's content
filtering rules and was prevented from being forwarded on to the list
membership.  You are receiving the only remaining copy of the discarded
message.

""")
        subject = _('Content filter message notification')
        notice = OwnerNotification(mlist, subject, roster=mlist.moderators)
        notice.set_type('multipart/mixed')
        notice.attach(MIMEText(text))
        notice.attach(MIMEMessage(msg))
        notice.send(mlist)
        # Let this fall through so the original message gets discarded.
    elif mlist.filter_action is FilterAction.preserve:
        if as_boolean(config.mailman.filtered_messages_are_preservable):
            # This is just like discarding the message except that a copy is
            # placed in the 'bad' queue should the site administrator want to
            # inspect the message.
            filebase = config.switchboards['bad'].enqueue(msg, msgdata)
            log.info('{} preserved in file base {}'.format(
                msg.get('message-id', 'n/a'), filebase))
    else:
        log.error('{} invalid FilterAction: {}.  Treating as discard'.format(
            mlist.fqdn_listname, mlist.filter_action.name))
    # Most cases also discard the message
    raise DiscardMessage(why)
Exemplo n.º 11
0
 def put(self, mlist, attribute, value):
     # attribute will contain the (bytes) name of the archiver that is
     # getting a new status.  value will be the representation of the new
     # boolean status.
     archiver = self._archiver_set.get(attribute)
     assert archiver is not None, attribute
     archiver.is_enabled = as_boolean(value)
Exemplo n.º 12
0
 def archivers(self):
     """Iterate over all the enabled archivers."""
     for section in self._config.getByCategory("archiver", []):
         if not as_boolean(section.enable):
             continue
         class_path = section["class"]
         yield call_name(class_path)
Exemplo n.º 13
0
 def put(self, mlist, attribute, value):
     # attribute will contain the (bytes) name of the archiver that is
     # getting a new status.  value will be the representation of the new
     # boolean status.
     archiver = self._archiver_set.get(attribute)
     assert archiver is not None, attribute
     archiver.is_enabled = as_boolean(value)
Exemplo n.º 14
0
 def sendmail(self, envsender, recipients, msgtext):
     """Mimic `smtplib.SMTP.sendmail`."""
     if as_boolean(config.devmode.enabled):
         # Force the recipients to the specified address, but still deliver
         # to the same number of recipients.
         recipients = [config.devmode.recipient] * len(recipients)
     if self._connection is None:
         self._connect()
     try:
         log.debug('envsender: %s, recipients: %s, size(msgtext): %s',
                   envsender, recipients, len(msgtext))
         results = self._connection.sendmail(envsender, recipients, msgtext)
     except smtplib.SMTPException:
         # For safety, close this connection.  The next send attempt will
         # automatically re-open it.  Pass the exception on up.
         self.quit()
         raise
     # This session has been successfully completed.
     self._session_count -= 1
     # By testing exactly for equality to 0, we automatically handle the
     # case for SMTP_MAX_SESSIONS_PER_CONNECTION <= 0 meaning never close
     # the connection.  We won't worry about wraparound <wink>.
     if self._session_count == 0:
         self.quit()
     return results
Exemplo n.º 15
0
def is_testing():
    """Return a 'testing' flag for use with the predictable factories.

    :return: True when in testing mode.
    :rtype: bool
    """
    return MockAndMonkeyLayer.testing_mode or as_boolean(config.devmode.testing)
Exemplo n.º 16
0
 def initialize(self, debug=None):
     """See `IDatabase`."""
     # Calculate the engine url.
     url = expand(config.database.url, config.paths)
     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._prepare(url)
     database = create_database(url)
     store = Store(database, GenerationalCache())
     database.DEBUG = (as_boolean(config.database.debug)
                       if debug is None else debug)
     self.store = store
     store.commit()
Exemplo n.º 17
0
def dispose(mlist, msg, msgdata, why):
    if mlist.filter_action is FilterAction.reject:
        # Bounce the message to the original author.
        raise errors.RejectMessage(why)
    elif mlist.filter_action is FilterAction.forward:
        # Forward it on to the list moderators.
        text=_("""\
The attached message matched the $mlist.display_name mailing list's content
filtering rules and was prevented from being forwarded on to the list
membership.  You are receiving the only remaining copy of the discarded
message.

""")
        subject=_('Content filter message notification')
        notice = OwnerNotification(mlist, subject, roster=mlist.moderators)
        notice.set_type('multipart/mixed')
        notice.attach(MIMEText(text))
        notice.attach(MIMEMessage(msg))
        notice.send(mlist)
        # Let this fall through so the original message gets discarded.
    elif mlist.filter_action is FilterAction.preserve:
        if as_boolean(config.mailman.filtered_messages_are_preservable):
            # This is just like discarding the message except that a copy is
            # placed in the 'bad' queue should the site administrator want to
            # inspect the message.
            filebase = config.switchboards['bad'].enqueue(msg, msgdata)
            log.info('{0} preserved in file base {1}'.format(
                msg.get('message-id', 'n/a'), filebase))
    else:
        log.error(
            '{1} invalid FilterAction: {0}.  Treating as discard'.format(
                mlist.fqdn_listname, mlist.filter_action.name))
    # Most cases also discard the message
    raise errors.DiscardMessage(why)
Exemplo n.º 18
0
 def process(self, args):
     """See `ICLISubCommand`."""
     if args.output is None:
         output = sys.stdout
     else:
         # We don't need to close output because that will happen
         # automatically when the script exits.
         output = open(args.output, 'w')
     print(MAILMAN_VERSION_FULL, file=output)
     print('Python', sys.version, file=output)
     print('config file:', config.filename, file=output)
     print('db url:', config.db.url, file=output)
     print('devmode:',
           'ENABLED' if as_boolean(config.devmode.enabled) else 'DISABLED',
           file=output)
     api = (API30 if config.webservice.api_version == '3.0' else API31)
     print('REST root url:', api.path_to('/'), file=output)
     print('REST credentials: {}:{}'.format(
         config.webservice.admin_user, config.webservice.admin_pass),
         file=output)
     if args.verbose:
         print('File system paths:', file=output)
         longest = 0
         paths = {}
         for attribute in dir(config):
             if attribute.endswith('_DIR') or attribute.endswith('_FILE'):
                 paths[attribute] = getattr(config, attribute)
             longest = max(longest, len(attribute))
         for attribute in sorted(paths):
             print('    {0:{2}} = {1}'.format(
                 attribute, paths[attribute], longest))
Exemplo n.º 19
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
Exemplo n.º 20
0
 def put(self, mlist, attribute, value):
     # attribute will contain the (bytes) name of the archiver that is
     # getting a new status.  value will be the representation of the new
     # boolean status.
     archiver = self._archiver_set.get(attribute)
     if archiver is None:
         raise ValueError('No such archiver: {}'.format(attribute))
     archiver.is_enabled = as_boolean(value)
Exemplo n.º 21
0
def is_testing():
    """Return a 'testing' flag for use with the predictable factories.

    :return: True when in testing mode.
    :rtype: bool
    """
    return (MockAndMonkeyLayer.testing_mode or
            as_boolean(config.devmode.testing))
Exemplo n.º 22
0
 def put(self, mlist, attribute, value):
     # attribute will contain the (bytes) name of the archiver that is
     # getting a new status.  value will be the representation of the new
     # boolean status.
     archiver = self._archiver_set.get(attribute.decode('utf-8'))
     if archiver is None:
         raise ValueError('No such archiver: {}'.format(attribute))
     archiver.is_enabled = as_boolean(value)
Exemplo n.º 23
0
 def path_to(cls, resource):
     """See `IAPI`."""
     return '{}://{}:{}/{}/{}'.format(
         ('https' if as_boolean(config.webservice.use_https) else 'http'),
         config.webservice.hostname,
         config.webservice.port,
         cls.version,
         (resource[1:] if resource.startswith('/') else resource),
         )
Exemplo n.º 24
0
 def archivers(self):
     """Iterate over all the archivers."""
     for section in self._config.getByCategory('archiver', []):
         class_path = section['class'].strip()
         if len(class_path) == 0:
             continue
         archiver = call_name(class_path)
         archiver.is_enabled = as_boolean(section.enable)
         yield archiver
Exemplo n.º 25
0
 def archivers(self):
     """Iterate over all the archivers."""
     for section in self._config.getByCategory('archiver', []):
         class_path = section['class'].strip()
         if len(class_path) == 0:
             continue
         archiver = call_name(class_path)
         archiver.is_enabled = as_boolean(section.enable)
         yield archiver
Exemplo n.º 26
0
 def path_to(cls, resource):
     """See `IAPI`."""
     return '{}://{}:{}/{}/{}'.format(
         ('https' if as_boolean(config.webservice.use_https) else 'http'),
         config.webservice.hostname,
         config.webservice.port,
         cls.version,
         (resource[1:] if resource.startswith('/') else resource),
         )
Exemplo n.º 27
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))
Exemplo n.º 28
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))
Exemplo n.º 29
0
 def _resource_as_dict(self, plugin_config):
     """See `CollectionMixin`."""
     name, plugin_section = plugin_config
     resource = {
         'name': name,
         'class': plugin_section['class'],
         'enabled': as_boolean(plugin_section['enabled']),
     }
     # Add the path to the plugin's own configuration file, if one was
     # given.
     plugin_config = plugin_section['configuration'].strip()
     if len(plugin_config) > 0:
         resource['configuration'] = plugin_config
     return resource
Exemplo n.º 30
0
 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
Exemplo n.º 31
0
def path_to(resource):
    """Return the url path to a resource.

    :param resource: The canonical path to the resource, relative to the
        system base URI.
    :type resource: string
    :return: The full path to the resource.
    :rtype: bytes
    """
    return b'{0}://{1}:{2}/{3}/{4}'.format(
        ('https' if as_boolean(config.webservice.use_https) else 'http'),
        config.webservice.hostname,
        config.webservice.port,
        config.webservice.api_version,
        (resource[1:] if resource.startswith('/') else resource),
        )
Exemplo n.º 32
0
def path_to(resource):
    """Return the url path to a resource.

    :param resource: The canonical path to the resource, relative to the
        system base URI.
    :type resource: string
    :return: The full path to the resource.
    :rtype: bytes
    """
    return '{0}://{1}:{2}/{3}/{4}'.format(
        ('https' if as_boolean(config.webservice.use_https) else 'http'),
        config.webservice.hostname,
        config.webservice.port,
        config.webservice.api_version,
        (resource[1:] if resource.startswith('/') else resource),
    )
Exemplo n.º 33
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))
Exemplo n.º 34
0
def initialize(propagate=None):
    """Initialize all logs.

    :param propagate: Flag specifying whether logs should propagate their
        messages to the root logger.  If omitted, propagation is determined
        from the configuration files.
    :type propagate: bool or None
    """
    # First, find the root logger and configure the logging subsystem.
    # Initialize the root logger, then create a formatter for all the
    # sublogs.  The root logger should log to stderr.
    logging.basicConfig(format=config.logging.root.format,
                        datefmt=config.logging.root.datefmt,
                        level=as_log_level(config.logging.root.level),
                        stream=sys.stderr)
    # Create the sub-loggers.  Note that we'll redirect flufl.lock to
    # mailman.locks.
    for logger_config in config.logger_configs:
        sub_name = logger_config.name.split('.')[-1]
        if sub_name == 'root':
            continue
        if sub_name == 'locks':
            log = logging.getLogger('flufl.lock')
        else:
            logger_name = 'mailman.' + sub_name
            log = logging.getLogger(logger_name)
        # Get settings from log configuration file (or defaults).
        log_format = logger_config.format
        log_datefmt = logger_config.datefmt
        # Propagation to the root logger is how we handle logging to stderr
        # when the runners are not run as a subprocess of 'bin/mailman start'.
        log.propagate = (as_boolean(logger_config.propagate)
                         if propagate is None else propagate)
        # Set the logger's level.
        log.setLevel(as_log_level(logger_config.level))
        # Create a formatter for this logger, then a handler, and link the
        # formatter to the handler.
        formatter = logging.Formatter(fmt=log_format, datefmt=log_datefmt)
        path_str = logger_config.path
        path_abs = os.path.normpath(os.path.join(config.LOG_DIR, path_str))
        handler = ReopenableFileHandler(sub_name, path_abs)
        _handlers[sub_name] = handler
        handler.setFormatter(formatter)
        log.addHandler(handler)
Exemplo n.º 35
0
def _init_logger(propagate, sub_name, log, logger_config):
    # Get settings from log configuration file (or defaults).
    log_format = logger_config.format
    log_datefmt = logger_config.datefmt
    # Propagation to the root logger is how we handle logging to stderr
    # when the runners are not run as a subprocess of 'bin/mailman start'.
    log.propagate = (as_boolean(logger_config.propagate)
                     if propagate is None else propagate)
    # Set the logger's level.
    log.setLevel(as_log_level(logger_config.level))
    # Create a formatter for this logger, then a handler, and link the
    # formatter to the handler.
    formatter = logging.Formatter(fmt=log_format, datefmt=log_datefmt)
    path_str = logger_config.path
    path_abs = os.path.normpath(os.path.join(config.LOG_DIR, path_str))
    handler = ReopenableFileHandler(sub_name, path_abs)
    _handlers[sub_name] = handler
    handler.setFormatter(formatter)
    log.addHandler(handler)
Exemplo n.º 36
0
def _init_logger(propagate, sub_name, log, logger_config):
    # Get settings from log configuration file (or defaults).
    log_format = logger_config.format
    log_datefmt = logger_config.datefmt
    # Propagation to the root logger is how we handle logging to stderr when
    # the runners are not run as a subprocess of 'mailman start'.
    log.propagate = (as_boolean(logger_config.propagate)
                     if propagate is None else propagate)
    # Set the logger's level.
    log.setLevel(as_log_level(logger_config.level))
    # Create a formatter for this logger, then a handler, and link the
    # formatter to the handler.
    formatter = logging.Formatter(fmt=log_format, datefmt=log_datefmt)
    path_str = logger_config.path
    path_abs = os.path.normpath(os.path.join(config.LOG_DIR, path_str))
    handler = ReopenableFileHandler(sub_name, path_abs)
    _handlers[sub_name] = handler
    handler.setFormatter(formatter)
    log.addHandler(handler)
Exemplo n.º 37
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
    """
    goodbye_message = _get_message(mlist.goodbye_message_uri, mlist, language)
    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))
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))
Exemplo n.º 39
0
def initialize():
    """Initialize all enabled plugins."""
    for name, plugin_config in config.plugin_configs:
        class_path = plugin_config['class'].strip()
        if not as_boolean(plugin_config['enabled']) or len(class_path) == 0:
            log.info(
                'Plugin not enabled, or empty class path: {}'.format(name))
            continue
        if name in config.plugins:
            log.error('Duplicate plugin name: {}'.format(name))
            continue
        plugin = call_name(class_path)
        try:
            verifyObject(IPlugin, plugin)
        except DoesNotImplement:
            log.error('Plugin class does not implement IPlugin: {}'.format(
                class_path))
            continue
        plugin.name = name
        config.plugins[name] = plugin
Exemplo n.º 40
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
    """
    goodbye_message = _get_message(mlist.goodbye_message_uri,
                                   mlist, language)
    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))
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))
Exemplo n.º 42
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
Exemplo n.º 43
0
    def start_runners(self, runner_names=None):
        """Start all the configured runners.

        :param runners: If given, a sequence of runner names to start.  If not
            given, this sequence is taken from the configuration file.
        :type runners: a sequence of strings
        """
        if not runner_names:
            runner_names = []
            for runner_config in config.runner_configs:
                # Strip off the 'runner.' prefix.
                assert runner_config.name.startswith('runner.'), (
                    'Unexpected runner configuration section name: {}'.format(
                        runner_config.name))
                runner_names.append(runner_config.name[7:])
        # For each runner we want to start, find their config section, which
        # will tell us the name of the class to instantiate, along with the
        # number of hash space slices to manage.
        for name in runner_names:
            section_name = 'runner.' + name
            # Let AttributeError propagate.
            runner_config = getattr(config, section_name)
            if not as_boolean(runner_config.start):
                continue
            # Find out how many runners to instantiate.  This must be a power
            # of 2.
            count = int(runner_config.instances)
            assert (count & (count - 1)) == 0, (
                'Runner "{0}", not a power of 2: {1}'.format(name, count))
            for slice_number in range(count):
                # runner name, slice #, # of slices, restart count
                info = (name, slice_number, count, 0)
                spec = '{0}:{1:d}:{2:d}'.format(name, slice_number, count)
                pid = self._start_runner(spec)
                log = logging.getLogger('mailman.runner')
                log.debug('[{0:d}] {1}'.format(pid, spec))
                self._kids.add(pid, info)
Exemplo n.º 44
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))
Exemplo n.º 45
0
    def start_runners(self, runner_names=None):
        """Start all the configured runners.

        :param runners: If given, a sequence of runner names to start.  If not
            given, this sequence is taken from the configuration file.
        :type runners: a sequence of strings
        """
        if not runner_names:
            runner_names = []
            for runner_config in config.runner_configs:
                # Strip off the 'runner.' prefix.
                assert runner_config.name.startswith('runner.'), (
                    'Unexpected runner configuration section name: {0}'.format(
                    runner_config.name))
                runner_names.append(runner_config.name[7:])
        # For each runner we want to start, find their config section, which
        # will tell us the name of the class to instantiate, along with the
        # number of hash space slices to manage.
        for name in runner_names:
            section_name = 'runner.' + name
            # Let AttributeError propagate.
            runner_config = getattr(config, section_name)
            if not as_boolean(runner_config.start):
                continue
            # Find out how many runners to instantiate.  This must be a power
            # of 2.
            count = int(runner_config.instances)
            assert (count & (count - 1)) == 0, (
                'Runner "{0}", not a power of 2: {1}'.format(name, count))
            for slice_number in range(count):
                # runner name, slice #, # of slices, restart count
                info = (name, slice_number, count, 0)
                spec = '{0}:{1:d}:{2:d}'.format(name, slice_number, count)
                pid = self._start_runner(spec)
                log = logging.getLogger('mailman.runner')
                log.debug('[{0:d}] {1}'.format(pid, spec))
                self._kids.add(pid, info)
Exemplo n.º 46
0
    def process_event(self, store, event):
        """See `IBounceProcessor`."""
        list_manager = getUtility(IListManager)
        mlist = list_manager.get(event.list_id)
        if mlist is None:
            # List was removed before the bounce is processed.
            event.processed = True
            # This needs an explicit commit because of the raise.
            config.db.commit()
            raise InvalidBounceEvent(
                'Bounce for non-existent list {}'.format(event.list_id))
        member = mlist.members.get_member(event.email)
        if member is None:
            event.processed = True
            # This needs an explicit commit because of the raise.
            config.db.commit()
            raise InvalidBounceEvent(
                'Email {} is not a subcriber of {}'.format(
                    event.email, mlist.list_id))

        # If this is a probe bounce, that we are sent before to check for this
        # Mailbox, we just disable the delivery for this member.
        if event.context == BounceContext.probe:
            log.info('Probe bounce received for member %s on list %s.',
                     event.email, mlist.list_id)
            event.processed = True
            self._disable_delivery(mlist, member, event)
            return

        # Looks like this is a regular bounce event, we need to process it
        # in the follow order:
        # 0. If the member is already disabled by bounce, we ignore this
        #    event.
        # 1. If the date of the bounce is same as the previous bounce, we
        #    ignore this event.
        # 2. Check if the bounce info is valid, bump the bounce_score and
        #    update the last bounce info.
        # 3. If the bounce info is stale, reset the bounce score with
        #    this new value.
        # 4. If the bounce_score is greater than threshold after the above,
        #    a) Send a VERP probe, if configured to do so
        #    b) Disable membership otherwise and notify the user and
        #       warnings.
        if member.preferences.delivery_status == DeliveryStatus.by_bounces:
            log.info('Residual bounce received for member %s on list %s.',
                     event.email, mlist.list_id)
            event.processed = True
            return
        if (member.last_bounce_received is not None and
                member.last_bounce_received.date() == event.timestamp.date()):
            # Update the timestamp of the last bounce received.
            member.last_bounce_received = event.timestamp
            event.processed = True
            log.info('Member %s already scored a bounce on list %s today.',
                     event.email, mlist.list_id)
            return

        if member.last_bounce_received is None or (
                member.last_bounce_received <
                event.timestamp - mlist.bounce_info_stale_after):
            # Reset the bounce score to 1, for the current bounce that we got.
            member.bounce_score = 1
        else:
            # Update the bounce score to reflect this bounce.
            member.bounce_score += 1
        # Update the last received time for the bounce.
        member.last_bounce_received = event.timestamp
        log.info('Member %s on list %s, bounce score = %d.', event.email,
                 mlist.list_id, member.bounce_score)
        # Now, we are done updating. Let's see if the threshold is reached and
        # disable based on that.
        if member.bounce_score >= mlist.bounce_score_threshold:
            # Save bounce_score because sending probe resets it.
            saved_bounce_score = member.bounce_score
            if as_boolean(config.mta.verp_probes):
                send_probe(member, message_id=event.message_id)
                action = 'sending probe'
            else:
                self._disable_delivery(mlist, member, event)
                action = 'disabling delivery'
            log.info(
                'Member %s on list %s, bounce score %d >= threshold %d, %s.',
                event.email, mlist.list_id, saved_bounce_score,
                mlist.bounce_score_threshold, action)
        event.processed = True
Exemplo n.º 47
0
 def process(self, mlist, msg, msgdata):
     """See `IHandler`."""
     if as_boolean(config.mta.remove_dkim_headers):
         del msg['domainkey-signature']
         del msg['dkim-signature']
         del msg['authentication-results']
Exemplo n.º 48
0
 def process(self, args):
     """See `ICLISubCommand`."""
     global m, r
     banner = DEFAULT_BANNER
     # Detailed help wanted?
     if args.details:
         self._details()
         return
     # Interactive is the default unless --run was given.
     if args.interactive is None:
         interactive = (args.run is None)
     else:
         interactive = args.interactive
     # List name cannot be a regular expression if --run is not given.
     if args.listname and args.listname.startswith('^') and not args.run:
         self.parser.error(_('Regular expression requires --run'))
         return
     # Handle --run.
     list_manager = getUtility(IListManager)
     if args.run:
         # When the module and the callable have the same name, a shorthand
         # without the dot is allowed.
         dotted_name = (args.run
                        if '.' in args.run else '{0}.{0}'.format(args.run))
         if args.listname is None:
             self.parser.error(_('--run requires a mailing list name'))
             return
         elif args.listname.startswith('^'):
             r = {}
             cre = re.compile(args.listname, re.IGNORECASE)
             for mailing_list in list_manager.mailing_lists:
                 if cre.match(mailing_list.fqdn_listname):
                     results = call_name(dotted_name, mailing_list)
                     r[mailing_list.fqdn_listname] = results
         else:
             fqdn_listname = args.listname
             m = list_manager.get(fqdn_listname)
             if m is None:
                 self.parser.error(_('No such list: $fqdn_listname'))
                 return
             r = call_name(dotted_name, m)
     else:
         # Not --run.
         if args.listname is not None:
             fqdn_listname = args.listname
             m = list_manager.get(fqdn_listname)
             if m is None:
                 self.parser.error(_('No such list: $fqdn_listname'))
                 return
             banner = _(
                 "The variable 'm' is the $fqdn_listname mailing list")
     # All other processing is finished; maybe go into interactive mode.
     if interactive:
         overrides = dict(
             m=m,
             commit=config.db.commit,
             abort=config.db.abort,
             config=config,
         )
         banner = config.shell.banner + '\n' + banner
         if as_boolean(config.shell.use_ipython):
             self._start_ipython(overrides, banner)
         else:
             self._start_python(overrides, banner)
Exemplo n.º 49
0
 def _dispose(self, mlist, msg, msgdata):
     # See if we should retry delivery of this message again.
     deliver_after = msgdata.get('deliver_after', datetime.fromtimestamp(0))
     if now() < deliver_after:
         return True
     # Calculate whether we should VERP this message or not.  The results of
     # this set the 'verp' key in the message metadata.
     interval = int(config.mta.verp_delivery_interval)
     if 'verp' in msgdata:
         # Honor existing settings.
         pass
     # If personalization is enabled for this list and we've configured
     # Mailman to always VERP personalized deliveries, then yes we VERP it.
     # Also, if personalization is /not/ enabled, but
     # verp_delivery_interval is set (and we've hit this interval), then
     # again, this message should be VERP'd. Otherwise, no.
     elif mlist.personalize != Personalization.none:
         if as_boolean(config.mta.verp_personalized_deliveries):
             msgdata['verp'] = True
     elif interval == 0:
         # Never VERP.
         msgdata['verp'] = False
     elif interval == 1:
         # VERP every time.
         msgdata['verp'] = True
     else:
         # VERP every 'interval' number of times.
         msgdata['verp'] = (mlist.post_id % interval == 0)
     try:
         debug_log.debug('[outgoing] {0}: {1}'.format(
             self._func, msg.get('message-id', 'n/a')))
         self._func(mlist, msg, msgdata)
         self._logged = False
     except socket.error:
         # There was a problem connecting to the SMTP server.  Log this
         # once, but crank up our sleep time so we don't fill the error
         # log.
         port = int(config.mta.smtp_port)
         if port == 0:
             port = 'smtp'            # Log this just once.
         if not self._logged:
             log.error('Cannot connect to SMTP server %s on port %s',
                       config.mta.smtp_host, port)
             self._logged = True
         return True
     except SomeRecipientsFailed as error:
         processor = getUtility(IBounceProcessor)
         # BAW: msg is the original message that failed delivery, not a
         # bounce message.  This may be confusing if this is what's sent to
         # the user in the probe message.  Maybe we should craft a
         # bounce-like message containing information about the permanent
         # SMTP failure?
         if 'probe_token' in msgdata:
             # This is a failure of our local MTA to deliver to a probe
             # message recipient.  Register the bounce event for permanent
             # failures.  Start by grabbing and confirming (i.e. removing)
             # the pendable record associated with this bounce token,
             # regardless of what address was actually failing.
             if len(error.permanent_failures) > 0:
                 pended = getUtility(IPendings).confirm(
                     msgdata['probe_token'])
                 # It's possible the token has been confirmed out of the
                 # database.  Just ignore that.
                 if pended is not None:
                     # The UUID had to be pended as a unicode.
                     member = getUtility(ISubscriptionService).get_member(
                         UUID(hex=pended['member_id']))
                     processor.register(
                         mlist, member.address.email, msg,
                         BounceContext.probe)
         else:
             # Delivery failed at SMTP time for some or all of the
             # recipients.  Permanent failures are registered as bounces,
             # but temporary failures are retried for later.
             for email in error.permanent_failures:
                 processor.register(mlist, email, msg, BounceContext.normal)
             # Move temporary failures to the qfiles/retry queue which will
             # occasionally move them back here for another shot at
             # delivery.
             if error.temporary_failures:
                 current_time = now()
                 recipients = error.temporary_failures
                 last_recip_count = msgdata.get('last_recip_count', 0)
                 deliver_until = msgdata.get('deliver_until', current_time)
                 if len(recipients) == last_recip_count:
                     # We didn't make any progress.  If we've exceeded the
                     # configured retry period, log this failure and
                     # discard the message.
                     if current_time > deliver_until:
                         smtp_log.error('Discarding message with '
                                        'persistent temporary failures: '
                                        '{0}'.format(msg['message-id']))
                         return False
                 else:
                     # We made some progress, so keep trying to delivery
                     # this message for a while longer.
                     deliver_until = current_time + as_timedelta(
                         config.mta.delivery_retry_period)
                 msgdata['last_recip_count'] = len(recipients)
                 msgdata['deliver_until'] = deliver_until
                 msgdata['recipients'] = recipients
                 self._retryq.enqueue(msg, msgdata)
     # We've successfully completed handling of this message.
     return False
Exemplo n.º 50
0
 def process(self, mlist, msg, msgdata):
     """See `IHandler`."""
     if as_boolean(config.mta.remove_dkim_headers):
         del msg['domainkey-signature']
         del msg['dkim-signature']
         del msg['authentication-results']
Exemplo n.º 51
0
 def process(self, args):
     """See `ICLISubCommand`."""
     global m, r
     banner = DEFAULT_BANNER
     # Detailed help wanted?
     if args.details:
         self._details()
         return
     # Interactive is the default unless --run was given.
     if args.interactive is None:
         interactive = (args.run is None)
     else:
         interactive = args.interactive
     # List name cannot be a regular expression if --run is not given.
     if args.listname and args.listname.startswith('^') and not args.run:
         self.parser.error(_('Regular expression requires --run'))
         return
     # Handle --run.
     list_manager = getUtility(IListManager)
     if args.run:
         # When the module and the callable have the same name, a shorthand
         # without the dot is allowed.
         dotted_name = (args.run
                        if '.' in args.run else '{0}.{0}'.format(args.run))
         if args.listname is None:
             self.parser.error(_('--run requires a mailing list name'))
             return
         elif args.listname.startswith('^'):
             r = {}
             cre = re.compile(args.listname, re.IGNORECASE)
             for mailing_list in list_manager.mailing_lists:
                 if cre.match(mailing_list.fqdn_listname):
                     results = call_name(dotted_name, mailing_list)
                     r[mailing_list.fqdn_listname] = results
         else:
             fqdn_listname = args.listname
             m = list_manager.get(fqdn_listname)
             if m is None:
                 self.parser.error(_('No such list: $fqdn_listname'))
                 return
             r = call_name(dotted_name, m)
     else:
         # Not --run.
         if args.listname is not None:
             fqdn_listname = args.listname
             m = list_manager.get(fqdn_listname)
             if m is None:
                 self.parser.error(_('No such list: $fqdn_listname'))
                 return
             banner = _(
                 "The variable 'm' is the $fqdn_listname mailing list")
     # All other processing is finished; maybe go into interactive mode.
     if interactive:
         overrides = dict(m=m,
                          commit=config.db.commit,
                          abort=config.db.abort,
                          config=config,
                          getUtility=getUtility)
         # Bootstrap some useful names into the namespace, mostly to make
         # the component architecture and interfaces easily available.
         for module_name in sys.modules:
             if not module_name.startswith('mailman.interfaces.'):
                 continue
             module = sys.modules[module_name]
             for name in module.__all__:
                 overrides[name] = getattr(module, name)
         banner = config.shell.banner + '\n' + (banner if isinstance(
             banner, str) else '')
         try:
             use_ipython = as_boolean(config.shell.use_ipython)
         except ValueError:
             if config.shell.use_ipython == 'debug':
                 use_ipython = True
                 debug = True
             else:
                 raise
         else:
             debug = False
         if use_ipython:
             self._start_ipython(overrides, banner, debug)
         else:
             self._start_python(overrides, banner)
Exemplo n.º 52
0
 def _dispose(self, mlist, msg, msgdata):
     # See if we should retry delivery of this message again.
     deliver_after = msgdata.get('deliver_after', datetime.fromtimestamp(0))
     if now() < deliver_after:
         return True
     # Calculate whether we should VERP this message or not.  The results of
     # this set the 'verp' key in the message metadata.
     interval = int(config.mta.verp_delivery_interval)
     if 'verp' in msgdata:
         # Honor existing settings.
         pass
     # If personalization is enabled for this list and we've configured
     # Mailman to always VERP personalized deliveries, then yes we VERP it.
     # Also, if personalization is /not/ enabled, but
     # verp_delivery_interval is set (and we've hit this interval), then
     # again, this message should be VERP'd. Otherwise, no.
     elif mlist.personalize != Personalization.none:
         if as_boolean(config.mta.verp_personalized_deliveries):
             msgdata['verp'] = True
     elif interval == 0:
         # Never VERP.
         msgdata['verp'] = False
     elif interval == 1:
         # VERP every time.
         msgdata['verp'] = True
     else:
         # VERP every 'interval' number of times.
         msgdata['verp'] = (mlist.post_id % interval == 0)
     try:
         debug_log.debug('[outgoing] {}: {}'.format(
             self._func, msg.get('message-id', 'n/a')))
         self._func(mlist, msg, msgdata)
         self._logged = False
     except socket.error:
         # There was a problem connecting to the SMTP server.  Log this
         # once, but crank up our sleep time so we don't fill the error
         # log.
         port = int(config.mta.smtp_port)
         if port == 0:
             port = 'smtp'  # Log this just once.
         if not self._logged:
             log.error('Cannot connect to SMTP server %s on port %s',
                       config.mta.smtp_host, port)
             self._logged = True
         return True
     except SomeRecipientsFailed as error:
         processor = getUtility(IBounceProcessor)
         # BAW: msg is the original message that failed delivery, not a
         # bounce message.  This may be confusing if this is what's sent to
         # the user in the probe message.  Maybe we should craft a
         # bounce-like message containing information about the permanent
         # SMTP failure?
         if 'probe_token' in msgdata:
             # This is a failure of our local MTA to deliver to a probe
             # message recipient.  Register the bounce event for permanent
             # failures.  Start by grabbing and confirming (i.e. removing)
             # the pendable record associated with this bounce token,
             # regardless of what address was actually failing.
             if len(error.permanent_failures) > 0:
                 pended = getUtility(IPendings).confirm(
                     msgdata['probe_token'])
                 # It's possible the token has been confirmed out of the
                 # database.  Just ignore that.
                 if pended is not None:
                     # The UUID had to be pended as a unicode.
                     member = getUtility(ISubscriptionService).get_member(
                         UUID(hex=pended['member_id']))
                     processor.register(mlist, member.address.email, msg,
                                        BounceContext.probe)
         else:
             # Delivery failed at SMTP time for some or all of the
             # recipients.  Permanent failures are registered as bounces,
             # but temporary failures are retried for later.
             for email in error.permanent_failures:
                 processor.register(mlist, email, msg, BounceContext.normal)
             # Move temporary failures to the qfiles/retry queue which will
             # occasionally move them back here for another shot at
             # delivery.
             if error.temporary_failures:
                 current_time = now()
                 recipients = error.temporary_failures
                 last_recip_count = msgdata.get('last_recip_count', 0)
                 deliver_until = msgdata.get('deliver_until', current_time)
                 if len(recipients) == last_recip_count:
                     # We didn't make any progress.  If we've exceeded the
                     # configured retry period, log this failure and
                     # discard the message.
                     if current_time > deliver_until:
                         smtp_log.error('Discarding message with '
                                        'persistent temporary failures: '
                                        '{}'.format(msg['message-id']))
                         return False
                 else:
                     # We made some progress, so keep trying to delivery
                     # this message for a while longer.
                     deliver_until = current_time + as_timedelta(
                         config.mta.delivery_retry_period)
                 msgdata['last_recip_count'] = len(recipients)
                 msgdata['deliver_until'] = deliver_until
                 msgdata['recipients'] = recipients
                 self._retryq.enqueue(msg, msgdata)
     # We've successfully completed handling of this message.
     return False
Exemplo n.º 53
0
 def process(self, args):
     """See `ICLISubCommand`."""
     global m, r
     banner = DEFAULT_BANNER
     # Detailed help wanted?
     if args.details:
         self._details()
         return
     # Interactive is the default unless --run was given.
     if args.interactive is None:
         interactive = (args.run is None)
     else:
         interactive = args.interactive
     # List name cannot be a regular expression if --run is not given.
     if args.listname and args.listname.startswith('^') and not args.run:
         self.parser.error(_('Regular expression requires --run'))
         return
     # Handle --run.
     list_manager = getUtility(IListManager)
     if args.run:
         # When the module and the callable have the same name, a shorthand
         # without the dot is allowed.
         dotted_name = (args.run if '.' in args.run
                        else '{0}.{0}'.format(args.run))
         if args.listname is None:
             self.parser.error(_('--run requires a mailing list name'))
             return
         elif args.listname.startswith('^'):
             r = {}
             cre = re.compile(args.listname, re.IGNORECASE)
             for mailing_list in list_manager.mailing_lists:
                 if cre.match(mailing_list.fqdn_listname):
                     results = call_name(dotted_name, mailing_list)
                     r[mailing_list.fqdn_listname] = results
         else:
             fqdn_listname = args.listname
             m = list_manager.get(fqdn_listname)
             if m is None:
                 self.parser.error(_('No such list: $fqdn_listname'))
                 return
             r = call_name(dotted_name, m)
     else:
         # Not --run.
         if args.listname is not None:
             fqdn_listname = args.listname
             m = list_manager.get(fqdn_listname)
             if m is None:
                 self.parser.error(_('No such list: $fqdn_listname'))
                 return
             banner = _(
                 "The variable 'm' is the $fqdn_listname mailing list")
     # All other processing is finished; maybe go into interactive mode.
     if interactive:
         overrides = dict(
             m=m,
             commit=config.db.commit,
             abort=config.db.abort,
             config=config,
             )
         banner = config.shell.banner + '\n' + banner
         if as_boolean(config.shell.use_ipython):
             self._start_ipython(overrides, banner)
         else:
             self._start_python(overrides, banner)
Exemplo n.º 54
0
 def process(self, args):
     """See `ICLISubCommand`."""
     global m, r
     banner = DEFAULT_BANNER
     # Detailed help wanted?
     if args.details:
         self._details()
         return
     # Interactive is the default unless --run was given.
     if args.interactive is None:
         interactive = (args.run is None)
     else:
         interactive = args.interactive
     # List name cannot be a regular expression if --run is not given.
     if args.listname and args.listname.startswith('^') and not args.run:
         self.parser.error(_('Regular expression requires --run'))
         return
     # Handle --run.
     list_manager = getUtility(IListManager)
     if args.run:
         # When the module and the callable have the same name, a shorthand
         # without the dot is allowed.
         dotted_name = (args.run if '.' in args.run
                        else '{0}.{0}'.format(args.run))
         if args.listname is None:
             self.parser.error(_('--run requires a mailing list name'))
             return
         elif args.listname.startswith('^'):
             r = {}
             cre = re.compile(args.listname, re.IGNORECASE)
             for mailing_list in list_manager.mailing_lists:
                 if cre.match(mailing_list.fqdn_listname):
                     results = call_name(dotted_name, mailing_list)
                     r[mailing_list.fqdn_listname] = results
         else:
             fqdn_listname = args.listname
             m = list_manager.get(fqdn_listname)
             if m is None:
                 self.parser.error(_('No such list: $fqdn_listname'))
                 return
             r = call_name(dotted_name, m)
     else:
         # Not --run.
         if args.listname is not None:
             fqdn_listname = args.listname
             m = list_manager.get(fqdn_listname)
             if m is None:
                 self.parser.error(_('No such list: $fqdn_listname'))
                 return
             banner = _(
                 "The variable 'm' is the $fqdn_listname mailing list")
     # All other processing is finished; maybe go into interactive mode.
     if interactive:
         overrides = dict(
             m=m,
             commit=config.db.commit,
             abort=config.db.abort,
             config=config,
             getUtility=getUtility
             )
         # Bootstrap some useful names into the namespace, mostly to make
         # the component architecture and interfaces easily available.
         for module_name in sys.modules:
             if not module_name.startswith('mailman.interfaces.'):
                 continue
             module = sys.modules[module_name]
             for name in module.__all__:
                 overrides[name] = getattr(module, name)
         banner = config.shell.banner + '\n' + (
             banner if isinstance(banner, str) else '')
         try:
             use_ipython = as_boolean(config.shell.use_ipython)
         except ValueError:
             if config.shell.use_ipython == 'debug':
                 use_ipython = True
                 debug = True
             else:
                 raise
         else:
             debug = False
         if use_ipython:
             self._start_ipython(overrides, banner, debug)
         else:
             self._start_python(overrides, banner)