示例#1
0
 def process_message(self, peer, mailfrom, rcpttos, data):
     from io import StringIO
     from Mailman import Utils
     from Mailman import Message
     from Mailman import MailList
     # If the message is to a Mailman mailing list, then we'll invoke the
     # Mailman script directly, without going through the real smtpd.
     # Otherwise we'll forward it to the local proxy for disposition.
     listnames = []
     for rcpt in rcpttos:
         local = rcpt.lower().split('@')[0]
         # We allow the following variations on the theme
         #   listname
         #   listname-admin
         #   listname-owner
         #   listname-request
         #   listname-join
         #   listname-leave
         parts = local.split('-')
         if len(parts) > 2:
             continue
         listname = parts[0]
         if len(parts) == 2:
             command = parts[1]
         else:
             command = ''
         if not Utils.list_exists(listname) or command not in (
                 '', 'admin', 'owner', 'request', 'join', 'leave'):
             continue
         listnames.append((rcpt, listname, command))
     # Remove all list recipients from rcpttos and forward what we're not
     # going to take care of ourselves.  Linear removal should be fine
     # since we don't expect a large number of recipients.
     for rcpt, listname, command in listnames:
         rcpttos.remove(rcpt)
     # If there's any non-list destined recipients left,
     print('forwarding recips:', ' '.join(rcpttos), file=DEBUGSTREAM)
     if rcpttos:
         refused = self._deliver(mailfrom, rcpttos, data)
         # TBD: what to do with refused addresses?
         print('we got refusals:', refused, file=DEBUGSTREAM)
     # Now deliver directly to the list commands
     mlists = {}
     s = StringIO(data)
     msg = Message.Message(s)
     # These headers are required for the proper execution of Mailman.  All
     # MTAs in existence seem to add these if the original message doesn't
     # have them.
     if not msg.get('from'):
         msg['From'] = mailfrom
     if not msg.get('date'):
         msg['Date'] = time.ctime(time.time())
     for rcpt, listname, command in listnames:
         print('sending message to', rcpt, file=DEBUGSTREAM)
         mlist = mlists.get(listname)
         if not mlist:
             mlist = MailList.MailList(listname, lock=0)
             mlists[listname] = mlist
         # dispatch on the type of command
         if command == '':
             # post
             msg.Enqueue(mlist, tolist=1)
         elif command == 'admin':
             msg.Enqueue(mlist, toadmin=1)
         elif command == 'owner':
             msg.Enqueue(mlist, toowner=1)
         elif command == 'request':
             msg.Enqueue(mlist, torequest=1)
         elif command in ('join', 'leave'):
             # TBD: this is a hack!
             if command == 'join':
                 msg['Subject'] = 'subscribe'
             else:
                 msg['Subject'] = 'unsubscribe'
             msg.Enqueue(mlist, torequest=1)
示例#2
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()
示例#3
0
from Mailman.ListAdmin import readMessage
from email.Iterators import typed_subpart_iterator

uid = getpwnam(mm_cfg.MAILMAN_USER)[2]
gid = getgrnam(mm_cfg.MAILMAN_GROUP)[2]

if not os.getuid():
    os.setregid(gid, gid)
    os.setreuid(uid, uid)

if (os.getuid() is not uid) or (os.getgid() is not gid):
    sys.exit(0)

for listname in Utils.list_names():
    try:
        mlist = MailList.MailList(listname, lock=0)
    except:
        print 'ERROR for ' + listname
        continue
    try:
        mlist.Lock()

        ############################################
        # do treatement here
        ############################################

        mlist.Save()
        mlist.Unlock()
        print 'OK    for ' + listname
    except:
        print 'ERROR for ' + listname
示例#4
0
文件: utils.py 项目: fser/mailman-api
 def change_list_attribute(self, attribute, value):
     mlist = MailList.MailList(self.list_name)
     setattr(mlist, attribute, value)
     mlist.Save()
     mlist.Unlock()
示例#5
0
def get_mailinglist(listname, lock=True):
    try:
        return MailList.MailList(listname, lock=lock)
    except Errors.MMUnknownListError:
        raise jsonify("Unknown Mailing List `{}`.".format(listname), 404)
def main():
    doc = Document()
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    parts = Utils.GetPathPieces()
    if not parts:
        doc.AddItem(Header(2, _("Error")))
        doc.AddItem(Bold(_('Invalid options to CGI script')))
        print(doc.Format())
        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)
        doc.AddItem(Header(2, _("Error")))
        doc.AddItem(Bold(_('No such list <em>%(safelistname)s</em>')))
        # Send this with a 404 status.
        print('Status: 404 Not Found')
        print(doc.Format())
        syslog('error', 'subscribe: No such list "%s": %s\n', listname, e)
        return

    # See if the form data has a preferred language set, in which case, use it
    # for the results.  If not, use the list's preferred language.
    cgidata = cgi.FieldStorage()
    try:
        language = cgidata.getfirst('language', '')
    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
    if not Utils.IsLanguage(language):
        language = mlist.preferred_language
    i18n.set_language(language)
    doc.set_language(language)

    # 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)

        process_form(mlist, doc, cgidata, language)
        mlist.Save()
    finally:
        mlist.Unlock()
示例#7
0
def listinfo_overview(msg=''):
    # Present the general listinfo overview
    hostname = Utils.get_domain()
    # Set up the document and assign it the correct language.  The only one we
    # know about at the moment is the server's default.
    doc = Document()
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    legend = _("%(hostname)s Mailing Lists")
    doc.SetTitle(legend)

    table = Table(border=0, width="100%")
    table.AddRow([Center(Header(2, legend))])
    table.AddCellInfo(table.GetCurrentRowIndex(),
                      0,
                      colspan=2,
                      bgcolor=mm_cfg.WEB_HEADER_COLOR)

    # Skip any mailing lists that isn't advertised.
    advertised = []
    listnames = Utils.list_names()
    listnames.sort()

    for name in listnames:
        try:
            mlist = MailList.MailList(name, lock=0)
        except Errors.MMUnknownListError:
            # The list could have been deleted by another process.
            continue
        if mlist.advertised:
            if mm_cfg.VIRTUAL_HOST_OVERVIEW and (
                    mlist.web_page_url.find('/%s/' % hostname) == -1
                    and mlist.web_page_url.find('/%s:' % hostname) == -1):
                # List is for different identity of this host - skip it.
                continue
            else:
                advertised.append(
                    (mlist.GetScriptURL('listinfo'), mlist.real_name,
                     Utils.websafe(mlist.description)))
    if msg:
        greeting = FontAttr(msg, color="ff5060", size="+1")
    else:
        greeting = FontAttr(_('Welcome!'), size='+2')

    welcome = [greeting]
    mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format()
    if not advertised:
        welcome.extend(
            _('''<p>There currently are no publicly-advertised
            %(mailmanlink)s mailing lists on %(hostname)s.'''))
    else:
        welcome.append(
            _('''<p>Below is a listing of all the public mailing lists on
            %(hostname)s.  Click on a list name to get more information about
            the list, or to subscribe, unsubscribe, and change the preferences
            on your subscription.'''))

    # set up some local variables
    adj = msg and _('right') or ''
    siteowner = Utils.get_site_email()
    welcome.extend(
        (_(''' To visit the general information page for an unadvertised list,
        open a URL similar to this one, but with a '/' and the %(adj)s
        list name appended.
        <p>List administrators, you can visit '''),
         Link(Utils.ScriptURL('admin'), _('the list admin overview page')),
         _(''' to find the management interface for your list.
         <p>If you are having trouble using the lists, please contact '''),
         Link('mailto:' + siteowner, siteowner), '.<p>'))

    table.AddRow([apply(Container, welcome)])
    table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2)

    if advertised:
        table.AddRow(['&nbsp;', '&nbsp;'])
        table.AddRow([
            Bold(FontAttr(_('List'), size='+2')),
            Bold(FontAttr(_('Description'), size='+2'))
        ])
        highlight = 1
        for url, real_name, description in advertised:
            table.AddRow([
                Link(url, Bold(real_name)), description
                or Italic(_('[no description available]'))
            ])
            if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR:
                table.AddRowInfo(table.GetCurrentRowIndex(),
                                 bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR)
            highlight = not highlight

    doc.AddItem(table)
    doc.AddItem('<hr>')
    doc.AddItem(MailmanLogo())
    print doc.Format()
示例#8
0
def main():
    doc = Document()
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    cgidata = cgi.FieldStorage()
    try:
        cgidata.getfirst('password', '')
    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

    parts = Utils.GetPathPieces()

    if not parts:
        # Bad URL specification
        title = _('Bad URL specification')
        doc.SetTitle(title)
        doc.AddItem(
            Header(3, Bold(FontAttr(title, color='#ff0000', size='+2'))))
        doc.AddItem('<hr>')
        doc.AddItem(MailmanLogo())
        print(doc.Format())
        syslog('error', 'Bad URL specification: %s', parts)
        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)
        title = _('No such list <em>%(safelistname)s</em>')
        doc.SetTitle(_('No such list %(safelistname)s'))
        doc.AddItem(
            Header(3, Bold(FontAttr(title, color='#ff0000', size='+2'))))
        doc.AddItem('<hr>')
        doc.AddItem(MailmanLogo())
        # Send this with a 404 status.
        print('Status: 404 Not Found')
        print(doc.Format())
        syslog('error', 'rmlist: No such list "%s": %s\n', listname, e)
        return

    # Now that we have a valid mailing list, set the language
    i18n.set_language(mlist.preferred_language)
    doc.set_language(mlist.preferred_language)

    # Be sure the list owners are not sneaking around!
    if not mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS:
        title = _("You're being a sneaky list owner!")
        doc.SetTitle(title)
        doc.AddItem(
            Header(3, Bold(FontAttr(title, color='#ff0000', size='+2'))))
        doc.AddItem(mlist.GetMailmanFooter())
        print(doc.Format())
        syslog('mischief', 'Attempt to sneakily delete a list: %s', listname)
        return

    if 'doit' in cgidata:
        process_request(doc, cgidata, mlist)
        print(doc.Format())
        return

    request_deletion(doc, mlist)
    # Always add the footer and print the document
    doc.AddItem(mlist.GetMailmanFooter())
    print(doc.Format())
def process_request(doc, cgidata):
    # Lowercase the listname since this is treated as the "internal" name.
    listname = cgidata.getfirst('listname', '').strip().lower()
    owner    = cgidata.getfirst('owner', '').strip()
    try:
        autogen  = int(cgidata.getfirst('autogen', '0'))
    except ValueError:
        autogen = 0
    try:
        notify  = int(cgidata.getfirst('notify', '0'))
    except ValueError:
        notify = 0
    try:
        moderate = int(cgidata.getfirst('moderate',
                       mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION))
    except ValueError:
        moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION

    password = cgidata.getfirst('password', '').strip()
    confirm  = cgidata.getfirst('confirm', '').strip()
    auth     = cgidata.getfirst('auth', '').strip()
    langs    = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE])

    if not isinstance(langs, list):
        langs = [langs]
    # Sanity check
    safelistname = Utils.websafe(listname)
    if '@' in listname:
        request_creation(doc, cgidata,
                         _('List name must not include "@": %(safelistname)s'))
        return
    if Utils.list_exists(listname):
        # BAW: should we tell them the list already exists?  This could be
        # used to mine/guess the existance of non-advertised lists.  Then
        # again, that can be done in other ways already, so oh well.
        request_creation(doc, cgidata,
                         _('List already exists: %(safelistname)s'))
        return
    if not listname:
        request_creation(doc, cgidata,
                         _('You forgot to enter the list name'))
        return
    if not owner:
        request_creation(doc, cgidata,
                         _('You forgot to specify the list owner'))
        return

    if autogen:
        if password or confirm:
            request_creation(
                doc, cgidata,
                _('''Leave the initial password (and confirmation) fields
                blank if you want Mailman to autogenerate the list
                passwords.'''))
            return
        password = confirm = Utils.MakeRandomPassword(
            mm_cfg.ADMIN_PASSWORD_LENGTH)
    else:
        if password != confirm:
            request_creation(doc, cgidata,
                             _('Initial list passwords do not match'))
            return
        if not password:
            request_creation(
                doc, cgidata,
                # The little <!-- ignore --> tag is used so that this string
                # differs from the one in bin/newlist.  The former is destined
                # for the web while the latter is destined for email, so they
                # must be different entries in the message catalog.
                _('The list password cannot be empty<!-- ignore -->'))
            return
    # The authorization password must be non-empty, and it must match either
    # the list creation password or the site admin password
    ok = 0
    if auth:
        ok = Utils.check_global_password(auth, 0)
        if not ok:
            ok = Utils.check_global_password(auth)
    if not ok:
        request_creation(
            doc, cgidata,
            _('You are not authorized to create new mailing lists'))
        return
    # Make sure the web hostname matches one of our virtual domains
    hostname = Utils.get_domain()
    if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
           hostname not in mm_cfg.VIRTUAL_HOSTS:
        safehostname = Utils.websafe(hostname)
        request_creation(doc, cgidata,
                         _('Unknown virtual host: %(safehostname)s'))
        return
    emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST)
    # We've got all the data we need, so go ahead and try to create the list
    # See admin.py for why we need to set up the signal handler.
    mlist = MailList.MailList()

    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)

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

        pw = sha_new(password.encode()).hexdigest()
        # Guarantee that all newly created files have the proper permission.
        # proper group ownership should be assured by the autoconf script
        # enforcing that all directories have the group sticky bit set
        oldmask = os.umask(0o02)
        try:
            try:
                mlist.Create(listname, owner, pw, langs, emailhost,
                             urlhost=hostname)
            finally:
                os.umask(oldmask)
        except Errors.EmailAddressError as e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(owner)
            request_creation(doc, cgidata,
                             _('Bad owner email address: %(s)s'))
            return
        except Errors.MMListAlreadyExistsError:
            # MAS: List already exists so we don't need to websafe it.
            request_creation(doc, cgidata,
                             _('List already exists: %(listname)s'))
            return
        except Errors.BadListNameError as e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(listname)
            request_creation(doc, cgidata,
                             _('Illegal list name: %(s)s'))
            return
        except Errors.MMListError:
            request_creation(
                doc, cgidata,
                _('''Some unknown error occurred while creating the list.
                Please contact the site administrator for assistance.'''))
            return

        # Initialize the host_name and web_page_url attributes, based on
        # virtual hosting settings and the request environment variables.
        mlist.default_member_moderation = moderate
        mlist.web_page_url = mm_cfg.DEFAULT_URL_PATTERN % hostname
        mlist.host_name = emailhost
        mlist.Save()
    finally:
        # Now be sure to unlock the list.  It's okay if we get a signal here
        # because essentially, the signal handler will do the same thing.  And
        # unlocking is unconditional, so it's not an error if we unlock while
        # we're already unlocked.
        mlist.Unlock()

    # Now do the MTA-specific list creation tasks
    if mm_cfg.MTA:
        modname = 'Mailman.MTA.' + mm_cfg.MTA
        __import__(modname)
        sys.modules[modname].create(mlist, cgi=1)

    # And send the notice to the list owner.
    if notify:
        siteowner = Utils.get_site_email(mlist.host_name, 'owner')
        text = Utils.maketext(
            'newlist.txt',
            {'listname'    : listname,
             'password'    : password,
             'admin_url'   : mlist.GetScriptURL('admin', absolute=1),
             'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
             'requestaddr' : mlist.GetRequestEmail(),
             'siteowner'   : siteowner,
             }, mlist=mlist)
        msg = Message.UserNotification(
            owner, siteowner,
            _('Your new mailing list: %(listname)s'),
            text, mlist.preferred_language)
        msg.send(mlist)

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

    title = _('Mailing list creation results')
    doc.SetTitle(title)
    table = Table(border=0, width='100%')
    table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
    table.AddCellInfo(table.GetCurrentRowIndex(), 0,
                      bgcolor=mm_cfg.WEB_HEADER_COLOR)
    table.AddRow([_('''You have successfully created the mailing list
    <b>%(listname)s</b> and notification has been sent to the list owner
    <b>%(owner)s</b>.  You can now:''')])
    ullist = UnorderedList()
    ullist.AddItem(Link(listinfo_url, _("Visit the list's info page")))
    ullist.AddItem(Link(admin_url, _("Visit the list's admin page")))
    ullist.AddItem(Link(create_url, _('Create another list')))
    table.AddRow([ullist])
    doc.AddItem(table)
def main():
    doc = Document()
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    parts = Utils.GetPathPieces()
    if not parts or len(parts) < 1:
        bad_confirmation(doc)
        doc.AddItem(MailmanLogo())
        print(doc.Format())
        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)
        bad_confirmation(doc, _('No such list <em>%(safelistname)s</em>'))
        doc.AddItem(MailmanLogo())
        # Send this with a 404 status.
        print('Status: 404 Not Found')
        print(doc.Format())
        syslog('error', 'confirm: No such list "%s": %s', listname, e)
        return

    # Set the language for the list
    i18n.set_language(mlist.preferred_language)
    doc.set_language(mlist.preferred_language)

    # Get the form data to see if this is a second-step confirmation
    cgidata = cgi.FieldStorage(keep_blank_values=1)
    try:
        cookie = cgidata.getfirst('cookie')
    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

    if cookie == '':
        ask_for_cookie(mlist, doc, _('Confirmation string was empty.'))
        return

    if not cookie and len(parts) == 2:
        cookie = parts[1]

    if len(parts) > 2:
        bad_confirmation(doc)
        doc.AddItem(mlist.GetMailmanFooter())
        print(doc.Format())
        return

    if not cookie:
        ask_for_cookie(mlist, doc)
        return

    days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1) + 0.5)
    confirmurl = mlist.GetScriptURL('confirm', absolute=1)
    # Avoid cross-site scripting attacks
    safecookie = Utils.websafe(cookie)
    badconfirmstr = _('''<b>Invalid confirmation string:</b>
    %(safecookie)s.

    <p>Note that confirmation strings expire approximately
    %(days)s days after the initial request.  They also expire if the
    request has already been handled in some way.  If your confirmation
    has expired, please try to re-submit your request.
    Otherwise, <a href="%(confirmurl)s">re-enter</a> your confirmation
    string.''')

    content = mlist.pend_confirm(cookie, expunge=False)
    if content is None:
        bad_confirmation(doc, badconfirmstr)
        doc.AddItem(mlist.GetMailmanFooter())
        print(doc.Format())
        return

    try:
        if content[0] == Pending.SUBSCRIPTION:
            if cgidata.getfirst('cancel'):
                subscription_cancel(mlist, doc, cookie)
            elif cgidata.getfirst('submit'):
                subscription_confirm(mlist, doc, cookie, cgidata)
            else:
                subscription_prompt(mlist, doc, cookie, content[1])
        elif content[0] == Pending.UNSUBSCRIPTION:
            try:
                if cgidata.getfirst('cancel'):
                    unsubscription_cancel(mlist, doc, cookie)
                elif cgidata.getfirst('submit'):
                    unsubscription_confirm(mlist, doc, cookie)
                else:
                    unsubscription_prompt(mlist, doc, cookie, *content[1:])
            except Errors.NotAMemberError:
                doc.addError(
                    _("""The address requesting unsubscription is not
                a member of the mailing list.  Perhaps you have already been
                unsubscribed, e.g. by the list administrator?"""))
                # Expunge this record from the pending database.
                expunge(mlist, cookie)
        elif content[0] == Pending.CHANGE_OF_ADDRESS:
            if cgidata.getfirst('cancel'):
                addrchange_cancel(mlist, doc, cookie)
            elif cgidata.getfirst('submit'):
                addrchange_confirm(mlist, doc, cookie)
            else:
                # Watch out for users who have unsubscribed themselves in the
                # meantime!
                try:
                    addrchange_prompt(mlist, doc, cookie, *content[1:])
                except Errors.NotAMemberError:
                    doc.addError(
                        _("""The address requesting to be changed has
                    been subsequently unsubscribed.  This request has been
                    cancelled."""))
                    # Expunge this record from the pending database.
                    expunge(mlist, cookie)
        elif content[0] == Pending.HELD_MESSAGE:
            if cgidata.getfirst('cancel'):
                heldmsg_cancel(mlist, doc, cookie)
            elif cgidata.getfirst('submit'):
                heldmsg_confirm(mlist, doc, cookie)
            else:
                heldmsg_prompt(mlist, doc, cookie, *content[1:])
        elif content[0] == Pending.RE_ENABLE:
            if cgidata.getfirst('cancel'):
                reenable_cancel(mlist, doc, cookie)
            elif cgidata.getfirst('submit'):
                reenable_confirm(mlist, doc, cookie)
            else:
                reenable_prompt(mlist, doc, cookie, *content[1:])
        else:
            bad_confirmation(doc, _('System error, bad content: %(content)s'))
    except Errors.MMBadConfirmation:
        bad_confirmation(doc, badconfirmstr)

    doc.AddItem(mlist.GetMailmanFooter())
    print(doc.Format())
示例#11
0
def create_list(userdesc, perms, vhost, listname, desc, advertise, modlevel,
                inslevel, owners, members):
    """ Create a new list.
            @root
    """
    name = vhost.lower() + VHOST_SEP + listname.lower()
    if Utils.list_exists(name):
        print >> sys.stderr, "List ", name, " already exists"
        return 0

    owner = []
    for o in owners:
        email = to_forlife(o)
        print >> sys.stderr, "owner in list", o, email
        email = email[0]
        if email is not None:
            owner.append(email)
    if len(owner) is 0:
        print >> sys.stderr, "No owner found in ", owners
        return 0

    mlist = MailList.MailList()
    try:
        oldmask = os.umask(002)
        pw = sha.new('foobar').hexdigest()

        try:
            mlist.Create(name, owner[0], pw)
        finally:
            os.umask(oldmask)

        mlist.real_name = listname
        mlist.host_name = 'listes.polytechnique.org'
        mlist.description = desc

        mlist.advertised = int(advertise) is 0
        mlist.default_member_moderation = int(modlevel) is 2
        mlist.generic_nonmember_action = int(modlevel) > 0
        mlist.subscribe_policy = 2 * (int(inslevel) is 1)
        mlist.admin_notify_mchanges = (mlist.subscribe_policy
                                       or mlist.generic_nonmember_action
                                       or mlist.default_member_moderation
                                       or not mlist.advertised)

        mlist.owner = owner

        mlist.subject_prefix = '[' + listname + '] '
        mlist.max_message_size = 0

        inverted_listname = listname.lower() + '_' + vhost.lower()
        mlist.msg_footer = "_______________________________________________\n" \
                         + "Liste de diffusion %(real_name)s\n" \
                         + "http://listes.polytechnique.org/members/" + inverted_listname

        mlist.header_filter_rules = []
        mlist.header_filter_rules.append(
            ('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False))
        mlist.header_filter_rules.append(
            ('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False))

        if ON_CREATE_CMD != '':
            try:
                os.system(ON_CREATE_CMD + ' ' + name)
            except:
                pass

        check_options_runner(userdesc, perms, mlist, listname.lower(), True)
        mass_subscribe(userdesc, perms, mlist, members)
        mlist.Save()
    finally:
        mlist.Unlock()

    # avoid the "-1 mail to moderate" bug
    mlist = MailList.MailList(name)
    try:
        mlist._UpdateRecords()
        mlist.Save()
    finally:
        mlist.Unlock()
    return 1
示例#12
0
def main():
    parts = Utils.GetPathPieces()
    if not parts:
        error_page(_('Invalid options to CGI script'))
        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')
        error_page(_('No such list <em>%(safelistname)s</em>'))
        syslog('error', 'roster: No such list "%s": %s', listname, e)
        return

    cgidata = cgi.FieldStorage()

    # messages in form should go in selected language (if any...)
    try:
        lang = cgidata.getfirst('language')
    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

    if not Utils.IsLanguage(lang):
        lang = mlist.preferred_language
    i18n.set_language(lang)

    # Perform authentication for protected rosters.  If the roster isn't
    # protected, then anybody can see the pages.  If members-only or
    # "admin"-only, then we try to cookie authenticate the user, and failing
    # that, we check roster-email and roster-pw fields for a valid password.
    # (also allowed: the list moderator, the list admin, and the site admin).
    password = cgidata.getfirst('roster-pw', '').strip()
    addr = cgidata.getfirst('roster-email', '').strip()
    list_hidden = (not mlist.WebAuthenticate(
        (mm_cfg.AuthUser, ), password, addr) and mlist.WebAuthenticate(
            (mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin,
             mm_cfg.AuthSiteAdmin), password))
    if mlist.private_roster == 0:
        # No privacy
        ok = 1
    elif mlist.private_roster == 1:
        # Members only
        ok = mlist.WebAuthenticate(
            (mm_cfg.AuthUser, mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin,
             mm_cfg.AuthSiteAdmin), password, addr)
    else:
        # Admin only, so we can ignore the address field
        ok = mlist.WebAuthenticate(
            (mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin,
             mm_cfg.AuthSiteAdmin), password)
    if not ok:
        realname = mlist.real_name
        doc = Document()
        doc.set_language(lang)
        # Send this with a 401 status.
        print('Status: 401 Unauthorized')
        error_page_doc(doc, _('%(realname)s roster authentication failed.'))
        doc.AddItem(mlist.GetMailmanFooter())
        print(doc.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 (roster): list=%s: remote=%s',
               listname, remote)
        return

    # The document and its language
    doc = HeadlessDocument()
    doc.set_language(lang)

    replacements = mlist.GetAllReplacements(lang, list_hidden)
    replacements['<mm-displang-box>'] = mlist.FormatButton(
        'displang-button', text=_('View this page in'))
    replacements['<mm-lang-form-start>'] = mlist.FormatFormStart('roster')
    doc.AddItem(mlist.ParseTags('roster.html', replacements, lang))
    print(doc.Format())
示例#13
0
def process_request(doc, cgidata):
    # Lowercase the listname since this is treated as the "internal" name.
    listname = cgidata.getvalue('listname', '').strip().lower()
    owner = cgidata.getvalue('owner', '').strip()
    try:
        autogen = int(cgidata.getvalue('autogen', '0'))
    except ValueError:
        autogen = 0
    try:
        notify = int(cgidata.getvalue('notify', '0'))
    except ValueError:
        notify = 0
    try:
        moderate = int(
            cgidata.getvalue('moderate',
                             mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION))
    except ValueError:
        moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION

    password = cgidata.getvalue('password', '').strip()
    confirm = cgidata.getvalue('confirm', '').strip()
    auth = cgidata.getvalue('auth', '').strip()
    langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE])

    if not isinstance(langs, ListType):
        langs = [langs]
    # Sanity check
    safelistname = Utils.websafe(listname)
    if '@' in listname:
        request_creation(doc, cgidata,
                         _('List name must not include "@": %(safelistname)s'))
        return
    if Utils.list_exists(listname):
        # BAW: should we tell them the list already exists?  This could be
        # used to mine/guess the existance of non-advertised lists.  Then
        # again, that can be done in other ways already, so oh well.
        request_creation(doc, cgidata,
                         _('List already exists: %(safelistname)s'))
        return
    if not listname:
        request_creation(doc, cgidata, _('You forgot to enter the list name'))
        return
    if not owner:
        request_creation(doc, cgidata,
                         _('You forgot to specify the list owner'))
        return

    if autogen:
        if password or confirm:
            request_creation(
                doc, cgidata,
                _('''Leave the initial password (and confirmation) fields
                blank if you want Mailman to autogenerate the list
                passwords.'''))
            return
        password = confirm = Utils.MakeRandomPassword(
            mm_cfg.ADMIN_PASSWORD_LENGTH)
    else:
        if password <> confirm:
            request_creation(doc, cgidata,
                             _('Initial list passwords do not match'))
            return
        if not password:
            request_creation(
                doc,
                cgidata,
                # The little <!-- ignore --> tag is used so that this string
                # differs from the one in bin/newlist.  The former is destined
                # for the web while the latter is destined for email, so they
                # must be different entries in the message catalog.
                _('The list password cannot be empty<!-- ignore -->'))
            return
    # The authorization password must be non-empty, and it must match either
    # the list creation password or the site admin password
    ok = 0
    if auth:
        ok = Utils.check_global_password(auth, 0)
        if not ok:
            ok = Utils.check_global_password(auth)
    if not ok:
        request_creation(
            doc, cgidata,
            _('You are not authorized to create new mailing lists'))
        return
    # Make sure the web hostname matches one of our virtual domains
    hostname = Utils.get_domain()
    if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
           not mm_cfg.VIRTUAL_HOSTS.has_key(hostname):
        safehostname = Utils.websafe(hostname)
        request_creation(doc, cgidata,
                         _('Unknown virtual host: %(safehostname)s'))
        return
    emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST)
    # We've got all the data we need, so go ahead and try to create the list
    # See admin.py for why we need to set up the signal handler.
    mlist = MailList.MailList()

    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)

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

        pw = sha_new(password).hexdigest()
        # Guarantee that all newly created files have the proper permission.
        # proper group ownership should be assured by the autoconf script
        # enforcing that all directories have the group sticky bit set
        oldmask = os.umask(002)
        try:
            try:
                mlist.Create(listname,
                             owner,
                             pw,
                             langs,
                             emailhost,
                             urlhost=hostname)
            finally:
                os.umask(oldmask)
        except Errors.EmailAddressError, e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(owner)
            request_creation(doc, cgidata, _('Bad owner email address: %(s)s'))
            return
        except Errors.MMListAlreadyExistsError:
            # MAS: List already exists so we don't need to websafe it.
            request_creation(doc, cgidata,
                             _('List already exists: %(listname)s'))
            return
        except Errors.BadListNameError, e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(listname)
            request_creation(doc, cgidata, _('Illegal list name: %(s)s'))
            return
示例#14
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()