def HoldUnsubscription(self, addr):
     # Assure the database is open for writing
     self.__opendb()
     # Get the next unique id
     id = self.__nextid()
     # All we need to do is save the unsubscribing address
     self.__db[id] = (UNSUBSCRIPTION, addr)
     syslog('vette', '%s: held unsubscription request from %s',
            self.internal_name(), addr)
     # Possibly notify the administrator of the hold
     if self.admin_immed_notify:
         realname = self.real_name
         subject = _(
             'New unsubscription request from %(realname)s by %(addr)s')
         text = Utils.maketext(
             'unsubauth.txt', {
                 'username': addr,
                 'listname': self.internal_name(),
                 'hostname': self.host_name,
                 'admindb_url': self.GetScriptURL('admindb', absolute=1),
             },
             mlist=self)
         # This message should appear to come from the <list>-owner so as
         # to avoid any useless bounce processing.
         owneraddr = self.GetOwnerEmail()
         msg = Message.UserNotification(owneraddr, owneraddr, subject, text,
                                        self.preferred_language)
         msg.send(self, **{'tomoderators': 1})
 def __refuse(self, request, recip, comment, origmsg=None, lang=None):
     # As this message is going to the requestor, try to set the language
     # to his/her language choice, if they are a member.  Otherwise use the
     # list's preferred language.
     realname = self.real_name
     if lang is None:
         lang = self.getMemberLanguage(recip)
     text = Utils.maketext('refuse.txt', {
         'listname': realname,
         'request': request,
         'reason': comment,
         'adminaddr': self.GetOwnerEmail(),
     },
                           lang=lang,
                           mlist=self)
     otrans = i18n.get_translation()
     i18n.set_language(lang)
     try:
         # add in original message, but not wrap/filled
         if origmsg:
             text = NL.join([
                 text,
                 '---------- ' + _('Original Message') + ' ----------',
                 str(origmsg)
             ])
         subject = _('Request to mailing list %(realname)s rejected')
     finally:
         i18n.set_translation(otrans)
     msg = Message.UserNotification(recip, self.GetOwnerEmail(), subject,
                                    text, lang)
     msg.send(self)
Ejemplo n.º 3
0
 def SendUnsubscribeAck(self, addr, lang):
     realname = self.real_name
     msg = Message.UserNotification(
         self.GetMemberAdminEmail(addr), self.GetBouncesEmail(),
         _('You have been unsubscribed from the %(realname)s mailing list'),
         Utils.wrap(self.goodbye_msg), lang)
     msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES)
Ejemplo n.º 4
0
 def __sendAdminBounceNotice(self, member, msg, did=_('disabled')):
     # BAW: This is a bit kludgey, but we're not providing as much
     # information in the new admin bounce notices as we used to (some of
     # it was of dubious value).  However, we'll provide empty, strange, or
     # meaningless strings for the unused %()s fields so that the language
     # translators don't have to provide new templates.
     siteowner = Utils.get_site_email(self.host_name)
     text = Utils.maketext('bounce.txt', {
         'listname': self.real_name,
         'addr': member,
         'negative': '',
         'did': did,
         'but': '',
         'reenable': '',
         'owneraddr': siteowner,
     },
                           mlist=self)
     subject = _('Bounce action notification')
     umsg = Message.UserNotification(self.GetOwnerEmail(),
                                     siteowner,
                                     subject,
                                     lang=self.preferred_language)
     # BAW: Be sure you set the type before trying to attach, or you'll get
     # a MultipartConversionError.
     umsg.set_type('multipart/mixed')
     umsg.attach(
         MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language)))
     if isinstance(msg, StringType):
         umsg.attach(MIMEText(msg))
     else:
         umsg.attach(MIMEMessage(msg))
     umsg.send(self)
Ejemplo n.º 5
0
 def test_user_notification(self):
     eq = self.assertEqual
     unless = self.failUnless
     msg = Message.UserNotification('*****@*****.**', '*****@*****.**',
                                    'Your Test List',
                                    'About your test list')
     msg.send(self._mlist)
     qmsg = email.message_from_string(self._readmsg())
     eq(qmsg['subject'], 'Your Test List')
     eq(qmsg['from'], '*****@*****.**')
     eq(qmsg['to'], '*****@*****.**')
     # The Message-ID: header has some time-variant information
     msgid = qmsg['message-id']
     unless(msgid.startswith('<mailman.'))
     unless(msgid.endswith('*****@*****.**>'))
     # The Sender: header is optional and addresses can be VERPed
     if self._mlist.include_sender_header:
         sender = qmsg['sender']
         unless(sender.startswith('"_xtest" <_xtest-bounces'))
         unless(sender.endswith('@dom.ain>'))
     eto = qmsg['errors-to']
     unless(eto.startswith('_xtest-bounces'))
     unless(eto.endswith('@dom.ain'))
     eq(qmsg['x-beenthere'], '*****@*****.**')
     eq(qmsg['x-mailman-version'], Version.VERSION)
     eq(qmsg['precedence'], 'bulk')
     eq(qmsg['list-id'], '<_xtest.dom.ain>')
     eq(qmsg['x-list-administrivia'], 'yes')
     eq(qmsg.get_payload(), 'About your test list')
Ejemplo n.º 6
0
def process(mlist, msg, msgdata):
    # Extract the sender's address and find them in the user database
    sender = msgdata.get('original_sender', msg.get_sender())
    try:
        ack = mlist.getMemberOption(sender, mm_cfg.AcknowledgePosts)
        if not ack:
            return
    except Errors.NotAMemberError:
        return
    # Okay, they want acknowledgement of their post.  Give them their original
    # subject.  BAW: do we want to use the decoded header?
    origsubj = msgdata.get('origsubj', msg.get('subject', _('(no subject)')))
    # Get the user's preferred language
    lang = msgdata.get('lang', mlist.getMemberLanguage(sender))
    # Now get the acknowledgement template
    realname = mlist.real_name
    text = Utils.maketext(
        'postack.txt', {
            'subject': Utils.oneline(origsubj, Utils.GetCharSet(lang)),
            'listname': realname,
            'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
            'optionsurl': mlist.GetOptionsURL(sender, absolute=1),
        },
        lang=lang,
        mlist=mlist,
        raw=1)
    # Craft the outgoing message, with all headers and attributes
    # necessary for general delivery.  Then enqueue it to the outgoing
    # queue.
    subject = _('%(realname)s post acknowledgement')
    usermsg = Message.UserNotification(sender, mlist.GetBouncesEmail(),
                                       subject, text, lang)
    usermsg.send(mlist)
Ejemplo n.º 7
0
    def process_message(self, peer, mailfrom, rcpttos, data):
        from cStringIO import StringIO
        from Mailman import Utils
        from Mailman import Message
        from Mailman import MailList
        listnames = []
        for rcpt in rcpttos:
            local = rcpt.lower().split('@')[0]
            parts = local.split('-')
            if len(parts) > 2:
                continue
            listname = parts[0]
            if len(parts) == 2:
                command = parts[1]
            else:
                command = ''
            if not Utils.list_exists(listname) or command not in (
                    '', 'admin', 'owner', 'request', 'join', 'leave'):
                continue
            listnames.append((rcpt, listname, command))

        for rcpt, listname, command in listnames:
            rcpttos.remove(rcpt)

        print >> DEBUGSTREAM, 'forwarding recips:', ' '.join(rcpttos)
        if rcpttos:
            refused = self._deliver(mailfrom, rcpttos, data)
            print >> DEBUGSTREAM, 'we got refusals:', refused
        mlists = {}
        s = StringIO(data)
        msg = Message.Message(s)
        if not msg.getheader('from'):
            msg['From'] = mailfrom
        if not msg.getheader('date'):
            msg['Date'] = time.ctime(time.time())
        for rcpt, listname, command in listnames:
            print >> DEBUGSTREAM, 'sending message to', rcpt
            mlist = mlists.get(listname)
            if not mlist:
                mlist = MailList.MailList(listname, lock=0)
                mlists[listname] = mlist
            if command == '':
                msg.Enqueue(mlist, tolist=1)
            elif command == 'admin':
                msg.Enqueue(mlist, toadmin=1)
            elif command == 'owner':
                msg.Enqueue(mlist, toowner=1)
            elif command == 'request':
                msg.Enqueue(mlist, torequest=1)
            elif command in ('join', 'leave'):
                if command == 'join':
                    msg['Subject'] = 'subscribe'
                else:
                    msg['Subject'] = 'unsubscribe'
                msg.Enqueue(mlist, torequest=1)
Ejemplo n.º 8
0
    def SendHostileSubscriptionNotice(self, listname, address):
        # Some one was invited to one list but tried to confirm to a different
        # list.  We inform both list owners of the bogosity, but be careful
        # not to reveal too much information.
        selfname = self.internal_name()
        syslog('mischief', '%s was invited to %s but confirmed to %s', address,
               listname, selfname)
        # First send a notice to the attacked list
        msg = Message.OwnerNotification(
            self, _('Hostile subscription attempt detected'),
            Utils.wrap(
                _("""%(address)s was invited to a different mailing
list, but in a deliberate malicious attempt they tried to confirm the
invitation to your list.  We just thought you'd like to know.  No further
action by you is required.""")))
        msg.send(self)
        # Now send a notice to the invitee list
        try:
            # Avoid import loops
            from Mailman.MailList import MailList
            mlist = MailList(listname, lock=False)
        except Errors.MMListError:
            # Oh well
            return
        otrans = i18n.get_translation()
        i18n.set_language(mlist.preferred_language)
        try:
            msg = Message.OwnerNotification(
                mlist, _('Hostile subscription attempt detected'),
                Utils.wrap(
                    _("""You invited %(address)s to your list, but in a
deliberate malicious attempt, they tried to confirm the invitation to a
different list.  We just thought you'd like to know.  No further action by you
is required.""")))
            msg.send(mlist)
        finally:
            i18n.set_translation(otrans)
Ejemplo n.º 9
0
 def MailUserPassword(self, user):
     listfullname = '%s@%s' % (self.real_name, self.host_name)
     requestaddr = self.GetRequestEmail()
     # find the lowercased version of the user's address
     adminaddr = self.GetBouncesEmail()
     assert self.isMember(user)
     if not self.getMemberPassword(user):
         # The user's password somehow got corrupted.  Generate a new one
         # for him, after logging this bogosity.
         syslog('error', 'User %s had a false password for list %s', user,
                self.internal_name())
         waslocked = self.Locked()
         if not waslocked:
             self.Lock()
         try:
             self.setMemberPassword(user, Utils.MakeRandomPassword())
             self.Save()
         finally:
             if not waslocked:
                 self.Unlock()
     # Now send the user his password
     cpuser = self.getMemberCPAddress(user)
     recipient = self.GetMemberAdminEmail(cpuser)
     subject = _('%(listfullname)s mailing list reminder')
     # Get user's language and charset
     lang = self.getMemberLanguage(user)
     cset = Utils.GetCharSet(lang)
     password = self.getMemberPassword(user)
     # TK: Make unprintables to ?
     # The list owner should allow users to set language options if they
     # want to use non-us-ascii characters in password and send it back.
     password = unicode(password, cset, 'replace').encode(cset, 'replace')
     # get the text from the template
     text = Utils.maketext(
         'userpass.txt', {
             'user': cpuser,
             'listname': self.real_name,
             'fqdn_lname': self.GetListEmail(),
             'password': password,
             'options_url': self.GetOptionsURL(user, absolute=True),
             'requestaddr': requestaddr,
             'owneraddr': self.GetOwnerEmail(),
         },
         lang=lang,
         mlist=self)
     msg = Message.UserNotification(recipient, adminaddr, subject, text,
                                    lang)
     msg['X-No-Archive'] = 'yes'
     msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES)
Ejemplo n.º 10
0
def create(mlist, cgi=False, nolock=False, quiet=False):
    if mlist is None:
        return
    listname = mlist.internal_name()
    fieldsz = len(listname) + len('-unsubscribe')
    if cgi:
        # If a list is being created via the CGI, the best we can do is send
        # an email message to mailman-owner requesting that the proper aliases
        # be installed.
        sfp = StringIO()
        if not quiet:
            print(_("""
The mailing list `%(listname)s' has been created via the through-the-web
interface.  In order to complete the activation of this mailing list, the
proper /etc/aliases (or equivalent) file must be updated.  The program
`newaliases' may also have to be run.

Here are the entries for the /etc/aliases file:
"""),
                  file=sfp)
        outfp = sfp
    else:
        if not quiet:
            print(
                C_("""\
To finish creating your mailing list, you must edit your /etc/aliases (or
equivalent) file by adding the following lines, and possibly running the
`newaliases' program:
"""))
        print(C_("""\
## %(listname)s mailing list"""))
        outfp = sys.stdout
    # Common path
    for k, v in makealiases(listname):
        print(k + ':', ((fieldsz - len(k)) * ' '), v, file=outfp)
    # If we're using the command line interface, we're done.  For ttw, we need
    # to actually send the message to mailman-owner now.
    if not cgi:
        print(file=outfp)
        return
    # Send the message to the site -owner so someone can do something about
    # this request.
    siteowner = Utils.get_site_email(extra='owner')
    # Should this be sent in the site list's preferred language?
    msg = Message.UserNotification(
        siteowner, siteowner,
        _('Mailing list creation request for list %(listname)s'),
        sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE)
    msg.send(mlist)
Ejemplo n.º 11
0
 def ForwardMessage(self, msg, text=None, subject=None, tomoderators=True):
     # Wrap the message as an attachment
     if text is None:
         text = _('No reason given')
     if subject is None:
         text = _('(no subject)')
     text = MIMEText(Utils.wrap(text),
                     _charset=Utils.GetCharSet(self.preferred_language))
     attachment = MIMEMessage(msg)
     notice = Message.OwnerNotification(
         self, subject, tomoderators=tomoderators)
     # Make it look like the message is going to the -owner address
     notice.set_type('multipart/mixed')
     notice.attach(text)
     notice.attach(attachment)
     notice.send(self)
Ejemplo n.º 12
0
    def notify_owner (self):
        """Send an email to the owner of the list of successful creation."""

        siteowner = Utils.get_site_email(self.ml.host_name, 'owner')
        text = Utils.maketext(
            'newlist.txt',
            {'listname'    : self.ln,
             'password'    : '',
             'admin_url'   : self.ml.GetScriptURL('admin', absolute=1),
             'listinfo_url': self.ml.GetScriptURL('listinfo', absolute=1),
             'requestaddr' : self.ml.GetRequestEmail(),
             'siteowner'   : siteowner,
             }, mlist=self.ml)
        msg = Message.UserNotification(
            self.owner, siteowner, 'Your new mailing list: %s' % self.ln,
            text, self.ml.preferred_language)
        msg.send(self.ml)
Ejemplo n.º 13
0
def remove(mlist, cgi=False):
    listname = mlist.internal_name()
    fieldsz = len(listname) + len('-unsubscribe')
    if cgi:
        # If a list is being removed via the CGI, the best we can do is send
        # an email message to mailman-owner requesting that the appropriate
        # aliases be deleted.
        sfp = StringIO()
        print(_("""\
The mailing list `%(listname)s' has been removed via the through-the-web
interface.  In order to complete the de-activation of this mailing list, the
appropriate /etc/aliases (or equivalent) file must be updated.  The program
`newaliases' may also have to be run.

Here are the entries in the /etc/aliases file that should be removed:
"""),
              file=sfp)
        outfp = sfp
    else:
        print(
            C_("""
To finish removing your mailing list, you must edit your /etc/aliases (or
equivalent) file by removing the following lines, and possibly running the
`newaliases' program:

## %(listname)s mailing list"""))
        outfp = sys.stdout
    # Common path
    for k, v in makealiases(listname):
        print(k + ':', ((fieldsz - len(k)) * ' '), v, file=outfp)
    # If we're using the command line interface, we're done.  For ttw, we need
    # to actually send the message to mailman-owner now.
    if not cgi:
        print(file=outfp)
        return
    siteowner = Utils.get_site_email(extra='owner')
    # Should this be sent in the site list's preferred language?
    msg = Message.UserNotification(
        siteowner, siteowner,
        _('Mailing list removal request for list %(listname)s'),
        sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE)
    msg['Date'] = email.utils.formatdate(localtime=1)
    outq = get_switchboard(mm_cfg.OUTQUEUE_DIR)
    outq.enqueue(msg, recips=[siteowner], nodecorate=1)
Ejemplo n.º 14
0
 def HoldSubscription(self, addr, fullname, password, digest, lang):
     # Assure that the database is open for writing
     self.__opendb()
     # Get the next unique id
     id = self.__nextid()
     # Save the information to the request database. for held subscription
     # entries, each record in the database will be one of the following
     # format:
     #
     # the time the subscription request was received
     # the subscriber's address
     # the subscriber's selected password (TBD: is this safe???)
     # the digest flag
     # the user's preferred language
     data = time.time(), addr, fullname, password, digest, lang
     self.__db[id] = (SUBSCRIPTION, data)
     #
     # TBD: this really shouldn't go here but I'm not sure where else is
     # appropriate.
     syslog('vette', '%s: held subscription request from %s',
            self.internal_name(), addr)
     # Possibly notify the administrator in default list language
     if self.admin_immed_notify:
         ## cpanel patch: is the str() handling still needed?
         i18n.set_language(self.preferred_language)
         realname = str(self.real_name)
         subject = _(
             'New subscription request to list %(realname)s from %(addr)s')
         text = Utils.maketext(
             'subauth.txt', {
                 'username': addr,
                 'listname': self.real_name,
                 'hostname': self.host_name,
                 'admindb_url': self.GetScriptURL('admindb', absolute=1),
             },
             mlist=self)
         # This message should appear to come from the <list>-owner so as
         # to avoid any useless bounce processing.
         owneraddr = self.GetOwnerEmail()
         msg = Message.UserNotification(owneraddr, owneraddr, subject, text,
                                        self.preferred_language)
         msg.send(self, **{'tomoderators': 1})
         # Restore the user's preferred language.
         i18n.set_language(lang)
Ejemplo n.º 15
0
    def SendSubscribeAck(self, name, password, digest, text=''):
        pluser = self.getMemberLanguage(name)
        # Need to set this here to get the proper l10n of the Subject:
        i18n.set_language(pluser)
        if self.welcome_msg:
            welcome = Utils.wrap(self.welcome_msg) + '\n'
        else:
            welcome = ''
        if self.umbrella_list:
            addr = self.GetMemberAdminEmail(name)
            umbrella = Utils.wrap(
                _('''\
Note: Since this is a list of mailing lists, administrative
notices like the password reminder will be sent to
your membership administrative address, %(addr)s.'''))
        else:
            umbrella = ''
        # get the text from the template
        text += Utils.maketext(
            'subscribeack.txt', {
                'real_name': self.real_name,
                'host_name': self.host_name,
                'welcome': welcome,
                'umbrella': umbrella,
                'emailaddr': self.GetListEmail(),
                'listinfo_url': self.GetScriptURL('listinfo', absolute=True),
                'optionsurl': self.GetOptionsURL(name, absolute=True),
                'password': password,
                'user': self.getMemberCPAddress(name),
            },
            lang=pluser,
            mlist=self)
        if digest:
            digmode = _(' (Digest mode)')
        else:
            digmode = ''
        realname = self.real_name
        msg = Message.UserNotification(
            self.GetMemberAdminEmail(name), self.GetRequestEmail(),
            _('Welcome to the "%(realname)s" mailing list%(digmode)s'), text,
            pluser)
        msg['X-No-Archive'] = 'yes'
        msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES)
Ejemplo n.º 16
0
 def sendProbe(self, member, msg):
     listname = self.real_name
     # Put together the substitution dictionary.
     d = {
         'listname': listname,
         'address': member,
         'optionsurl': self.GetOptionsURL(member, absolute=True),
         'owneraddr': self.GetOwnerEmail(),
     }
     text = Utils.maketext('probe.txt',
                           d,
                           lang=self.getMemberLanguage(member),
                           mlist=self)
     # Calculate the VERP'd sender address for bounce processing of the
     # probe message.
     token = self.pend_new(Pending.PROBE_BOUNCE, member, msg)
     probedict = {
         'bounces': self.internal_name() + '-bounces',
         'token': token,
     }
     probeaddr = '%s@%s' % (
         (mm_cfg.VERP_PROBE_FORMAT % probedict), self.host_name)
     # Calculate the Subject header, in the member's preferred language
     ulang = self.getMemberLanguage(member)
     otrans = i18n.get_translation()
     i18n.set_language(ulang)
     try:
         subject = _('%(listname)s mailing list probe message')
     finally:
         i18n.set_translation(otrans)
     outer = Message.UserNotification(member,
                                      probeaddr,
                                      subject,
                                      lang=ulang)
     outer.set_type('multipart/mixed')
     text = MIMEText(text, _charset=Utils.GetCharSet(ulang))
     outer.attach(text)
     outer.attach(MIMEMessage(msg))
     # Turn off further VERP'ing in the final delivery step.  We set
     # probe_token for the OutgoingRunner to more easily handling local
     # rejects of probe messages.
     outer.send(self, envsender=probeaddr, verp=False, probe_token=token)
Ejemplo n.º 17
0
def do_discard(mlist, msg):
    sender = msg.get_sender()
    # Do we forward auto-discards to the list owners?
    if mlist.forward_auto_discards:
        lang = mlist.preferred_language
        varhelp = '%s/?VARHELP=privacy/sender/discard_these_nonmembers' % \
                  mlist.GetScriptURL('admin', absolute=1)
        nmsg = Message.UserNotification(mlist.GetOwnerEmail(),
                                        mlist.GetBouncesEmail(),
                                        _('Auto-discard notification'),
                                        lang=lang)
        nmsg.set_type('multipart/mixed')
        text = MIMEText(Utils.wrap(
            _('The attached message has been automatically discarded.')),
                        _charset=Utils.GetCharSet(lang))
        nmsg.attach(text)
        nmsg.attach(MIMEMessage(msg))
        nmsg.send(mlist)
    # Discard this sucker
    raise Errors.DiscardMessage
Ejemplo n.º 18
0
 def BounceMessage(self, msg, msgdata, e=None):
     # Bounce a message back to the sender, with an error message if
     # provided in the exception argument.
     sender = msg.get_sender()
     subject = msg.get('subject', _('(no subject)'))
     subject = Utils.oneline(subject,
                             Utils.GetCharSet(self.preferred_language))
     if e is None:
         notice = _('[No bounce details are available]')
     else:
         notice = _(e.notice())
     # Currently we always craft bounces as MIME messages.
     bmsg = Message.UserNotification(msg.get_sender(),
                                     self.GetOwnerEmail(),
                                     subject,
                                     lang=self.preferred_language)
     # BAW: Be sure you set the type before trying to attach, or you'll get
     # a MultipartConversionError.
     bmsg.set_type('multipart/mixed')
     txt = MIMEText(notice,
                    _charset=Utils.GetCharSet(self.preferred_language))
     bmsg.attach(txt)
     bmsg.attach(MIMEMessage(msg))
     bmsg.send(self)
Ejemplo n.º 19
0
    def send_response(self):
        # Helper
        def indent(lines):
            return ['    ' + line for line in lines]

        # Quick exit for some commands which don't need a response
        if not self.respond:
            return
        resp = [
            Utils.wrap(
                _("""\
The results of your email command are provided below.
Attached is your original message.
"""))
        ]
        if self.results:
            resp.append(_('- Results:'))
            resp.extend(indent(self.results))
        # Ignore empty lines
        unprocessed = [
            line for line in self.commands[self.lineno:]
            if line and line.strip()
        ]
        if unprocessed and mm_cfg.RESPONSE_INCLUDE_LEVEL >= 2:
            resp.append(_('\n- Unprocessed:'))
            resp.extend(indent(unprocessed))
        if not unprocessed and not self.results:
            # The user sent an empty message; return a helpful one.
            resp.append(
                Utils.wrap(
                    _("""\
No commands were found in this message.
To obtain instructions, send a message containing just the word "help".
""")))
        if self.ignored and mm_cfg.RESPONSE_INCLUDE_LEVEL >= 2:
            resp.append(_('\n- Ignored:'))
            resp.extend(indent(self.ignored))
        resp.append(_('\n- Done.\n\n'))
        # Encode any unicode strings into the list charset, so we don't try to
        # join unicode strings and invalid ASCII.
        charset = Utils.GetCharSet(self.msgdata['lang'])
        encoded_resp = []
        for item in resp:
            if isinstance(item, UnicodeType):
                item = item.encode(charset, 'replace')
            encoded_resp.append(item)
        results = MIMEText(NL.join(encoded_resp), _charset=charset)
        # Safety valve for mail loops with misconfigured email 'bots.  We
        # don't respond to commands sent with "Precedence: bulk|junk|list"
        # unless they explicitly "X-Ack: yes", but not all mail 'bots are
        # correctly configured, so we max out the number of responses we'll
        # give to an address in a single day.
        #
        # BAW: We wait until now to make this decision since our sender may
        # not be self.msg.get_sender(), but I'm not sure this is right.
        recip = self.returnaddr or self.msg.get_sender()
        if not self.mlist.autorespondToSender(recip, self.msgdata['lang']):
            return
        msg = Message.UserNotification(recip,
                                       self.mlist.GetOwnerEmail(),
                                       _('The results of your email commands'),
                                       lang=self.msgdata['lang'])
        msg.set_type('multipart/mixed')
        msg.attach(results)
        if mm_cfg.RESPONSE_INCLUDE_LEVEL == 1:
            self.msg.set_payload(
                _('Message body suppressed by Mailman site configuration\n'))
        if mm_cfg.RESPONSE_INCLUDE_LEVEL == 0:
            orig = MIMEText(_(
                'Original message suppressed by Mailman site configuration\n'),
                            _charset=charset)
        else:
            orig = MIMEMessage(self.msg)
        msg.attach(orig)
        msg.send(self.mlist)
Ejemplo n.º 20
0
def send_i18n_digests(mlist, mboxfp):
    mbox = Mailbox(mboxfp)
    # Prepare common information (first lang/charset)
    lang = mlist.preferred_language
    lcset = Utils.GetCharSet(lang)
    lcset_out = Charset(lcset).output_charset or lcset
    # Common Information (contd)
    realname = mlist.real_name
    volume = mlist.volume
    issue = mlist.next_digest_number
    digestid = _('%(realname)s Digest, Vol %(volume)d, Issue %(issue)d')
    digestsubj = Header(digestid, lcset, header_name='Subject')
    # Set things up for the MIME digest.  Only headers not added by
    # CookHeaders need be added here.
    # Date/Message-ID should be added here also.
    mimemsg = Message.Message()
    mimemsg['Content-Type'] = 'multipart/mixed'
    mimemsg['MIME-Version'] = '1.0'
    mimemsg['From'] = mlist.GetRequestEmail()
    mimemsg['Subject'] = digestsubj
    mimemsg['To'] = mlist.GetListEmail()
    mimemsg['Reply-To'] = mlist.GetListEmail()
    mimemsg['Date'] = formatdate(localtime=1)
    mimemsg['Message-ID'] = Utils.unique_message_id(mlist)
    # Set things up for the rfc1153 digest
    plainmsg = StringIO()
    rfc1153msg = Message.Message()
    rfc1153msg['From'] = mlist.GetRequestEmail()
    rfc1153msg['Subject'] = digestsubj
    rfc1153msg['To'] = mlist.GetListEmail()
    rfc1153msg['Reply-To'] = mlist.GetListEmail()
    rfc1153msg['Date'] = formatdate(localtime=1)
    rfc1153msg['Message-ID'] = Utils.unique_message_id(mlist)
    separator70 = '-' * 70
    separator30 = '-' * 30
    # 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.
    mastheadtxt = Utils.maketext(
        'masthead.txt', {
            'real_name': mlist.real_name,
            'got_list_email': mlist.GetListEmail(),
            'got_listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
            'got_request_email': mlist.GetRequestEmail(),
            'got_owner_email': mlist.GetOwnerEmail(),
        },
        mlist=mlist)
    # MIME
    masthead = MIMEText(mastheadtxt, _charset=lcset)
    masthead['Content-Description'] = digestid
    mimemsg.attach(masthead)
    # RFC 1153
    print >> plainmsg, mastheadtxt
    print >> plainmsg
    # Now add the optional digest header but only if more than whitespace.
    if re.sub('\s', '', mlist.digest_header):
        headertxt = decorate(mlist, mlist.digest_header, _('digest header'))
        # MIME
        header = MIMEText(headertxt, _charset=lcset)
        header['Content-Description'] = _('Digest Header')
        mimemsg.attach(header)
        # RFC 1153
        print >> plainmsg, headertxt
        print >> plainmsg
    # Now we have to cruise through all the messages accumulated in the
    # mailbox file.  We can't add these messages to the plainmsg and mimemsg
    # yet, because we first have to calculate the table of contents
    # (i.e. grok out all the Subjects).  Store the messages in a list until
    # we're ready for them.
    #
    # Meanwhile prepare things for the table of contents
    toc = StringIO()
    print >> toc, _("Today's Topics:\n")
    # Now cruise through all the messages in the mailbox of digest messages,
    # building the MIME payload and core of the RFC 1153 digest.  We'll also
    # accumulate Subject: headers and authors for the table-of-contents.
    messages = []
    msgcount = 0
    msg = mbox.next()
    while msg is not None:
        if msg == '':
            # It was an unparseable message
            msg = mbox.next()
            continue
        msgcount += 1
        messages.append(msg)
        # Get the Subject header
        msgsubj = msg.get('subject', _('(no subject)'))
        subject = Utils.oneline(msgsubj, lcset)
        # Don't include the redundant subject prefix in the toc
        mo = re.match('(re:? *)?(%s)' % re.escape(mlist.subject_prefix),
                      subject, re.IGNORECASE)
        if mo:
            subject = subject[:mo.start(2)] + subject[mo.end(2):]
        username = ''
        addresses = getaddresses([Utils.oneline(msg.get('from', ''), lcset)])
        # Take only the first author we find
        if isinstance(addresses, ListType) and addresses:
            username = addresses[0][0]
            if not username:
                username = addresses[0][1]
        if username:
            username = '******' % username
        # Put count and Wrap the toc subject line
        wrapped = Utils.wrap('%2d. %s' % (msgcount, subject), 65)
        slines = wrapped.split('\n')
        # See if the user's name can fit on the last line
        if len(slines[-1]) + len(username) > 70:
            slines.append(username)
        else:
            slines[-1] += username
        # Add this subject to the accumulating topics
        first = True
        for line in slines:
            if first:
                print >> toc, ' ', line
                first = False
            else:
                print >> toc, '     ', line.lstrip()
        # We do not want all the headers of the original message to leak
        # through in the digest messages.  For this phase, we'll leave the
        # same set of headers in both digests, i.e. those required in RFC 1153
        # plus a couple of other useful ones.  We also need to reorder the
        # headers according to RFC 1153.  Later, we'll strip out headers for
        # for the specific MIME or plain digests.
        keeper = {}
        all_keepers = {}
        for header in (mm_cfg.MIME_DIGEST_KEEP_HEADERS +
                       mm_cfg.PLAIN_DIGEST_KEEP_HEADERS):
            all_keepers[header] = True
        all_keepers = all_keepers.keys()
        for keep in all_keepers:
            keeper[keep] = msg.get_all(keep, [])
        # Now remove all unkempt headers :)
        for header in msg.keys():
            del msg[header]
        # And add back the kept header in the RFC 1153 designated order
        for keep in all_keepers:
            for field in keeper[keep]:
                msg[keep] = field
        # And a bit of extra stuff
        msg['Message'] = ` msgcount `
        # Get the next message in the digest mailbox
        msg = mbox.next()
    # Now we're finished with all the messages in the digest.  First do some
    # sanity checking and then on to adding the toc.
    if msgcount == 0:
        # Why did we even get here?
        return
    toctext = to_cset_out(toc.getvalue(), lcset)
    # MIME
    tocpart = MIMEText(toctext, _charset=lcset)
    tocpart['Content-Description'] = _(
        "Today's Topics (%(msgcount)d messages)")
    mimemsg.attach(tocpart)
    # RFC 1153
    print >> plainmsg, toctext
    print >> plainmsg
    # For RFC 1153 digests, we now need the standard separator
    print >> plainmsg, separator70
    print >> plainmsg
    # Now go through and add each message
    mimedigest = MIMEBase('multipart', 'digest')
    mimemsg.attach(mimedigest)
    first = True
    for msg in messages:
        # MIME.  Make a copy of the message object since the rfc1153
        # processing scrubs out attachments.
        mimedigest.attach(MIMEMessage(copy.deepcopy(msg)))
        # rfc1153
        if first:
            first = False
        else:
            print >> plainmsg, separator30
            print >> plainmsg
        # Use Mailman.Handlers.Scrubber.process() to get plain text
        try:
            msg = scrubber(mlist, msg)
        except Errors.DiscardMessage:
            print >> plainmsg, _('[Message discarded by content filter]')
            continue
        # Honor the default setting
        for h in mm_cfg.PLAIN_DIGEST_KEEP_HEADERS:
            if msg[h]:
                uh = Utils.wrap('%s: %s' % (h, Utils.oneline(msg[h], lcset)))
                uh = '\n\t'.join(uh.split('\n'))
                print >> plainmsg, uh
        print >> plainmsg
        # If decoded payload is empty, this may be multipart message.
        # -- just stringfy it.
        payload = msg.get_payload(decode=True) \
                  or msg.as_string().split('\n\n',1)[1]
        mcset = msg.get_content_charset('')
        if mcset and mcset <> lcset and mcset <> lcset_out:
            try:
                payload = unicode(payload, mcset,
                                  'replace').encode(lcset, 'replace')
            except (UnicodeError, LookupError):
                # TK: Message has something unknown charset.
                #     _out means charset in 'outer world'.
                payload = unicode(payload, lcset_out,
                                  'replace').encode(lcset, 'replace')
        print >> plainmsg, payload
        if not payload.endswith('\n'):
            print >> plainmsg
    # Now add the footer but only if more than whitespace.
    if re.sub('\s', '', mlist.digest_footer):
        footertxt = decorate(mlist, mlist.digest_footer, _('digest footer'))
        # MIME
        footer = MIMEText(footertxt, _charset=lcset)
        footer['Content-Description'] = _('Digest Footer')
        mimemsg.attach(footer)
        # RFC 1153
        # MAS: There is no real place for the digest_footer in an RFC 1153
        # compliant digest, so add it as an additional message with
        # Subject: Digest Footer
        print >> plainmsg, separator30
        print >> plainmsg
        print >> plainmsg, 'Subject: ' + _('Digest Footer')
        print >> plainmsg
        print >> plainmsg, footertxt
        print >> plainmsg
        print >> plainmsg, separator30
        print >> plainmsg
    # Do the last bit of stuff for each digest type
    signoff = _('End of ') + digestid
    # MIME
    # BAW: This stuff is outside the normal MIME goo, and it's what the old
    # MIME digester did.  No one seemed to complain, probably because you
    # won't see it in an MUA that can't display the raw message.  We've never
    # got complaints before, but if we do, just wax this.  It's primarily
    # included for (marginally useful) backwards compatibility.
    mimemsg.postamble = signoff
    # rfc1153
    print >> plainmsg, signoff
    print >> plainmsg, '*' * len(signoff)
    # Do our final bit of housekeeping, and then send each message to the
    # outgoing queue for delivery.
    mlist.next_digest_number += 1
    virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR)
    # Calculate the recipients lists
    plainrecips = []
    mimerecips = []
    drecips = mlist.getDigestMemberKeys() + mlist.one_last_digest.keys()
    for user in mlist.getMemberCPAddresses(drecips):
        # user might be None if someone who toggled off digest delivery
        # subsequently unsubscribed from the mailing list.  Also, filter out
        # folks who have disabled delivery.
        if user is None or mlist.getDeliveryStatus(user) <> ENABLED:
            continue
        # Otherwise, decide whether they get MIME or RFC 1153 digests
        if mlist.getMemberOption(user, mm_cfg.DisableMime):
            plainrecips.append(user)
        else:
            mimerecips.append(user)
    # Zap this since we're now delivering the last digest to these folks.
    mlist.one_last_digest.clear()
    # MIME
    virginq.enqueue(mimemsg,
                    recips=mimerecips,
                    listname=mlist.internal_name(),
                    isdigest=True)
    # RFC 1153
    rfc1153msg.set_payload(to_cset_out(plainmsg.getvalue(), lcset), lcset)
    virginq.enqueue(rfc1153msg,
                    recips=plainrecips,
                    listname=mlist.internal_name(),
                    isdigest=True)
Ejemplo n.º 21
0
 def sendNextNotification(self, member):
     info = self.getBounceInfo(member)
     if info is None:
         return
     reason = self.getDeliveryStatus(member)
     if info.noticesleft <= 0:
         # BAW: Remove them now, with a notification message
         self.ApprovedDeleteMember(
             member,
             'disabled address',
             admin_notif=self.bounce_notify_owner_on_removal,
             userack=1)
         # Expunge the pending cookie for the user.  We throw away the
         # returned data.
         self.pend_confirm(info.cookie)
         if reason == MemberAdaptor.BYBOUNCE:
             syslog('bounce', '%s: %s deleted after exhausting notices',
                    self.internal_name(), member)
         syslog(
             'subscribe', '%s: %s auto-unsubscribed [reason: %s]',
             self.internal_name(), member, {
                 MemberAdaptor.BYBOUNCE: 'BYBOUNCE',
                 MemberAdaptor.BYUSER: '******',
                 MemberAdaptor.BYADMIN: 'BYADMIN',
                 MemberAdaptor.UNKNOWN: 'UNKNOWN'
             }.get(reason, 'invalid value'))
         return
     # Send the next notification
     confirmurl = '%s/%s' % (self.GetScriptURL('confirm',
                                               absolute=1), info.cookie)
     optionsurl = self.GetOptionsURL(member, absolute=1)
     reqaddr = self.GetRequestEmail()
     lang = self.getMemberLanguage(member)
     txtreason = REASONS.get(reason)
     if txtreason is None:
         txtreason = _('for unknown reasons')
     else:
         txtreason = _(txtreason)
     # Give a little bit more detail on bounce disables
     if reason == MemberAdaptor.BYBOUNCE:
         date = time.strftime('%d-%b-%Y',
                              time.localtime(Utils.midnight(info.date)))
         extra = _(' The last bounce received from you was dated %(date)s')
         txtreason += extra
     text = Utils.maketext('disabled.txt', {
         'listname': self.real_name,
         'noticesleft': info.noticesleft,
         'confirmurl': confirmurl,
         'optionsurl': optionsurl,
         'password': self.getMemberPassword(member),
         'owneraddr': self.GetOwnerEmail(),
         'reason': txtreason,
     },
                           lang=lang,
                           mlist=self)
     msg = Message.UserNotification(member, reqaddr, text=text, lang=lang)
     # BAW: See the comment in MailList.py ChangeMemberAddress() for why we
     # set the Subject this way.
     del msg['subject']
     msg['Subject'] = 'confirm ' + info.cookie
     # Send without Precedence: bulk.  Bug #808821.
     msg.send(self, noprecedence=True)
     info.noticesleft -= 1
     info.lastnotice = time.localtime()[:3]
     # In case the MemberAdaptor stores bounce info externally to
     # the list, we need to tell it to update
     self.setBounceInfo(member, info)
Ejemplo n.º 22
0
    # And send the notice to the list owner.
    if notify:
        siteowner = Utils.get_site_email(mlist.host_name, 'owner')
        text = Utils.maketext(
            'newlist.txt', {
                'listname': listname,
                'password': password,
                'admin_url': mlist.GetScriptURL('admin', absolute=1),
                'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
                'requestaddr': mlist.GetRequestEmail(),
                'siteowner': siteowner,
            },
            mlist=mlist)
        msg = Message.UserNotification(
            owner, siteowner, _('Your new mailing list: %(listname)s'), text,
            mlist.preferred_language)
        msg.send(mlist)

    # Success!
    listinfo_url = mlist.GetScriptURL('listinfo', absolute=1)
    admin_url = mlist.GetScriptURL('admin', absolute=1)
    create_url = Utils.ScriptURL('create')

    title = _('Mailing list creation results')
    doc.SetTitle(title)
    table = Table(border=0, width='100%')
    table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
    table.AddCellInfo(table.GetCurrentRowIndex(),
                      0,
                      bgcolor=mm_cfg.WEB_HEADER_COLOR)
Ejemplo n.º 23
0
def hold_for_approval(mlist, msg, msgdata, exc):
    # BAW: This should really be tied into the email confirmation system so
    # that the message can be approved or denied via email as well as the
    # web.
    #
    # XXX We use the weird type(type) construct below because in Python 2.1,
    # type is a function not a type and so can't be used as the second
    # argument in isinstance().  However, in Python 2.5, exceptions are
    # new-style classes and so are not of ClassType.
    # FIXME pzv
    if isinstance(exc, type) or isinstance(exc, type(type)):
        # Go ahead and instantiate it now.
        exc = exc()
    listname = mlist.real_name
    sender = msgdata.get('sender', msg.get_sender())
    usersubject = msg.get('subject')
    charset = Utils.GetCharSet(mlist.preferred_language)
    if usersubject:
        usersubject = Utils.oneline(usersubject, charset)
    else:
        usersubject = _('(no subject)')
    message_id = msg.get('message-id', 'n/a')
    owneraddr = mlist.GetOwnerEmail()
    adminaddr = mlist.GetBouncesEmail()
    requestaddr = mlist.GetRequestEmail()
    # We need to send both the reason and the rejection notice through the
    # translator again, because of the games we play above
    reason = Utils.wrap(exc.reason_notice())
    if isinstance(exc, NonMemberPost) and mlist.nonmember_rejection_notice:
        msgdata['rejection_notice'] = Utils.wrap(
            mlist.nonmember_rejection_notice.replace('%(listowner)s',
                                                     owneraddr))
    else:
        msgdata['rejection_notice'] = Utils.wrap(exc.rejection_notice(mlist))
    id = mlist.HoldMessage(msg, reason, msgdata)
    # Now we need to craft and send a message to the list admin so they can
    # deal with the held message.
    d = {
        'listname': listname,
        'hostname': mlist.host_name,
        'reason': _(reason),
        'sender': sender,
        'subject': usersubject,
        'admindb_url': mlist.GetScriptURL('admindb', absolute=1),
    }
    # We may want to send a notification to the original sender too
    fromusenet = msgdata.get('fromusenet')
    # Since we're sending two messages, which may potentially be in different
    # languages (the user's preferred and the list's preferred for the admin),
    # we need to play some i18n games here.  Since the current language
    # context ought to be set up for the user, let's craft his message first.
    cookie = mlist.pend_new(Pending.HELD_MESSAGE, id)
    if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \
           mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)):
        # Get a confirmation cookie
        d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm',
                                                        absolute=1), cookie)
        lang = msgdata.get('lang', mlist.getMemberLanguage(sender))
        subject = _('Your message to %(listname)s awaits moderator approval')
        text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist)
        nmsg = Message.UserNotification(sender, owneraddr, subject, text, lang)
        nmsg.send(mlist)
    # Now the message for the list owners.  Be sure to include the list
    # moderators in this message.  This one should appear to come from
    # <list>-owner since we really don't need to do bounce processing on it.
    if mlist.admin_immed_notify:
        # Now let's temporarily set the language context to that which the
        # admin is expecting.
        otranslation = i18n.get_translation()
        i18n.set_language(mlist.preferred_language)
        try:
            lang = mlist.preferred_language
            charset = Utils.GetCharSet(lang)
            # We need to regenerate or re-translate a few values in d
            d['reason'] = _(reason)
            d['subject'] = usersubject
            # craft the admin notification message and deliver it
            subject = _('%(listname)s post from %(sender)s requires approval')
            nmsg = Message.UserNotification(owneraddr,
                                            owneraddr,
                                            subject,
                                            lang=lang)
            nmsg.set_type('multipart/mixed')
            text = MIMEText(Utils.maketext('postauth.txt',
                                           d,
                                           raw=1,
                                           mlist=mlist),
                            _charset=charset)
            dmsg = MIMEText(Utils.wrap(
                _("""\
If you reply to this message, keeping the Subject: header intact, Mailman will
discard the held message.  Do this if the message is spam.  If you reply to
this message and include an Approved: header with the list password in it, the
message will be approved for posting to the list.  The Approved: header can
also appear in the first line of the body of the reply.""")),
                            _charset=Utils.GetCharSet(lang))
            dmsg['Subject'] = 'confirm ' + cookie
            dmsg['Sender'] = requestaddr
            dmsg['From'] = requestaddr
            dmsg['Date'] = email.utils.formatdate(localtime=True)
            dmsg['Message-ID'] = Utils.unique_message_id(mlist)
            nmsg.attach(text)
            nmsg.attach(message.MIMEMessage(msg))
            nmsg.attach(message.MIMEMessage(dmsg))
            nmsg.send(mlist, **{'tomoderators': 1})
        finally:
            i18n.set_translation(otranslation)
    # Log the held message
    syslog('vette', '%s post from %s held, message-id=%s: %s', listname,
           sender, message_id, reason)
    # raise the specific MessageHeld exception to exit out of the message
    # delivery pipeline
    raise exc
Ejemplo n.º 24
0
               # encoded in their language.  Otherwise it'll be the preferred
               # language of the mailing list.
               lang = self.getMemberLanguage(addr)
           else:
               # Throw away the realnames
               addr = [a for realname, a in addrs]
               # Which member language do we attempt to use?  We could use
               # the first match or the first address, but in the face of
               # ambiguity, let's just use the list's preferred language
               lang = self.preferred_language
           otrans = i18n.get_translation()
           i18n.set_language(lang)
           try:
               fmsg = Message.UserNotification(
                   addr,
                   self.GetBouncesEmail(),
                   _('Forward of moderated message'),
                   lang=lang)
           finally:
               i18n.set_translation(otrans)
           fmsg.set_type('message/rfc822')
           fmsg.attach(copy)
           fmsg.send(self)
       # Log the rejection
       if rejection:
           note = '''%(listname)s: %(rejection)s posting:
tFrom: %(sender)s
tSubject: %(subject)s''' % {
               'listname': self.internal_name(),
               'rejection': rejection,
               'sender': str(sender).replace('%', '%%'),
Ejemplo n.º 25
0
   def __handlepost(self, record, value, comment, preserve, forward, addr):
       # For backwards compatibility with pre 2.0beta3
       ptime, sender, subject, reason, filename, msgdata = record
       path = os.path.join(mm_cfg.DATA_DIR, filename)
       # Handle message preservation
       if preserve:
           parts = os.path.split(path)[1].split(DASH)
           parts[0] = 'spam'
           spamfile = DASH.join(parts)
           # Preserve the message as plain text, not as a pickle
           try:
               fp = open(path)
           except IOError as e:
               if e.errno != errno.ENOENT: raise
               return LOST
           try:
               if path.endswith('.pck'):
                   msg = pickle.load(fp)
               else:
                   assert path.endswith('.txt'), '%s not .pck or .txt' % path
                   msg = fp.read()
           finally:
               fp.close()
           # Save the plain text to a .msg file, not a .pck file
           outpath = os.path.join(mm_cfg.SPAM_DIR, spamfile)
           head, ext = os.path.splitext(outpath)
           outpath = head + '.msg'
           outfp = open(outpath, 'wb')
           try:
               if path.endswith('.pck'):
                   g = Generator(outfp)
                   g.flatten(msg, 1)
               else:
                   outfp.write(msg)
           finally:
               outfp.close()
       # Now handle updates to the database
       rejection = None
       fp = None
       msg = None
       status = REMOVE
       if value == mm_cfg.DEFER:
           # Defer
           status = DEFER
       elif value == mm_cfg.APPROVE:
           # Approved.
           try:
               msg = readMessage(path)
           except IOError as e:
               if e.errno != errno.ENOENT: raise
               return LOST
           msg = readMessage(path)
           msgdata['approved'] = 1
           # adminapproved is used by the Emergency handler
           msgdata['adminapproved'] = 1
           # Calculate a new filebase for the approved message, otherwise
           # delivery errors will cause duplicates.
           try:
               del msgdata['filebase']
           except KeyError:
               pass
           # Queue the file for delivery by qrunner.  Trying to deliver the
           # message directly here can lead to a huge delay in web
           # turnaround.  Log the moderation and add a header.
           msg['X-Mailman-Approved-At'] = email.utils.formatdate(localtime=1)
           syslog('vette', '%s: held message approved, message-id: %s',
                  self.internal_name(), msg.get('message-id', 'n/a'))
           # Stick the message back in the incoming queue for further
           # processing.
           inq = get_switchboard(mm_cfg.INQUEUE_DIR)
           inq.enqueue(msg, _metadata=msgdata)
       elif value == mm_cfg.REJECT:
           # Rejected
           rejection = 'Refused'
           lang = self.getMemberLanguage(sender)
           subject = Utils.oneline(subject, Utils.GetCharSet(lang))
           self.__refuse(_('Posting of your message titled "%(subject)s"'),
                         sender,
                         comment or _('[No reason given]'),
                         lang=lang)
       else:
           assert value == mm_cfg.DISCARD
           # Discarded
           rejection = 'Discarded'
       # Forward the message
       if forward and addr:
           # If we've approved the message, we need to be sure to craft a
           # completely unique second message for the forwarding operation,
           # since we don't want to share any state or information with the
           # normal delivery.
           try:
               copy = readMessage(path)
           except IOError as e:
               if e.errno != errno.ENOENT: raise
               raise Errors.LostHeldMessage(path)
           # It's possible the addr is a comma separated list of addresses.
           addrs = getaddresses([addr])
           if len(addrs) == 1:
               realname, addr = addrs[0]
               # If the address getting the forwarded message is a member of
               # the list, we want the headers of the outer message to be
               # encoded in their language.  Otherwise it'll be the preferred
               # language of the mailing list.
               lang = self.getMemberLanguage(addr)
           else:
               # Throw away the realnames
               addr = [a for realname, a in addrs]
               # Which member language do we attempt to use?  We could use
               # the first match or the first address, but in the face of
               # ambiguity, let's just use the list's preferred language
               lang = self.preferred_language
           otrans = i18n.get_translation()
           i18n.set_language(lang)
           try:
               fmsg = Message.UserNotification(
                   addr,
                   self.GetBouncesEmail(),
                   _('Forward of moderated message'),
                   lang=lang)
           finally:
               i18n.set_translation(otrans)
           fmsg.set_type('message/rfc822')
           fmsg.attach(copy)
           fmsg.send(self)
       # Log the rejection
       if rejection:
           note = '''%(listname)s: %(rejection)s posting:
tFrom: %(sender)s
tSubject: %(subject)s''' % {
               'listname': self.internal_name(),
               'rejection': rejection,
               'sender': str(sender).replace('%', '%%'),
               'subject': str(subject).replace('%', '%%'),
           }
           if comment:
               note += '\n\tReason: ' + comment.replace('%', '%%')
           syslog('vette', note)
       # Always unlink the file containing the message text.  It's not
       # necessary anymore, regardless of the disposition of the message.
       if status != DEFER:
           try:
               os.unlink(path)
           except OSError as e:
               if e.errno != errno.ENOENT: raise
               # We lost the message text file.  Clean up our housekeeping
               # and inform of this status.
               return LOST
       return status
Ejemplo n.º 26
0
#! /usr/bin/env python
Ejemplo n.º 27
0
            # a probe occurred.  BAW: should we inform the list moderator?
            listaddr = mlist.GetListEmail()
            # Set the language for this email message to the member's language.
            mlang = mlist.getMemberLanguage(email)
            otrans = i18n.get_translation()
            i18n.set_language(mlang)
            try:
                msg = Message.UserNotification(
                    mlist.getMemberCPAddress(email),
                    mlist.GetBouncesEmail(),
                    _('Mailman privacy alert'),
                    _("""\
An attempt was made to subscribe your address to the mailing list
%(listaddr)s.  You are already subscribed to this mailing list.

Note that the list membership is not public, so it is possible that a bad
person was trying to probe the list for its membership.  This would be a
privacy violation if we let them do this, but we didn't.

If you submitted the subscription request and forgot that you were already
subscribed to the list, then you can ignore this message.  If you suspect that
an attempt is being made to covertly discover whether you are a member of this
list, and you are worried about your privacy, then feel free to send a message
to the list administrator at %(listowner)s.
"""), lang=mlang)
            finally:
                i18n.set_translation(otrans)
            msg.send(mlist)
    # These shouldn't happen unless someone's tampering with the form
    except Errors.MMCantDigestError:
        results = _('This list does not support digest delivery.')
    except Errors.MMMustDigestError:
Ejemplo n.º 28
0
def process(mlist, msg, msgdata):
    # Normally, the replybot should get a shot at this message, but there are
    # some important short-circuits, mostly to suppress 'bot storms, at least
    # for well behaved email bots (there are other governors for misbehaving
    # 'bots).  First, if the original message has an "X-Ack: No" header, we
    # skip the replybot.  Then, if the message has a Precedence header with
    # values bulk, junk, or list, and there's no explicit "X-Ack: yes" header,
    # we short-circuit.  Finally, if the message metadata has a true 'noack'
    # key, then we skip the replybot too.
    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 owner script sets the `toowner' key, and the various
    # confirm, join, leave, request, subscribe and unsubscribe scripts set the
    # keys we use for `torequest'.
    toadmin = msgdata.get('toowner')
    torequest = msgdata.get('torequest') or msgdata.get('toconfirm') or \
                    msgdata.get('tojoin') or msgdata.get('toleave')
    if ((toadmin and not mlist.autorespond_admin) or
           (torequest and not mlist.autorespond_requests) or \
           (not toadmin and not torequest and not mlist.autorespond_postings)):
        return
    # Now see if we're in the grace period for this sender.  graceperiod <= 0
    # means always autorespond, as does an "X-Ack: yes" header (useful for
    # debugging).
    sender = msg.get_sender()
    now = time.time()
    graceperiod = mlist.autoresponse_graceperiod
    if graceperiod > 0 and ack <> 'yes':
        if toadmin:
            quiet_until = mlist.admin_responses.get(sender, 0)
        elif torequest:
            quiet_until = mlist.request_responses.get(sender, 0)
        else:
            quiet_until = mlist.postings_responses.get(sender, 0)
        if quiet_until > now:
            return
    #
    # Okay, we know we're going to auto-respond to this sender, craft the
    # message, send it, and update the database.
    realname = mlist.real_name
    subject = _(
        'Auto-response for your message to the "%(realname)s" mailing list')
    # Do string interpolation
    d = SafeDict({'listname'    : realname,
                  'listurl'     : mlist.GetScriptURL('listinfo'),
                  'requestemail': mlist.GetRequestEmail(),
                  # BAW: Deprecate adminemail; it's not advertised but still
                  # supported for backwards compatibility.
                  'adminemail'  : mlist.GetBouncesEmail(),
                  'owneremail'  : mlist.GetOwnerEmail(),
                  })
    # Just because we're using a SafeDict doesn't mean we can't get all sorts
    # of other exceptions from the string interpolation.  Let's be ultra
    # conservative here.
    if toadmin:
        rtext = mlist.autoresponse_admin_text
    elif torequest:
        rtext = mlist.autoresponse_request_text
    else:
        rtext = mlist.autoresponse_postings_text
    # Using $-strings?
    if getattr(mlist, 'use_dollar_strings', 0):
        rtext = Utils.to_percent(rtext)
    try:
        text = rtext % d
    except Exception:
        syslog('error', 'Bad autoreply text for list: %s\n%s',
               mlist.internal_name(), rtext)
        text = rtext
    # Wrap the response.
    text = Utils.wrap(text)
    outmsg = Message.UserNotification(sender, mlist.GetBouncesEmail(),
                                      subject, text, mlist.preferred_language)
    outmsg['X-Mailer'] = _('The Mailman Replybot')
    # prevent recursions and mail loops!
    outmsg['X-Ack'] = 'No'
    outmsg.send(mlist)
    # update the grace period database
    if graceperiod > 0:
        # graceperiod is in days, we need # of seconds
        quiet_until = now + graceperiod * 24 * 60 * 60
        if toadmin:
            mlist.admin_responses[sender] = quiet_until
        elif torequest:
            mlist.request_responses[sender] = quiet_until
        else:
            mlist.postings_responses[sender] = quiet_until
Ejemplo n.º 29
0
 def process_message(self, peer, mailfrom, rcpttos, data):
     from cStringIO import StringIO
     from Mailman import Utils
     from Mailman import Message
     from Mailman import MailList
     # If the message is to a Mailman mailing list, then we'll invoke the
     # Mailman script directly, without going through the real smtpd.
     # Otherwise we'll forward it to the local proxy for disposition.
     listnames = []
     for rcpt in rcpttos:
         local = rcpt.lower().split('@')[0]
         # We allow the following variations on the theme
         #   listname
         #   listname-admin
         #   listname-owner
         #   listname-request
         #   listname-join
         #   listname-leave
         parts = local.split('-')
         if len(parts) > 2:
             continue
         listname = parts[0]
         if len(parts) == 2:
             command = parts[1]
         else:
             command = ''
         if not Utils.list_exists(listname) or command not in (
                 '', 'admin', 'owner', 'request', 'join', 'leave'):
             continue
         listnames.append((rcpt, listname, command))
     # Remove all list recipients from rcpttos and forward what we're not
     # going to take care of ourselves.  Linear removal should be fine
     # since we don't expect a large number of recipients.
     for rcpt, listname, command in listnames:
         rcpttos.remove(rcpt)
     # If there's any non-list destined recipients left,
     print >> DEBUGSTREAM, 'forwarding recips:', ' '.join(rcpttos)
     if rcpttos:
         refused = self._deliver(mailfrom, rcpttos, data)
         # TBD: what to do with refused addresses?
         print >> DEBUGSTREAM, 'we got refusals:', refused
     # Now deliver directly to the list commands
     mlists = {}
     s = StringIO(data)
     msg = Message.Message(s)
     # These headers are required for the proper execution of Mailman.  All
     # MTAs in existence seem to add these if the original message doesn't
     # have them.
     if not msg.getheader('from'):
         msg['From'] = mailfrom
     if not msg.getheader('date'):
         msg['Date'] = time.ctime(time.time())
     for rcpt, listname, command in listnames:
         print >> DEBUGSTREAM, 'sending message to', rcpt
         mlist = mlists.get(listname)
         if not mlist:
             mlist = MailList.MailList(listname, lock=0)
             mlists[listname] = mlist
         # dispatch on the type of command
         if command == '':
             # post
             msg.Enqueue(mlist, tolist=1)
         elif command == 'admin':
             msg.Enqueue(mlist, toadmin=1)
         elif command == 'owner':
             msg.Enqueue(mlist, toowner=1)
         elif command == 'request':
             msg.Enqueue(mlist, torequest=1)
         elif command in ('join', 'leave'):
             # TBD: this is a hack!
             if command == 'join':
                 msg['Subject'] = 'subscribe'
             else:
                 msg['Subject'] = 'unsubscribe'
             msg.Enqueue(mlist, torequest=1)
Ejemplo n.º 30
0
def process_form(mlist, doc, cgidata, lang):
    listowner = mlist.GetOwnerEmail()
    realname = mlist.real_name
    results = []

    # The email address being subscribed, required
    email = cgidata.getfirst('email', '').strip()
    if not email:
        results.append(_('You must supply a valid email address.'))

    fullname = cgidata.getfirst('fullname', '')
    # Canonicalize the full name
    fullname = Utils.canonstr(fullname, lang)
    # Who was doing the subscribing?
    remote = os.environ.get(
        'HTTP_FORWARDED_FOR',
        os.environ.get('HTTP_X_FORWARDED_FOR',
                       os.environ.get('REMOTE_ADDR', 'unidentified origin')))

    # Check reCAPTCHA submission, if enabled
    if mm_cfg.RECAPTCHA_SECRET_KEY:
        request = urllib.request.Request(
            url='https://www.google.com/recaptcha/api/siteverify',
            data=urllib.parse.urlencode({
                'secret':
                mm_cfg.RECAPTCHA_SECRET_KEY,
                'response':
                cgidata.getvalue('g-recaptcha-response', ''),
                'remoteip':
                remote
            }))
        try:
            httpresp = urllib.request.urlopen(request)
            captcha_response = json.load(httpresp)
            httpresp.close()
            if not captcha_response['success']:
                e_codes = COMMASPACE.join(captcha_response['error-codes'])
                results.append(_('reCAPTCHA validation failed: %(e_codes)s'))
        except urllib.error.URLError as e:
            e_reason = e.reason
            results.append(_('reCAPTCHA could not be validated: %(e_reason)s'))

    # Are we checking the hidden data?
    if mm_cfg.SUBSCRIBE_FORM_SECRET:
        now = int(time.time())
        # Try to accept a range in case of load balancers, etc.  (LP: #1447445)
        if remote.find('.') >= 0:
            # ipv4 - drop last octet
            remote1 = remote.rsplit('.', 1)[0]
        else:
            # ipv6 - drop last 16 (could end with :: in which case we just
            #        drop one : resulting in an invalid format, but it's only
            #        for our hash so it doesn't matter.
            remote1 = remote.rsplit(':', 1)[0]
        try:
            ftime, fhash = cgidata.getfirst('sub_form_token', '').split(':')
            then = int(ftime)
        except ValueError:
            ftime = fhash = ''
            then = 0
        token = Utils.sha_new(
            (mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + ftime + ":" +
             mlist.internal_name() + ":" + remote1).encode()).hexdigest()
        if ftime and now - then > mm_cfg.FORM_LIFETIME:
            results.append(_('The form is too old.  Please GET it again.'))
        if ftime and now - then < mm_cfg.SUBSCRIBE_FORM_MIN_TIME:
            results.append(
                _('Please take a few seconds to fill out the form before submitting it.'
                  ))
        if ftime and token != fhash:
            results.append(
                _("The hidden token didn't match.  Did your IP change?"))
        if not ftime:
            results.append(
                _('There was no hidden token in your submission or it was corrupted.'
                  ))
            results.append(_('You must GET the form before submitting it.'))
    # Was an attempt made to subscribe the list to itself?
    if email == mlist.GetListEmail():
        syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote)
        results.append(_('You may not subscribe a list to itself!'))
    # If the user did not supply a password, generate one for him
    password = cgidata.getfirst('pw', '').strip()
    confirmed = cgidata.getfirst('pw-conf', '').strip()

    if not password and not confirmed:
        password = Utils.MakeRandomPassword()
    elif not password or not confirmed:
        results.append(_('If you supply a password, you must confirm it.'))
    elif password != confirmed:
        results.append(_('Your passwords did not match.'))

    # Get the digest option for the subscription.
    digestflag = cgidata.getfirst('digest')
    if digestflag:
        try:
            digest = int(digestflag)
        except (TypeError, ValueError):
            digest = 0
    else:
        digest = mlist.digest_is_default

    # Sanity check based on list configuration.  BAW: It's actually bogus that
    # the page allows you to set the digest flag if you don't really get the
    # choice. :/
    if not mlist.digestable:
        digest = 0
    elif not mlist.nondigestable:
        digest = 1

    if results:
        print_results(mlist, ERRORSEP.join(results), doc, lang)
        return

    # If this list has private rosters, we have to be careful about the
    # message that gets printed, otherwise the subscription process can be
    # used to mine for list members.  It may be inefficient, but it's still
    # possible, and that kind of defeats the purpose of private rosters.
    # We'll use this string for all successful or unsuccessful subscription
    # results.
    if mlist.private_roster == 0:
        # Public rosters
        privacy_results = ''
    else:
        privacy_results = _("""\
Your subscription request has been received, and will soon be acted upon.
Depending on the configuration of this mailing list, your subscription request
may have to be first confirmed by you via email, or approved by the list
moderator.  If confirmation is required, you will soon get a confirmation
email which contains further instructions.""")

    try:
        userdesc = UserDesc(email, fullname, password, digest, lang)
        mlist.AddMember(userdesc, remote)
        results = ''
    # Check for all the errors that mlist.AddMember can throw options on the
    # web page for this cgi
    except Errors.MembershipIsBanned:
        results = _("""The email address you supplied is banned from this
        mailing list.  If you think this restriction is erroneous, please
        contact the list owners at %(listowner)s.""")
    except Errors.MMBadEmailError:
        results = _("""\
The email address you supplied is not valid.  (E.g. it must contain an
`@'.)""")
    except Errors.MMHostileAddress:
        results = _("""\
Your subscription is not allowed because the email address you gave is
insecure.""")
    except Errors.MMSubscribeNeedsConfirmation:
        # Results string depends on whether we have private rosters or not
        if privacy_results:
            results = privacy_results
        else:
            results = _("""\
Confirmation from your email address is required, to prevent anyone from
subscribing you without permission.  Instructions are being sent to you at
%(email)s.  Please note your subscription will not start until you confirm
your subscription.""")
    except Errors.MMNeedApproval as x:
        # Results string depends on whether we have private rosters or not
        if privacy_results:
            results = privacy_results
        else:
            # We need to interpolate into x.__str__()
            x = _(str(x))
            results = _("""\
Your subscription request was deferred because %(x)s.  Your request has been
forwarded to the list moderator.  You will receive email informing you of the
moderator's decision when they get to your request.""")
    except Errors.MMAlreadyAMember:
        # Results string depends on whether we have private rosters or not
        if not privacy_results:
            results = _('You are already subscribed.')
        else:
            results = privacy_results
            # This could be a membership probe.  For safety, let the user know
            # a probe occurred.  BAW: should we inform the list moderator?
            listaddr = mlist.GetListEmail()
            # Set the language for this email message to the member's language.
            mlang = mlist.getMemberLanguage(email)
            otrans = i18n.get_translation()
            i18n.set_language(mlang)
            try:
                msg = Message.UserNotification(mlist.getMemberCPAddress(email),
                                               mlist.GetBouncesEmail(),
                                               _('Mailman privacy alert'),
                                               _("""\
An attempt was made to subscribe your address to the mailing list
%(listaddr)s.  You are already subscribed to this mailing list.

Note that the list membership is not public, so it is possible that a bad
person was trying to probe the list for its membership.  This would be a
privacy violation if we let them do this, but we didn't.

If you submitted the subscription request and forgot that you were already
subscribed to the list, then you can ignore this message.  If you suspect that
an attempt is being made to covertly discover whether you are a member of this
list, and you are worried about your privacy, then feel free to send a message
to the list administrator at %(listowner)s.
"""),
                                               lang=mlang)
            finally:
                i18n.set_translation(otrans)
            msg.send(mlist)
    # These shouldn't happen unless someone's tampering with the form
    except Errors.MMCantDigestError:
        results = _('This list does not support digest delivery.')
    except Errors.MMMustDigestError:
        results = _('This list only supports digest delivery.')
    else:
        # Everything's cool.  Our return string actually depends on whether
        # this list has private rosters or not
        if privacy_results:
            results = privacy_results
        else:
            results = _("""\
You have been successfully subscribed to the %(realname)s mailing list.""")
    # Show the results
    print_results(mlist, results, doc, lang)