示例#1
0
def loginpage(mlist, scriptname, msg='', frontpage=None):
    url = mlist.GetScriptURL(scriptname)
    if frontpage:
        actionurl = url
    else:
        actionurl = Utils.GetRequestURI(url)
    if msg:
        msg = FontAttr(msg, color='#ff0000', size='+1').Format()
        # give an HTTP 401 for authentication failure
        print 'Status: 401 Unauthorized'
    if scriptname == 'admindb':
        who = _('Moderator')
    else:
        who = _('Administrator')
    # Language stuff
    charset = Utils.GetCharSet(mlist.preferred_language)
    print 'Content-type: text/html; charset=' + charset + '\n\n'
    print Utils.maketext(
        'admlogin.html',
        {'listname': mlist.real_name,
         'path'    : actionurl,
         'message' : msg,
         'who'     : who,
         }, mlist=mlist)
    print mlist.GetMailmanFooter()
示例#2
0
def loginpage(mlist, scriptname, msg='', frontpage=None):
    url = mlist.GetScriptURL(scriptname)
    if frontpage:
        actionurl = url
    else:
        actionurl = Utils.GetRequestURI(url)
    if msg:
        msg = FontAttr(msg, color='#ff0000', size='+1').Format()
        # give an HTTP 401 for authentication failure
        print 'Status: 401 Unauthorized'
    if scriptname == 'admindb':
        who = _('Moderator')
    else:
        who = _('Administrator')
    # Language stuff
    charset = Utils.GetCharSet(mlist.preferred_language)
    print 'Content-type: text/html; charset=' + charset + '\n\n'
    print Utils.maketext('admlogin.html', {
        'listname': mlist.real_name,
        'path': actionurl,
        'message': msg,
        'who': who,
    },
                         mlist=mlist)
    print mlist.GetMailmanFooter()
示例#3
0
 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,
              ## cpanel patch
              '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})
示例#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)
示例#5
0
    def ParseTags(self, template, replacements, lang=None):
        if lang is None:
            charset = 'us-ascii'
        else:
            charset = Utils.GetCharSet(lang)
        text = Utils.maketext(template, raw=1, lang=lang, mlist=self)
        parts = re.split('(</?[Mm][Mm]-[^>]*>)', text)
        i = 1
        while i < len(parts):
            tag = parts[i].lower()
            if replacements.has_key(tag):
                repl = replacements[tag]
                if isinstance(repl, type(u'')):
                    repl = repl.encode(charset, 'replace')
                parts[i] = repl
            else:
                parts[i] = ''

            # EGL: Munged these values to handle the separate hostnames for the 
            # mail and web servers.
            if tag == "<mm-posting-addr>":
                parts[i] = parts[i].replace("mail.", "")

            i = i + 2
        return EMPTYSTRING.join(parts)
示例#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)
示例#7
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)
示例#8
0
 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)
示例#9
0
 def __sendAdminBounceNotice(self, member, msg):
     # 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": _("disabled"),
             "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)
示例#10
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)
 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)
 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})
示例#13
0
文件: util.py 项目: truls/dikumail.dk
    def create(self, email):
        if self.exists:
            raise ListAlreadyExists

        langs = [mm_cfg.DEFAULT_SERVER_LANGUAGE]
        pw = Utils.MakeRandomPassword()
        pw_hashed = Utils.sha_new(pw).hexdigest()
        urlhost = mm_cfg.DEFAULT_URL_HOST
        host_name = mm_cfg.DEFAULT_EMAIL_HOST
        web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost

        # TODO: Add some atomicity. We should roll back changes using
        #       a try/else if something (like MTA alias update) fails
        #       before the function terminates.

        try:
            oldmask = os.umask(002)
            self.mlist.Create(self.name, email, pw_hashed, langs=langs,
                              emailhost=host_name, urlhost=urlhost)
            self.mlist.preferred_language = langs[0]

            # Reply-To set to list address
            self.mlist.reply_goes_to_list = 2
            self.mlist.reply_to_address = "%s@%s" % (self.list, self.domain)

            # Allow messages from listname@domain
            self.mlist.acceptable_aliases = "%s@%s\n" % (self.list, self.domain)

            self.mlist.subject_prefix = "[%s] " % (self.list)
            self.mlist.msg_footer = ""
            self.mlist.subscribe_policy = 2 # Confirm and approve
            self.mlist.max_message_size = 20480 # 20M

            self.mlist.Save()

        finally:
            os.umask(oldmask)
            self.mlist.Unlock()

        if mm_cfg.MTA:
            modname = 'Mailman.MTA.' + mm_cfg.MTA
            __import__(modname)
            sys.modules[modname].create(self.mlist)

        siteowner = Utils.get_site_email(self.mlist.host_name, 'owner')
        text = Utils.maketext(
            'newlist.txt',
            {'listname'    : self.name,
             'password'    : pw,
             'admin_url'   : self.mlist.GetScriptURL('admin', absolute=1),
             'listinfo_url': self.mlist.GetScriptURL('listinfo', absolute=1),
             'requestaddr' : self.mlist.GetRequestEmail(),
             'siteowner'   : siteowner,
             }, mlist=self.mlist)


        msg = Message.UserNotification(email, siteowner, 'Your new mailing list: %s' % self.name,
                                        text, self.mlist.preferred_language)
        msg.send(self.mlist)
def process(res, args):
    # Get the help text introduction
    mlist = res.mlist
    # Since this message is personalized, add some useful information if the
    # address requesting help is a member of the list.
    msg = res.msg
    for sender in msg.get_senders():
        if mlist.isMember(sender):
            memberurl = mlist.GetOptionsURL(sender, absolute=1)
            urlhelp = _(
                'You can access your personal options via the following url:')
            res.results.append(urlhelp)
            res.results.append(memberurl)
            # Get a blank line in the output.
            res.results.append('')
            break
    # build the specific command helps from the module docstrings
    modhelps = {}
    import Mailman.Commands
    path = os.path.dirname(os.path.abspath(Mailman.Commands.__file__))
    for file in os.listdir(path):
        if not file.startswith('cmd_') or not file.endswith('.py'):
            continue
        module = os.path.splitext(file)[0]
        modname = 'Mailman.Commands.' + module
        try:
            __import__(modname)
        except ImportError:
            continue
        cmdname = module[4:]
        help = None
        if hasattr(sys.modules[modname], 'gethelp'):
            help = sys.modules[modname].gethelp(mlist)
        if help:
            modhelps[cmdname] = help
    # Now sort the command helps
    helptext = []
    keys = list(modhelps.keys())
    keys.sort()
    for cmd in keys:
        helptext.append(modhelps[cmd])
    commands = EMPTYSTRING.join(helptext)
    # Now craft the response
    helptext = Utils.maketext(
        'help.txt', {
            'listname': mlist.real_name,
            'version': mm_cfg.VERSION,
            'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
            'requestaddr': mlist.GetRequestEmail(),
            'adminaddr': mlist.GetOwnerEmail(),
            'commands': commands,
        },
        mlist=mlist,
        lang=res.msgdata['lang'],
        raw=1)
    # Now add to the response
    res.results.append('help')
    res.results.append(helptext)
示例#15
0
def hold_for_approval(mlist, msg, msgdata, exc):
    # TBD: 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.  That's for later though, because it would mean a revamp of the
    # MailCommandHandler too.
    #
    if type(exc) is ClassType:
        # Go ahead and instantiate it now.
        exc = exc()
    listname = mlist.real_name
    reason = str(exc)
    sender = msg.GetSender()
    adminaddr = mlist.GetAdminEmail()
    msgdata['rejection-notice'] = exc.rejection_notice(mlist)
    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'    : msg.get('subject', '(no subject)'),
         'admindb_url': mlist.GetScriptURL('admindb', absolute=1),
         }
    if mlist.admin_immed_notify:
        # get the text from the template
        subject = '%s post from %s requires approval' % (listname, sender)
        text = Utils.maketext('postauth.txt', d, raw=1)
        # craft the admin notification message and deliver it
        msg = Message.UserNotification(adminaddr, adminaddr, subject, text)
        HandlerAPI.DeliverToUser(mlist, msg)
    # We may want to send a notification to the original sender too
    fromusenet = msgdata.get('fromusenet')
    if not fromusenet and not mlist.dont_respond_to_post_requests:
        subject = 'Your message to %s awaits moderator approval' % listname
        text = Utils.maketext('postheld.txt', d)
        msg = Message.UserNotification(sender, adminaddr, subject, text)
        HandlerAPI.DeliverToUser(mlist, msg)
    # Log the held message
    syslog('vette', '%s post from %s held: %s' % (listname, sender, reason))
    # raise the specific MessageHeld exception to exit out of the message
    # delivery pipeline
    raise exc
示例#16
0
def process(res, args):
    # Get the help text introduction
    mlist = res.mlist
    # Since this message is personalized, add some useful information if the
    # address requesting help is a member of the list.
    msg = res.msg
    for sender in  msg.get_senders():
        if mlist.isMember(sender):
            memberurl = mlist.GetOptionsURL(sender, absolute=1)
            urlhelp = _(
                'You can access your personal options via the following url:')
            res.results.append(urlhelp)
            res.results.append(memberurl)
            # Get a blank line in the output.
            res.results.append('')
            break
    # build the specific command helps from the module docstrings
    modhelps = {}
    import Mailman.Commands
    path = os.path.dirname(os.path.abspath(Mailman.Commands.__file__))
    for file in os.listdir(path):
        if not file.startswith('cmd_') or not file.endswith('.py'):
            continue
        module = os.path.splitext(file)[0]
        modname = 'Mailman.Commands.' + module
        try:
            __import__(modname)
        except ImportError:
            continue
        cmdname = module[4:]
        help = None
        if hasattr(sys.modules[modname], 'gethelp'):
            help = sys.modules[modname].gethelp(mlist)
        if help:
            modhelps[cmdname] = help
    # Now sort the command helps
    helptext = []
    keys = modhelps.keys()
    keys.sort()
    for cmd in keys:
        helptext.append(modhelps[cmd])
    commands = EMPTYSTRING.join(helptext)
    # Now craft the response
    helptext = Utils.maketext(
        'help.txt',
        {'listname'    : mlist.real_name,
         'version'     : mm_cfg.VERSION,
         'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
         'requestaddr' : mlist.GetRequestEmail(),
         'adminaddr'   : mlist.GetOwnerEmail(),
         'commands'    : commands,
         }, mlist=mlist, lang=res.msgdata['lang'], raw=1)
    # Now add to the response
    res.results.append('help')
    res.results.append(helptext)
示例#17
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)
示例#18
0
    def GetConfigInfo(self):
        WIDTH = mm_cfg.TEXTFIELDWIDTH

	return [
            "Batched-delivery digest characteristics.",

	    ('digestable', mm_cfg.Toggle, ('No', 'Yes'), 1,
	     'Can list members choose to receive list traffic '
	     'bunched in digests?'),

	    ('digest_is_default', mm_cfg.Radio, 
	     ('Regular', 'Digest'), 0,
	     'Which delivery mode is the default for new users?'),

	    ('mime_is_default_digest', mm_cfg.Radio, 
	     ('Plain', 'Mime'), 0,
	     'When receiving digests, which format is default?'),

	    ('digest_size_threshhold', mm_cfg.Number, 3, 0,
	     'How big in Kb should a digest be before it gets sent out?'),
            # Should offer a 'set to 0' for no size threshhold.

 	    ('digest_send_periodic', mm_cfg.Radio, ('No', 'Yes'), 1,
	     'Should a digest be dispatched daily when the size threshold '
	     "isn't reached?"),

            ('digest_header', mm_cfg.Text, (4, WIDTH), 0,
	     'Header added to every digest',
             "Text attached (as an initial message, before the table"
             " of contents) to the top of digests. "
             + Utils.maketext('headfoot.html', raw=1)),

	    ('digest_footer', mm_cfg.Text, (4, WIDTH), 0,
	     'Footer added to every digest',
             "Text attached (as a final message) to the bottom of digests. "
             + Utils.maketext('headfoot.html', raw=1)),
	    ]
示例#19
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)
示例#20
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)
 def ParseTag(self, template, replacements):
    
     text = Utils.maketext(template, raw=1, lang=lang, mlist=self)
     parts = re.split('(</?[Mm][Mm]-[^>]*>)', text)
     i = 1
     while i < len(parts):
         tag = parts[i].lower()
         if replacements.has_key(tag):
             repl = replacements[tag]
             if isinstance(repl, type(u'')):
                 repl = repl.encode(charset, 'replace')
             parts[i] = repl
         else:
             parts[i] = ''
         i = i + 2
     return EMPTYSTRING.join(parts)    
示例#22
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)
示例#23
0
 def __refuse(self, request, recip, comment, origmsg=None):
     adminaddr = self.GetAdminEmail()
     text = Utils.maketext(
         'refuse.txt',
         {'listname' : self.real_name,
          'request'  : request,
          'reason'   : comment,
          'adminaddr': adminaddr,
          })
     # add in original message, but not wrap/filled
     if origmsg:
         text = string.join([text,
                             '---------- Original Message ----------',
                             str(origmsg)], '\n')
     subject = 'Request to mailing list %s rejected' % self.real_name
     msg = Message.UserNotification(recip, adminaddr, subject, text)
     HandlerAPI.DeliverToUser(self, msg, {'_enqueue_immediate': 1})
示例#24
0
def PrintRequests(mlist, doc):
    # The only types of requests we know about are add member and post.
    # Anything else that might have gotten in here somehow we'll just ignore
    # (This should never happen unless someone is hacking at the code).
    doc.AddItem(Header(2, 'Administrative requests for mailing list: <em>' +
                       mlist.real_name + '</em>'))
    # short circuit for when there are no pending requests
    if not mlist.NumRequestsPending():
	doc.AddItem('There are no pending requests.')
	doc.AddItem(mlist.GetMailmanFooter())
	return

    doc.AddItem(Utils.maketext(
        'admindbpreamble.html', {'listname': mlist.real_name}, raw=1))
    doc.AddItem('.<p>')
    form = Form(mlist.GetScriptURL('admindb'))
    doc.AddItem(form)
    form.AddItem(SubmitButton('submit', 'Submit All Data'))
    #
    # Add the subscription request section
    subpendings = mlist.GetSubscriptionIds()
    if subpendings:
        form.AddItem('<hr>')
	form.AddItem(Center(Header(2, 'Subscription Requests')))
	t = Table(border=2)
	t.AddRow([
	    Bold('Address'),
	    Bold('Your Decision'),
	    Bold('If you refuse this subscription, please explain (optional)')
            ])
        for id in subpendings:
	    PrintAddMemberRequest(mlist, id, t)
	form.AddItem(t)
    # Post holds are now handled differently
    heldmsgs = mlist.GetHeldMessageIds()
    total = len(heldmsgs)
    if total:
        count = 1
        for id in heldmsgs:
            info = mlist.GetRecord(id)
            PrintPostRequest(mlist, id, info, total, count, form)
            count = count + 1
    form.AddItem('<hr>')
    form.AddItem(SubmitButton('submit', 'Submit All Data'))
    doc.AddItem(mlist.GetMailmanFooter())
示例#25
0
def FormatHTML(mlist, doc, template_name, template_info):
    doc.AddItem(Header(1,'%s:' % mlist.real_name))
    doc.AddItem(Header(1, template_info))
    doc.AddItem('<hr>')

    link = Link(mlist.GetScriptURL('admin'),
                _('View or edit the list configuration information.'))

    doc.AddItem(FontSize("+1", link))
    doc.AddItem('<p>')
    doc.AddItem('<hr>')
    form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name)
    text = Utils.maketext(template_name, raw=1, mlist=mlist)
    # MAS: Don't websafe twice.  TextArea does it.
    form.AddItem(TextArea('html_code', text, rows=40, cols=75))
    form.AddItem('<p>' + _('When you are done making changes...'))
    form.AddItem(SubmitButton('submit', _('Submit Changes')))
    doc.AddItem(form)
示例#26
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)
    def SendSubscribeAck(self, name, password, digest, text=''):
        pluser = self.getMemberLanguage(name)
        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 = ''
        #added to support a different template for Dlists
        if DlistUtils.enabled(self):
            template = "subscribeack-dyn.txt"
        else:
            template = "subscribeack.txt"
            
        # get the text from the template
        text += Utils.maketext(
            template,
            {'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)
示例#28
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)
示例#29
0
 def ParseTags(self, template, replacements, lang=None):
     if lang is None:
         charset = 'us-ascii'
     else:
         charset = Utils.GetCharSet(lang)
     text = Utils.maketext(template, raw=1, lang=lang, mlist=self)
     parts = re.split('(</?[Mm][Mm]-[^>]*>)', text)
     i = 1
     while i < len(parts):
         tag = parts[i].lower()
         if replacements.has_key(tag):
             repl = replacements[tag]
             if isinstance(repl, type(u'')):
                 repl = repl.encode(charset, 'replace')
             parts[i] = repl
         else:
             parts[i] = ''
         i = i + 2
     return EMPTYSTRING.join(parts)
示例#30
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)
示例#31
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)
示例#32
0
 def HoldSubscription(self, addr, password, digest):
     # assure that the database is open for writing
     self.__opendb()
     # get the next unique id
     id = self.__request_id()
     assert not self.__db.has_key(id)
     #
     # 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
     #
     data = time.time(), addr, password, digest
     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.real_name, addr))
     # possibly notify the administrator
     if self.admin_immed_notify:
         subject = 'New subscription request to list %s from %s' % (
             self.real_name, addr)
         text = Utils.maketext(
             'subauth.txt',
             {'username'   : addr,
              'listname'   : self.real_name,
              'hostname'   : self.host_name,
              'admindb_url': self.GetScriptURL('admindb', absolute=1),
              })
         adminaddr = self.GetAdminEmail()
         msg = Message.UserNotification(adminaddr, adminaddr, subject, text)
         HandlerAPI.DeliverToUser(self, msg, {'_enqueue_immediate': 1})
示例#33
0
def process(mlist, msg, msgdata):
    sender = msgdata.get('original_sender', msg.GetSender())
    sender = mlist.FindUser(sender)
    if sender and mlist.GetUserOption(sender, mm_cfg.AcknowledgePosts):
        subject = msg.getheader('subject')
        if subject:
            # trim off the subject prefix
            prefix = mlist.subject_prefix
            plen = len(prefix)
            if len(subject) > plen and subject[0:plen] == prefix:
                   subject = subject[plen:]
        # get the text from the template
        text = Utils.maketext(
            'postack.txt',
            {'subject'     : subject,
             'listname'    : mlist.real_name,
             'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
             })
        # craft the outgoing message, with all headers and attributes
        # necessary for general delivery
        subject = '%s post acknowledgement' % mlist.real_name
        msg = Message.UserNotification(sender, mlist.GetAdminEmail(),
                                       subject, text)
        HandlerAPI.DeliverToUser(mlist, msg)
示例#34
0
 # Add a link back to the overview, if we're not viewing the overview!
 adminurl = mlist.GetScriptURL('admin', absolute=1)
 d = {
     'listname': mlist.real_name,
     'detailsurl': admindburl + '?details=instructions',
     'summaryurl': admindburl,
     'viewallurl': admindburl + '?details=all',
     'adminurl': adminurl,
     'filterurl': adminurl + '/privacy/sender',
 }
 addform = 1
 if sender:
     esender = Utils.websafe(sender)
     d['description'] = _("all of %(esender)s's held messages.")
     doc.AddItem(
         Utils.maketext('admindbpreamble.html', d, raw=1, mlist=mlist))
     show_sender_requests(mlist, form, sender)
 elif msgid:
     d['description'] = _('a single held message.')
     doc.AddItem(
         Utils.maketext('admindbpreamble.html', d, raw=1, mlist=mlist))
     show_message_requests(mlist, form, msgid)
 elif details == 'all':
     d['description'] = _('all held messages.')
     doc.AddItem(
         Utils.maketext('admindbpreamble.html', d, raw=1, mlist=mlist))
     show_detailed_requests(mlist, form)
 elif details == 'instructions':
     doc.AddItem(
         Utils.maketext('admindbdetails.html', d, raw=1, mlist=mlist))
     addform = 0
示例#35
0
         _('Discard all messages marked <em>Defer</em>')
         ))
 # Add a link back to the overview, if we're not viewing the overview!
 adminurl = mlist.GetScriptURL('admin', absolute=1)
 d = {'listname'  : mlist.real_name,
      'detailsurl': admindburl + '?details=instructions',
      'summaryurl': admindburl,
      'viewallurl': admindburl + '?details=all',
      'adminurl'  : adminurl,
      'filterurl' : adminurl + '/privacy/sender',
      }
 addform = 1
 if sender:
     esender = Utils.websafe(sender)
     d['description'] = _("all of %(esender)s's held messages.")
     doc.AddItem(Utils.maketext('admindbpreamble.html', d,
                                raw=1, mlist=mlist))
     show_sender_requests(mlist, form, sender)
 elif msgid:
     d['description'] = _('a single held message.')
     doc.AddItem(Utils.maketext('admindbpreamble.html', d,
                                raw=1, mlist=mlist))
     show_message_requests(mlist, form, msgid)
 elif details == 'all':
     d['description'] = _('all held messages.')
     doc.AddItem(Utils.maketext('admindbpreamble.html', d,
                                raw=1, mlist=mlist))
     show_detailed_requests(mlist, form)
 elif details == 'instructions':
     doc.AddItem(Utils.maketext('admindbdetails.html', d,
                                raw=1, mlist=mlist))
     addform = 0
示例#36
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)
示例#37
0
            action += '.mbox'
        if parts[1:]:
            action = os.path.join(action, SLASH.join(parts[1:]))
        # If we added '/index.html' to true_filename, add a slash to the URL.
        # We need this because we no longer add the trailing slash in the
        # private.html template.  It's always OK to test parts[-1] since we've
        # already verified parts[0] is listname.  The basic rule is if the
        # post URL (action) is a directory, it must be slash terminated, but
        # not if it's a file.  Otherwise, relative links in the target archive
        # page don't work.
        if true_filename.endswith('/index.html') and parts[-1] <> 'index.html':
            action += SLASH
        # Escape web input parameter to avoid cross-site scripting.
        print Utils.maketext(
            'private.html',
            {'action'  : Utils.websafe(action),
             'realname': mlist.real_name,
             'message' : message,
             }, mlist=mlist)
        return

    lang = mlist.getMemberLanguage(username)
    i18n.set_language(lang)
    doc.set_language(lang)

    # Authorization confirmed... output the desired file
    try:
        ctype, enc = guess_type(path, strict=0)
        if ctype is None:
            ctype = 'text/html'
        if mboxfile:
            f = open(os.path.join(mlist.archive_dir() + '.mbox',
示例#38
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
示例#39
0
    def GetConfigInfo(self, mlist, category, subcat=None):
        if category <> 'digest':
            return None
        WIDTH = mm_cfg.TEXTFIELDWIDTH

        info = [
            _("Batched-delivery digest characteristics."),
            ('digestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1,
             _('Can list members choose to receive list traffic '
               'bunched in digests?')),
            ('digest_is_default', mm_cfg.Radio, (_('Regular'), _('Digest')), 0,
             _('Which delivery mode is the default for new users?')),
            ('mime_is_default_digest', mm_cfg.Radio, (_('Plain'), _('MIME')),
             0, _('When receiving digests, which format is default?')),
            ('digest_size_threshhold', mm_cfg.Number, 3, 0,
             _('How big in Kb should a digest be before it gets sent out?'
               '  0 implies no maximum size.')),
            ('digest_send_periodic', mm_cfg.Radio, (_('No'), _('Yes')), 1,
             _('Should a digest be dispatched daily when the size threshold '
               "isn't reached?")),
            ('digest_header', mm_cfg.Text, (4, WIDTH), 0,
             _('Header added to every digest'),
             _("Text attached (as an initial message, before the table"
               " of contents) to the top of digests. ") +
             Utils.maketext('headfoot.html', raw=1, mlist=mlist)),
            ('digest_footer', mm_cfg.Text, (4, WIDTH), 0,
             _('Footer added to every digest'),
             _("Text attached (as a final message) to the bottom of digests. ")
             + Utils.maketext('headfoot.html', raw=1, mlist=mlist)),
            ('digest_volume_frequency',
             mm_cfg.Radio, (_('Yearly'), _('Monthly'), _('Quarterly'),
                            _('Weekly'), _('Daily')), 0,
             _('How often should a new digest volume be started?'),
             _('''When a new digest volume is started, the volume number is
             incremented and the issue number is reset to 1.''')),
            ('_new_volume', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
             _('Should Mailman start a new digest volume?'),
             _('''Setting this option instructs Mailman to start a new volume
             with the next digest sent out.''')),
            ('_send_digest_now', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
             _('''Should Mailman send the next digest right now, if it is not
             empty?''')),
        ]

        ##        if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION:
        ##            info.extend([
        ##                ('digest_personalize', mm_cfg.Toggle, (_('No'), _('Yes')), 1,

        ##                 _('''Should Mailman personalize each digest delivery?
        ##                 This is often useful for announce-only lists, but <a
        ##                 href="?VARHELP=digest/digest_personalize">read the details</a>
        ##                 section for a discussion of important performance
        ##                 issues.'''),

        ##                 _("""Normally, Mailman sends the digest messages to
        ##                 the mail server in batches.  This is much more efficent
        ##                 because it reduces the amount of traffic between Mailman and
        ##                 the mail server.

        ##                 <p>However, some lists can benefit from a more personalized
        ##                 approach.  In this case, Mailman crafts a new message for
        ##                 each member on the digest delivery list.  Turning this on
        ##                 adds a few more expansion variables that can be included in
        ##                 the <a href="?VARHELP=digest/digest_header">message header</a>
        ##                 and <a href="?VARHELP=digest/digest_footer">message footer</a>
        ##                 but it may degrade the performance of your site as
        ##                 a whole.

        ##                 <p>You need to carefully consider whether the trade-off is
        ##                 worth it, or whether there are other ways to accomplish what
        ##                 you want.  You should also carefully monitor your system load
        ##                 to make sure it is acceptable.

        ##                 <p>These additional substitution variables will be available
        ##                 for your headers and footers, when this feature is enabled:

        ##                 <ul><li><b>user_address</b> - The address of the user,
        ##                         coerced to lower case.
        ##                     <li><b>user_delivered_to</b> - The case-preserved address
        ##                         that the user is subscribed with.
        ##                     <li><b>user_password</b> - The user's password.
        ##                     <li><b>user_name</b> - The user's full name.
        ##                     <li><b>user_optionsurl</b> - The url to the user's option
        ##                         page.
        ##                 """))
        ##                ])

        return info
示例#40
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)
示例#41
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)
示例#42
0
def main():
    doc = Document()
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    parts = Utils.GetPathPieces()
    if not parts:
        doc.SetTitle(_("Private Archive Error"))
        doc.AddItem(Header(3, _("You must specify a list.")))
        print(doc.Format())
        return

    path = os.environ.get('PATH_INFO')
    tpath = true_path(path)
    if tpath != path[1:]:
        msg = _('Private archive - "./" and "../" not allowed in URL.')
        doc.SetTitle(msg)
        doc.AddItem(Header(2, msg))
        print(doc.Format())
        syslog('mischief', 'Private archive hostile path: %s', path)
        return
    # BAW: This needs to be converted to the Site module abstraction
    true_filename = os.path.join(
        mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath)

    listname = parts[0].lower()
    mboxfile = ''
    if len(parts) > 1:
        mboxfile = parts[1]

    # See if it's the list's mbox file is being requested
    if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \
           listname[:-5] == mboxfile[:-5]:
        listname = listname[:-5]
    else:
        mboxfile = ''

    # If it's a directory, we have to append index.html in this script.  We
    # must also check for a gzipped file, because the text archives are
    # usually stored in compressed form.
    if os.path.isdir(true_filename):
        true_filename = true_filename + '/index.html'
    if not os.path.exists(true_filename) and \
           os.path.exists(true_filename + '.gz'):
        true_filename = true_filename + '.gz'

    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError as e:
        # Avoid cross-site scripting attacks
        safelistname = Utils.websafe(listname)
        msg = _('No such list <em>%(safelistname)s</em>')
        doc.SetTitle(_("Private Archive Error - %(msg)s"))
        doc.AddItem(Header(2, msg))
        # Send this with a 404 status.
        print('Status: 404 Not Found')
        print(doc.Format())
        syslog('error', 'private: No such list "%s": %s\n', listname, e)
        return

    i18n.set_language(mlist.preferred_language)
    doc.set_language(mlist.preferred_language)

    cgidata = cgi.FieldStorage()
    try:
        username = cgidata.getfirst('username', '')
    except TypeError:
        # Someone crafted a POST with a bad Content-Type:.
        doc.AddItem(Header(2, _("Error")))
        doc.AddItem(Bold(_('Invalid options to CGI script.')))
        # Send this with a 400 status.
        print('Status: 400 Bad Request')
        print(doc.Format())
        return
    password = cgidata.getfirst('password', '')

    is_auth = 0
    realname = mlist.real_name
    message = ''

    if not mlist.WebAuthenticate((mm_cfg.AuthUser,
                                  mm_cfg.AuthListModerator,
                                  mm_cfg.AuthListAdmin,
                                  mm_cfg.AuthSiteAdmin),
                                 password, username):
        if 'submit' in cgidata:
            # This is a re-authorization attempt
            message = Bold(FontSize('+1', _('Authorization failed.'))).Format()
            remote = os.environ.get('HTTP_FORWARDED_FOR',
                     os.environ.get('HTTP_X_FORWARDED_FOR',
                     os.environ.get('REMOTE_ADDR',
                                    'unidentified origin')))
            syslog('security',
                 'Authorization failed (private): user=%s: list=%s: remote=%s',
                   username, listname, remote)
            # give an HTTP 401 for authentication failure
            print('Status: 401 Unauthorized')
        # Are we processing a password reminder from the login screen?
        if 'login-remind' in cgidata:
            if username:
                message = Bold(FontSize('+1', _("""If you are a list member,
                          your password has been emailed to you."""))).Format()
            else:
                message = Bold(FontSize('+1',
                                _('Please enter your email address'))).Format()
            if mlist.isMember(username):
                mlist.MailUserPassword(username)
            elif username:
                # Not a member
                if mlist.private_roster == 0:
                    # Public rosters
                    safeuser = Utils.websafe(username)
                    message = Bold(FontSize('+1',
                                  _('No such member: %(safeuser)s.'))).Format()
                else:
                    syslog('mischief',
                       'Reminder attempt of non-member w/ private rosters: %s',
                       username)
        # Output the password form
        charset = Utils.GetCharSet(mlist.preferred_language)
        print('Content-type: text/html; charset=' + charset + '\n\n')
        # Put the original full path in the authorization form, but avoid
        # trailing slash if we're not adding parts.  We add it below.
        action = mlist.GetScriptURL('private', absolute=1)
        if mboxfile:
            action += '.mbox'
        if parts[1:]:
            action = os.path.join(action, SLASH.join(parts[1:]))
        # If we added '/index.html' to true_filename, add a slash to the URL.
        # We need this because we no longer add the trailing slash in the
        # private.html template.  It's always OK to test parts[-1] since we've
        # already verified parts[0] is listname.  The basic rule is if the
        # post URL (action) is a directory, it must be slash terminated, but
        # not if it's a file.  Otherwise, relative links in the target archive
        # page don't work.
        if true_filename.endswith('/index.html') and parts[-1] != 'index.html':
            action += SLASH
        # Escape web input parameter to avoid cross-site scripting.
        print(Utils.maketext(
            'private.html',
            {'action'  : Utils.websafe(action),
             'realname': mlist.real_name,
             'message' : message,
             }, mlist=mlist))
        return

    lang = mlist.getMemberLanguage(username)
    i18n.set_language(lang)
    doc.set_language(lang)

    # Authorization confirmed... output the desired file
    try:
        ctype, enc = guess_type(path, strict=0)
        if ctype is None:
            ctype = 'text/html'
        if mboxfile:
            f = open(os.path.join(mlist.archive_dir() + '.mbox',
                                  mlist.internal_name() + '.mbox'))
            ctype = 'text/plain'
        elif true_filename.endswith('.gz'):
            import gzip
            f = gzip.open(true_filename, 'r')
        else:
            f = open(true_filename, 'r')
    except IOError:
        msg = _('Private archive file not found')
        doc.SetTitle(msg)
        doc.AddItem(Header(2, msg))
        print('Status: 404 Not Found')
        print(doc.Format())
        syslog('error', 'Private archive file not found: %s', true_filename)
    else:
        print('Content-type: %s\n' % ctype)
        sys.stdout.write(f.read())
        f.close()
示例#43
0
    def GetConfigInfo(self, mlist, category, subcat=None):
        if category <> 'nondigest':
            return None
        WIDTH = mm_cfg.TEXTFIELDWIDTH

        info = [
            _("Policies concerning immediately delivered list traffic."),

            ('nondigestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1,
             _("""Can subscribers choose to receive mail immediately, rather
             than in batched digests?""")),
            ]

        if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION:
            info.extend([
                ('personalize', mm_cfg.Radio,
                 (_('No'), _('Yes'), _('Full Personalization')), 1,

                 _('''Should Mailman personalize each non-digest delivery?
                 This is often useful for announce-only lists, but <a
                 href="?VARHELP=nondigest/personalize">read the details</a>
                 section for a discussion of important performance
                 issues.'''),

                 _("""Normally, Mailman sends the regular delivery messages to
                 the mail server in batches.  This is much more efficent
                 because it reduces the amount of traffic between Mailman and
                 the mail server.

                 <p>However, some lists can benefit from a more personalized
                 approach.  In this case, Mailman crafts a new message for
                 each member on the regular delivery list.  Turning this
                 feature on may degrade the performance of your site, so you
                 need to carefully consider whether the trade-off is worth it,
                 or whether there are other ways to accomplish what you want.
                 You should also carefully monitor your system load to make
                 sure it is acceptable.

                 <p>Select <em>No</em> to disable personalization and send
                 messages to the members in batches.  Select <em>Yes</em> to
                 personalize deliveries and allow additional substitution
                 variables in message headers and footers (see below).  In
                 addition, by selecting <em>Full Personalization</em>, the
                 <code>To</code> header of posted messages will be modified to
                 include the member's address instead of the list's posting
                 address.

                 <p>When personalization is enabled, a few more expansion
                 variables can be included in the <a
                 href="?VARHELP=nondigest/msg_header">message header</a> and
                 <a href="?VARHELP=nondigest/msg_footer">message footer</a>.

                 <p>These additional substitution variables will be available
                 for your headers and footers, when this feature is enabled:

                 <ul><li><b>user_address</b> - The address of the user,
                         coerced to lower case.
                     <li><b>user_delivered_to</b> - The case-preserved address
                         that the user is subscribed with.
                     <li><b>user_password</b> - The user's password.
                     <li><b>user_name</b> - The user's full name.
                     <li><b>user_optionsurl</b> - The url to the user's option
                         page.
                 </ul>
                 """))
                ])
        # BAW: for very dumb reasons, we want the `personalize' attribute to
        # show up before the msg_header and msg_footer attrs, otherwise we'll
        # get a bogus warning if the header/footer contains a personalization
        # substitution variable, and we're transitioning from no
        # personalization to personalization enabled.
        headfoot = Utils.maketext('headfoot.html', mlist=mlist, raw=1)
        if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION:
            extra = _("""\
When <a href="?VARHELP=nondigest/personalize">personalization</a> is enabled
for this list, additional substitution variables are allowed in your headers
and footers:

<ul><li><b>user_address</b> - The address of the user,
        coerced to lower case.
    <li><b>user_delivered_to</b> - The case-preserved address
        that the user is subscribed with.
    <li><b>user_password</b> - The user's password.
    <li><b>user_name</b> - The user's full name.
    <li><b>user_optionsurl</b> - The url to the user's option
        page.
</ul>
""")
        else:
            extra = ''

        info.extend([('msg_header', mm_cfg.Text, (10, WIDTH), 0,
             _('Header added to mail sent to regular list members'),
             _('''Text prepended to the top of every immediately-delivery
             message. ''') + headfoot + extra),

            ('msg_footer', mm_cfg.Text, (10, WIDTH), 0,
             _('Footer added to mail sent to regular list members'),
             _('''Text appended to the bottom of every immediately-delivery
             message. ''') + headfoot + extra),
            ])

        info.extend([
            ('scrub_nondigest', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
             _('Scrub attachments of regular delivery message?'),
             _('''When you scrub attachments, they are stored in the archive
             area and links are made in the message so that the member can
             access them via a web browser. If you want the attachments to
             totally disappear, you can use content filtering options.''')),
            ])

        info.extend([
            _('Sibling lists'),

            ('regular_exclude_lists', mm_cfg.EmailList, (3, WIDTH), 0,
             _("""Other mailing lists on this site whose members are
             excluded from the regular (non-digest) delivery if those
             list addresses appear in a To: or Cc: header."""),
             _("""The list addresses should be written in full mail address
             format (e.g. [email protected]). Do not specify this list
             address mutually in the exclude list configuration page of the
             other list, or members of both lists won't get any message.
             Note also that the site administrator may prohibit cross domain
             siblings.""")),

            ('regular_exclude_ignore', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
             _("""Ignore regular_exclude_lists of which the poster is not a
             member."""),
             _("""If a post is addressed to this list and to one or more of
             the exclude lists, regular members of those lists will not be
             sent the post from this list, but if the poster is not a member
             of an excluded list, the post may not be accepted by that list
             which leaves the members of that list with no copy of the post.
             Setting this to Yes ignores any of the exclude lists of which the
             poster is not a member.""")),

            ('regular_include_lists', mm_cfg.EmailList, (3, WIDTH), 0,
             _("""Other mailing lists on this site whose members are
             included in the regular (non-digest) delivery if those
             list addresses don't appear in a To: or Cc: header."""),
             _("""The list addresses should be written in full mail address
             format (e.g. [email protected]). Note also that the site
             administrator may prohibit cross domain siblings.""")),
            ])


        return info
示例#44
0
    def GetConfigInfo(self, mlist, category, subcat=None):
        if category <> "digest":
            return None
        WIDTH = mm_cfg.TEXTFIELDWIDTH

        info = [
            _("Batched-delivery digest characteristics."),
            (
                "digestable",
                mm_cfg.Toggle,
                (_("No"), _("Yes")),
                1,
                _("Can list members choose to receive list traffic " "bunched in digests?"),
            ),
            (
                "digest_is_default",
                mm_cfg.Radio,
                (_("Regular"), _("Digest")),
                0,
                _("Which delivery mode is the default for new users?"),
            ),
            (
                "mime_is_default_digest",
                mm_cfg.Radio,
                (_("Plain"), _("MIME")),
                0,
                _("When receiving digests, which format is default?"),
            ),
            (
                "digest_size_threshhold",
                mm_cfg.Number,
                3,
                0,
                _("How big in Kb should a digest be before it gets sent out?"),
            ),
            # Should offer a 'set to 0' for no size threshhold.
            (
                "digest_send_periodic",
                mm_cfg.Radio,
                (_("No"), _("Yes")),
                1,
                _("Should a digest be dispatched daily when the size threshold " "isn't reached?"),
            ),
            (
                "digest_header",
                mm_cfg.Text,
                (4, WIDTH),
                0,
                _("Header added to every digest"),
                _("Text attached (as an initial message, before the table" " of contents) to the top of digests. ")
                + Utils.maketext("headfoot.html", raw=1, mlist=mlist),
            ),
            (
                "digest_footer",
                mm_cfg.Text,
                (4, WIDTH),
                0,
                _("Footer added to every digest"),
                _("Text attached (as a final message) to the bottom of digests. ")
                + Utils.maketext("headfoot.html", raw=1, mlist=mlist),
            ),
            (
                "digest_volume_frequency",
                mm_cfg.Radio,
                (_("Yearly"), _("Monthly"), _("Quarterly"), _("Weekly"), _("Daily")),
                0,
                _("How often should a new digest volume be started?"),
                _(
                    """When a new digest volume is started, the volume number is
             incremented and the issue number is reset to 1."""
                ),
            ),
            (
                "_new_volume",
                mm_cfg.Toggle,
                (_("No"), _("Yes")),
                0,
                _("Should Mailman start a new digest volume?"),
                _(
                    """Setting this option instructs Mailman to start a new volume
             with the next digest sent out."""
                ),
            ),
            (
                "_send_digest_now",
                mm_cfg.Toggle,
                (_("No"), _("Yes")),
                0,
                _(
                    """Should Mailman send the next digest right now, if it is not
             empty?"""
                ),
            ),
        ]

        ##        if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION:
        ##            info.extend([
        ##                ('digest_personalize', mm_cfg.Toggle, (_('No'), _('Yes')), 1,

        ##                 _('''Should Mailman personalize each digest delivery?
        ##                 This is often useful for announce-only lists, but <a
        ##                 href="?VARHELP=digest/digest_personalize">read the details</a>
        ##                 section for a discussion of important performance
        ##                 issues.'''),

        ##                 _("""Normally, Mailman sends the digest messages to
        ##                 the mail server in batches.  This is much more efficent
        ##                 because it reduces the amount of traffic between Mailman and
        ##                 the mail server.

        ##                 <p>However, some lists can benefit from a more personalized
        ##                 approach.  In this case, Mailman crafts a new message for
        ##                 each member on the digest delivery list.  Turning this on
        ##                 adds a few more expansion variables that can be included in
        ##                 the <a href="?VARHELP=digest/digest_header">message header</a>
        ##                 and <a href="?VARHELP=digest/digest_footer">message footer</a>
        ##                 but it may degrade the performance of your site as
        ##                 a whole.

        ##                 <p>You need to carefully consider whether the trade-off is
        ##                 worth it, or whether there are other ways to accomplish what
        ##                 you want.  You should also carefully monitor your system load
        ##                 to make sure it is acceptable.

        ##                 <p>These additional substitution variables will be available
        ##                 for your headers and footers, when this feature is enabled:

        ##                 <ul><li><b>user_address</b> - The address of the user,
        ##                         coerced to lower case.
        ##                     <li><b>user_delivered_to</b> - The case-preserved address
        ##                         that the user is subscribed with.
        ##                     <li><b>user_password</b> - The user's password.
        ##                     <li><b>user_name</b> - The user's full name.
        ##                     <li><b>user_optionsurl</b> - The url to the user's option
        ##                         page.
        ##                 """))
        ##                ])

        return info
示例#45
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)
示例#46
0
    def GetConfigInfo(self, mlist, category, subcat=None):
        if category <> 'nondigest':
            return None
        WIDTH = mm_cfg.TEXTFIELDWIDTH

        info = [
            _("Policies concerning immediately delivered list traffic."),
            ('nondigestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1,
             _("""Can subscribers choose to receive mail immediately, rather
             than in batched digests?""")),
        ]

        if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION:
            info.extend([
                ('personalize', mm_cfg.Radio, (_('No'), _('Yes'),
                                               _('Full Personalization')), 1,
                 _('''Should Mailman personalize each non-digest delivery?
                 This is often useful for announce-only lists, but <a
                 href="?VARHELP=nondigest/personalize">read the details</a>
                 section for a discussion of important performance
                 issues.'''),
                 _("""Normally, Mailman sends the regular delivery messages to
                 the mail server in batches.  This is much more efficent
                 because it reduces the amount of traffic between Mailman and
                 the mail server.

                 <p>However, some lists can benefit from a more personalized
                 approach.  In this case, Mailman crafts a new message for
                 each member on the regular delivery list.  Turning this
                 feature on may degrade the performance of your site, so you
                 need to carefully consider whether the trade-off is worth it,
                 or whether there are other ways to accomplish what you want.
                 You should also carefully monitor your system load to make
                 sure it is acceptable.

                 <p>Select <em>No</em> to disable personalization and send
                 messages to the members in batches.  Select <em>Yes</em> to
                 personalize deliveries and allow additional substitution
                 variables in message headers and footers (see below).  In
                 addition, by selecting <em>Full Personalization</em>, the
                 <code>To</code> header of posted messages will be modified to
                 include the member's address instead of the list's posting
                 address.

                 <p>When personalization is enabled, a few more expansion
                 variables can be included in the <a
                 href="?VARHELP=nondigest/msg_header">message header</a> and
                 <a href="?VARHELP=nondigest/msg_footer">message footer</a>.

                 <p>These additional substitution variables will be available
                 for your headers and footers, when this feature is enabled:

                 <ul><li><b>user_address</b> - The address of the user,
                         coerced to lower case.
                     <li><b>user_delivered_to</b> - The case-preserved address
                         that the user is subscribed with.
                     <li><b>user_password</b> - The user's password.
                     <li><b>user_name</b> - The user's full name.
                     <li><b>user_optionsurl</b> - The url to the user's option
                         page.
                 </ul>
                 """))
            ])
        # BAW: for very dumb reasons, we want the `personalize' attribute to
        # show up before the msg_header and msg_footer attrs, otherwise we'll
        # get a bogus warning if the header/footer contains a personalization
        # substitution variable, and we're transitioning from no
        # personalization to personalization enabled.
        headfoot = Utils.maketext('headfoot.html', mlist=mlist, raw=1)
        if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION:
            extra = _("""\
When <a href="?VARHELP=nondigest/personalize">personalization</a> is enabled
for this list, additional substitution variables are allowed in your headers
and footers:

<ul><li><b>user_address</b> - The address of the user,
        coerced to lower case.
    <li><b>user_delivered_to</b> - The case-preserved address
        that the user is subscribed with.
    <li><b>user_password</b> - The user's password.
    <li><b>user_name</b> - The user's full name.
    <li><b>user_optionsurl</b> - The url to the user's option
        page.
</ul>
""")
        else:
            extra = ''

        info.extend([
            ('msg_header', mm_cfg.Text, (10, WIDTH), 0,
             _('Header added to mail sent to regular list members'),
             _('''Text prepended to the top of every immediately-delivery
             message. ''') + headfoot + extra),
            ('msg_footer', mm_cfg.Text, (10, WIDTH), 0,
             _('Footer added to mail sent to regular list members'),
             _('''Text appended to the bottom of every immediately-delivery
             message. ''') + headfoot + extra),
        ])

        info.extend([
            ('scrub_nondigest', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
             _('Scrub attachments of regular delivery message?'),
             _('''When you scrub attachments, they are stored in the archive
             area and links are made in the message so that the member can
             access them via a web browser. If you want the attachments to
             totally disappear, you can use content filtering options.''')),
        ])

        info.extend([
            _('Sibling lists'),
            ('regular_exclude_lists', mm_cfg.EmailList, (3, WIDTH), 0,
             _("""Other mailing lists on this site whose members are
             excluded from the regular (non-digest) delivery if those
             list addresses appear in a To: or Cc: header."""),
             _("""The list addresses should be written in full mail address
             format (e.g. [email protected]). Do not specify this list
             address mutually in the exclude list configuration page of the
             other list, or members of both lists won't get any message.
             Note also that the site administrator may prohibit cross domain
             siblings.""")),
            ('regular_exclude_ignore', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
             _("""Ignore regular_exclude_lists of which the poster is not a
             member."""),
             _("""If a post is addressed to this list and to one or more of
             the exclude lists, regular members of those lists will not be
             sent the post from this list, but if the poster is not a member
             of an excluded list, the post may not be accepted by that list
             which leaves the members of that list with no copy of the post.
             Setting this to Yes ignores any of the exclude lists of which the
             poster is not a member.""")),
            ('regular_include_lists', mm_cfg.EmailList, (3, WIDTH), 0,
             _("""Other mailing lists on this site whose members are
             included in the regular (non-digest) delivery if those
             list addresses don't appear in a To: or Cc: header."""),
             _("""The list addresses should be written in full mail address
             format (e.g. [email protected]). Note also that the site
             administrator may prohibit cross domain siblings.""")),
        ])

        return info
示例#47
0
class Archiver:
    #
    # Interface to Pipermail.  HyperArch.py uses this method to get the
    # archive directory for the mailing list
    #
    def InitVars(self):
        # Configurable
        self.archive = mm_cfg.DEFAULT_ARCHIVE
        # 0=public, 1=private:
        self.archive_private = mm_cfg.DEFAULT_ARCHIVE_PRIVATE
        self.archive_volume_frequency = \
                mm_cfg.DEFAULT_ARCHIVE_VOLUME_FREQUENCY
        # The archive file structure by default is:
        #
        # archives/
        #     private/
        #         listname.mbox/
        #             listname.mbox
        #         listname/
        #             lots-of-pipermail-stuff
        #     public/
        #         listname.mbox@ -> ../private/listname.mbox
        #         listname@ -> ../private/listname
        #
        # IOW, the mbox and pipermail archives are always stored in the
        # private archive for the list.  This is safe because archives/private
        # is always set to o-rx.  Public archives have a symlink to get around
        # the private directory, pointing directly to the private/listname
        # which has o+rx permissions.  Private archives do not have the
        # symbolic links.
        omask = os.umask(0)
        try:
            try:
                os.mkdir(self.archive_dir() + '.mbox', 02775)
            except OSError, e:
                if e.errno <> errno.EEXIST: raise
                # We also create an empty pipermail archive directory into
                # which we'll drop an empty index.html file into.  This is so
                # that lists that have not yet received a posting have
                # /something/ as their index.html, and don't just get a 404.
            try:
                os.mkdir(self.archive_dir(), 02775)
            except OSError, e:
                if e.errno <> errno.EEXIST: raise
            # See if there's an index.html file there already and if not,
            # write in the empty archive notice.
            indexfile = os.path.join(self.archive_dir(), 'index.html')
            fp = None
            try:
                fp = open(indexfile)
            except IOError, e:
                if e.errno <> errno.ENOENT: raise
                omask = os.umask(002)
                try:
                    fp = open(indexfile, 'w')
                finally:
                    os.umask(omask)
                fp.write(
                    Utils.maketext(
                        'emptyarchive.html', {
                            'listname': self.real_name,
                            'listinfo': self.GetScriptURL('listinfo',
                                                          absolute=1),
                        },
                        mlist=self))
示例#48
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
     msg.send(self)
     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)
示例#49
0
            action += '.mbox'
        if parts[1:]:
            action = os.path.join(action, SLASH.join(parts[1:]))
        # If we added '/index.html' to true_filename, add a slash to the URL.
        # We need this because we no longer add the trailing slash in the
        # private.html template.  It's always OK to test parts[-1] since we've
        # already verified parts[0] is listname.  The basic rule is if the
        # post URL (action) is a directory, it must be slash terminated, but
        # not if it's a file.  Otherwise, relative links in the target archive
        # page don't work.
        if true_filename.endswith('/index.html') and parts[-1] <> 'index.html':
            action += SLASH
        # Escape web input parameter to avoid cross-site scripting.
        print Utils.maketext('private.html', {
            'action': Utils.websafe(action),
            'realname': mlist.real_name,
            'message': message,
        },
                             mlist=mlist)
        return

    lang = mlist.getMemberLanguage(username)
    i18n.set_language(lang)
    doc.set_language(lang)

    # Authorization confirmed... output the desired file
    try:
        ctype, enc = guess_type(path, strict=0)
        if ctype is None:
            ctype = 'text/html'
        if mboxfile:
            f = open(
示例#50
0
    def HandleBouncingAddress(self, addr, msg):
        """Disable or remove addr according to bounce_action setting."""
        if self.automatic_bounce_action == 0:
            return
        elif self.automatic_bounce_action == 1:
	    # Only send if call works ok.
            (succeeded, send) = self.DisableBouncingAddress(addr)
            did = "disabled"
        elif self.automatic_bounce_action == 2:
            (succeeded, send) = self.DisableBouncingAddress(addr)
            did = "disabled"
	    # Never send.
            send = 0
        elif self.automatic_bounce_action == 3:
            (succeeded, send) = self.RemoveBouncingAddress(addr)
	    # Always send.
            send = 1
            did = "removed"
        if send:
            if succeeded != 1:
                negative="not "
            else:
                negative=""
            recipient = self.GetAdminEmail()
            if addr in self.owner + [recipient]:
                # Whoops!  This is a bounce of a bounce notice - do not
                # perpetuate the bounce loop!  Log it prominently and be
                # satisfied with that.
                syslog("error",
                       "%s: Bounce recipient loop"
                       " encountered!\n\t%s\n\tBad admin recipient: %s" %
                       (self.internal_name(),
                        "(Ie, bounce notification addr, itself, bounces.)",
                        addr))
                return
            import mimetools
            boundary = mimetools.choose_boundary()
            # report about success
            but = ''
            if succeeded <> 1:
                but = 'BUT:        %s' % succeeded
            # disabled?
            if did == 'disabled' and succeeded == 1:
                reenable = Utils.maketext(
                    'reenable.txt',
                    {'admin_url': self.GetScriptURL('admin', absolute=1),
                     })
            else:
                reenable = ''
            # the mail message text
            text = Utils.maketext(
                'bounce.txt',
                {'boundary' : boundary,
                 'listname' : self.real_name,
                 'addr'     : addr,
                 'negative' : negative,
                 'did'      : did,
                 'but'      : but,
                 'reenable' : reenable,
                 'owneraddr': mm_cfg.MAILMAN_OWNER,
                 })
            # add this here so it doesn't get wrapped/filled
            text = text + '\n\n--' + boundary + \
                   '\nContent-type: text/plain; charset=us-ascii\n'

            # We do this here so this text won't be wrapped.  note that
            # 'bounce.txt' has a trailing newline.  Be robust about getting
            # the body of the message.
            body = '[original message unavailable]'
            try:
                body = msg.body
            except AttributeError:
                try:
                    msg.rewindbody()
                    body = msg.fp.read()
                except IOError:
                    pass
            text = text + \
                   string.join(msg.headers, '') + '\n' + \
                   Utils.QuotePeriods(body) + '\n' + \
                   '--' + boundary + '--'

            if negative:
                negative = string.upper(negative)

            # send the bounce message
            msg = Message.UserNotification(
                recipient, mm_cfg.MAILMAN_OWNER,
                '%s member %s bouncing - %s%s' % (self.real_name, addr,
                                                  negative, did),
                text)
            msg['MIME-Version'] = '1.0'
            msg['Content-Type'] = 'multipart/mixed; boundary="%s"' % boundary
            HandlerAPI.DeliverToUser(self, msg)
示例#51
0
def main():
    global ssort
    # Figure out which list is being requested
    parts = Utils.GetPathPieces()
    if not parts:
        handle_no_list()
        return

    listname = parts[0].lower()
    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError as e:
        # Avoid cross-site scripting attacks
        safelistname = Utils.websafe(listname)
        # Send this with a 404 status.
        print('Status: 404 Not Found')
        handle_no_list(_('No such list <em>%(safelistname)s</em>'))
        syslog('error', 'admindb: No such list "%s": %s\n', listname, e)
        return

    # Now that we know which list to use, set the system's language to it.
    i18n.set_language(mlist.preferred_language)

    # Make sure the user is authorized to see this page.
    cgidata = cgi.FieldStorage(keep_blank_values=1)
    try:
        cgidata.getfirst('adminpw', '')
    except TypeError:
        # Someone crafted a POST with a bad Content-Type:.
        doc = Document()
        doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
        doc.AddItem(Header(2, _("Error")))
        doc.AddItem(Bold(_('Invalid options to CGI script.')))
        # Send this with a 400 status.
        print('Status: 400 Bad Request')
        print(doc.Format())
        return

    # CSRF check
    safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details']
    params = list(cgidata.keys())
    if set(params) - set(safe_params):
        csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'))
    else:
        csrf_checked = True
    # if password is present, void cookie to force password authentication.
    if cgidata.getfirst('adminpw'):
        os.environ['HTTP_COOKIE'] = ''
        csrf_checked = True

    if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
                                  mm_cfg.AuthListModerator,
                                  mm_cfg.AuthSiteAdmin),
                                 cgidata.getfirst('adminpw', '')):
        if 'adminpw' in cgidata:
            # This is a re-authorization attempt
            msg = Bold(FontSize('+1', _('Authorization failed.'))).Format()
            remote = os.environ.get('HTTP_FORWARDED_FOR',
                     os.environ.get('HTTP_X_FORWARDED_FOR',
                     os.environ.get('REMOTE_ADDR',
                                    'unidentified origin')))
            syslog('security',
                   'Authorization failed (admindb): list=%s: remote=%s',
                   listname, remote)
        else:
            msg = ''
        Auth.loginpage(mlist, 'admindb', msg=msg)
        return

    # Add logout function. Note that admindb may be accessed with
    # site-wide admin, moderator and list admin privileges.
    # site admin may have site or admin cookie. (or both?)
    # See if this is a logout request
    if len(parts) >= 2 and parts[1] == 'logout':
        if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site':
            print(mlist.ZapCookie(mm_cfg.AuthSiteAdmin))
        if mlist.AuthContextInfo(mm_cfg.AuthListModerator)[0]:
            print(mlist.ZapCookie(mm_cfg.AuthListModerator))
        print(mlist.ZapCookie(mm_cfg.AuthListAdmin))
        Auth.loginpage(mlist, 'admindb', frontpage=1)
        return

    # Set up the results document
    doc = Document()
    doc.set_language(mlist.preferred_language)

    # See if we're requesting all the messages for a particular sender, or if
    # we want a specific held message.
    sender = None
    msgid = None
    details = None
    envar = os.environ.get('QUERY_STRING')
    if envar:
        # POST methods, even if their actions have a query string, don't get
        # put into FieldStorage's keys :-(
        qs = cgi.parse_qs(envar).get('sender')
        if qs and isinstance(qs, list):
            sender = qs[0]
        qs = cgi.parse_qs(envar).get('msgid')
        if qs and isinstance(qs,list):
            msgid = qs[0]
        qs = cgi.parse_qs(envar).get('details')
        if qs and isinstance(qs, list):
            details = qs[0]

    # We need a signal handler to catch the SIGTERM that can come from Apache
    # when the user hits the browser's STOP button.  See the comment in
    # admin.py for details.
    #
    # BAW: Strictly speaking, the list should not need to be locked just to
    # read the request database.  However the request database asserts that
    # the list is locked in order to load it and it's not worth complicating
    # that logic.
    def sigterm_handler(signum, frame, mlist=mlist):
        # Make sure the list gets unlocked...
        mlist.Unlock()
        # ...and ensure we exit, otherwise race conditions could cause us to
        # enter MailList.Save() while we're in the unlocked state, and that
        # could be bad!
        sys.exit(0)

    mlist.Lock()
    try:
        # Install the emergency shutdown signal handler
        signal.signal(signal.SIGTERM, sigterm_handler)

        realname = mlist.real_name
        if not list(cgidata.keys()) or 'admlogin' in cgidata:
            # If this is not a form submission (i.e. there are no keys in the
            # form) or it's a login, then we don't need to do much special.
            doc.SetTitle(_('%(realname)s Administrative Database'))
        elif not details:
            # This is a form submission
            doc.SetTitle(_('%(realname)s Administrative Database Results'))
            if csrf_checked:
                process_form(mlist, doc, cgidata)
            else:
                doc.addError(
                    _('The form lifetime has expired. (request forgery check)'))
        # Now print the results and we're done.  Short circuit for when there
        # are no pending requests, but be sure to save the results!
        admindburl = mlist.GetScriptURL('admindb', absolute=1)
        if not mlist.NumRequestsPending():
            title = _('%(realname)s Administrative Database')
            doc.SetTitle(title)
            doc.AddItem(Header(2, title))
            doc.AddItem(_('There are no pending requests.'))
            doc.AddItem(' ')
            doc.AddItem(Link(admindburl,
                             _('Click here to reload this page.')))
            # Put 'Logout' link before the footer
            doc.AddItem('\n<div align="right"><font size="+2">')
            doc.AddItem(Link('%s/logout' % admindburl,
                '<b>%s</b>' % _('Logout')))
            doc.AddItem('</font></div>\n')
            doc.AddItem(mlist.GetMailmanFooter())
            print(doc.Format())
            mlist.Save()
            return

        form = Form(admindburl, mlist=mlist, contexts=AUTH_CONTEXTS)
        # Add the instructions template
        if details == 'instructions':
            doc.AddItem(Header(
                2, _('Detailed instructions for the administrative database')))
        else:
            doc.AddItem(Header(
                2,
                _('Administrative requests for mailing list:')
                + ' <em>%s</em>' % mlist.real_name))
        if details != 'instructions':
            form.AddItem(Center(SubmitButton('submit', _('Submit All Data'))))
        nomessages = not mlist.GetHeldMessageIds()
        if not (details or sender or msgid or nomessages):
            form.AddItem(Center(
                '<label>' +
                CheckBox('discardalldefersp', 0).Format() +
                '&nbsp;' +
                _('Discard all messages marked <em>Defer</em>') +
                '</label>'
                ))
        # Add a link back to the overview, if we're not viewing the overview!
        adminurl = mlist.GetScriptURL('admin', absolute=1)
        d = {'listname'  : mlist.real_name,
             'detailsurl': admindburl + '?details=instructions',
             'summaryurl': admindburl,
             'viewallurl': admindburl + '?details=all',
             'adminurl'  : adminurl,
             'filterurl' : adminurl + '/privacy/sender',
             }
        addform = 1
        if sender:
            esender = Utils.websafe(sender)
            d['description'] = _("all of %(esender)s's held messages.")
            doc.AddItem(Utils.maketext('admindbpreamble.html', d,
                                       raw=1, mlist=mlist))
            show_sender_requests(mlist, form, sender)
        elif msgid:
            d['description'] = _('a single held message.')
            doc.AddItem(Utils.maketext('admindbpreamble.html', d,
                                       raw=1, mlist=mlist))
            show_message_requests(mlist, form, msgid)
        elif details == 'all':
            d['description'] = _('all held messages.')
            doc.AddItem(Utils.maketext('admindbpreamble.html', d,
                                       raw=1, mlist=mlist))
            show_detailed_requests(mlist, form)
        elif details == 'instructions':
            doc.AddItem(Utils.maketext('admindbdetails.html', d,
                                       raw=1, mlist=mlist))
            addform = 0
        else:
            # Show a summary of all requests
            doc.AddItem(Utils.maketext('admindbsummary.html', d,
                                       raw=1, mlist=mlist))
            num = show_pending_subs(mlist, form)
            num += show_pending_unsubs(mlist, form)
            num += show_helds_overview(mlist, form, ssort)
            addform = num > 0
        # Finish up the document, adding buttons to the form
        if addform:
            doc.AddItem(form)
            form.AddItem('<hr>')
            if not (details or sender or msgid or nomessages):
                form.AddItem(Center(
                    '<label>' +
                    CheckBox('discardalldefersp', 0).Format() +
                    '&nbsp;' +
                    _('Discard all messages marked <em>Defer</em>') +
                    '</label>'
                    ))
            form.AddItem(Center(SubmitButton('submit', _('Submit All Data'))))
        # Put 'Logout' link before the footer
        doc.AddItem('\n<div align="right"><font size="+2">')
        doc.AddItem(Link('%s/logout' % admindburl,
            '<b>%s</b>' % _('Logout')))
        doc.AddItem('</font></div>\n')
        doc.AddItem(mlist.GetMailmanFooter())
        print(doc.Format())
        # Commit all changes
        mlist.Save()
    finally:
        mlist.Unlock()