Пример #1
0
def main():
    doc = Document()
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    parts = Utils.GetPathPieces()
    lenparts = parts and len(parts)
    if not parts or lenparts < 1:
        title = _('CGI script error')
        doc.SetTitle(title)
        doc.AddItem(Header(2, title))
        doc.addError(_('Invalid options to CGI script.'))
        doc.AddItem('<hr>')
        doc.AddItem(MailmanLogo())
        print doc.Format()
        return

    # get the list and user's name
    listname = parts[0].lower()
    # open list
    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError, e:
        # Avoid cross-site scripting attacks
        safelistname = Utils.websafe(listname)
        title = _('CGI script error')
        doc.SetTitle(title)
        doc.AddItem(Header(2, title))
        doc.addError(_('No such list <em>%(safelistname)s</em>'))
        doc.AddItem('<hr>')
        doc.AddItem(MailmanLogo())
        # Send this with a 404 status.
        print 'Status: 404 Not Found'
        print doc.Format()
        syslog('error', 'No such list "%s": %s\n', listname, e)
        return
Пример #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, 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
Пример #3
0
def main():
    # Trick out pygettext since we want to mark template_data as translatable,
    # but we don't want to actually translate it here.
    def _(s):
        return s

    template_data = (
        ('listinfo.html', _('General list information page')),
        ('subscribe.html', _('Subscribe results page')),
        ('options.html', _('User specific options page')),
        ('subscribeack.txt', _('Welcome email text file')),
        ('masthead.txt', _('Digest masthead')),
        ('postheld.txt', _('User notice of held post')),
        ('approve.txt', _('User notice of held subscription')),
        ('refuse.txt', _('Notice of post refused by moderator')),
        ('invite.txt', _('Invitation to join list')),
        ('verify.txt', _('Request to confirm subscription')),
        ('unsub.txt', _('Request to confirm unsubscription')),
        ('nomoretoday.txt', _('User notice of autoresponse limit')),
        ('postack.txt', _('User post acknowledgement')),
        ('disabled.txt', _('Subscription disabled by bounce warning')),
        ('admlogin.html', _('Admin/moderator login page')),
        ('private.html', _('Private archive login page')),
        ('userpass.txt', _('On demand password reminder')),
    )

    _ = i18n._
    doc = Document()

    # Set up the system default language
    i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    parts = Utils.GetPathPieces()
    if not parts:
        doc.AddItem(Header(2, _("List name is required.")))
        print doc.Format()
        return

    listname = parts[0].lower()
    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError, e:
        # Avoid cross-site scripting attacks
        safelistname = Utils.websafe(listname)
        doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>')))
        # Send this with a 404 status.
        print 'Status: 404 Not Found'
        print doc.Format()
        syslog('error', 'edithtml: No such list "%s": %s', listname, e)
        return
Пример #4
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, 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
Пример #5
0
def main():
    parts = Utils.GetPathPieces()
    if not parts:
        listinfo_overview()
        return

    listname = parts[0].lower()
    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError, e:
        # Avoid cross-site scripting attacks
        safelistname = Utils.websafe(listname)
        # Send this with a 404 status.
        print 'Status: 404 Not Found'
        listinfo_overview(_('No such list <em>%(safelistname)s</em>'))
        syslog('error', 'No such list "%s": %s', listname, e)
        return
Пример #6
0
def main():
    # 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, 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', 'No such list "%s": %s\n', listname, e)
        return
Пример #7
0
def doit ():
    parts = Utils.GetPathPieces()
    if not parts:
        Home().handler()
        return

    action = parts[0].lower().strip()

    if action == 'view':
        View().handler(parts[1:])
    elif action == 'listadmin':
        Create().handler(parts[1:])
    elif action == 'siteadmin':
        Admin().handler()
    elif action == 'subscribe':
        try:
            Subscription().handler()
        except Exception, e:
            ## FIXME: This is not the best approach. The overall exception
            ## handling has become a mess... Hm.
            raise JSONException(str(e))
Пример #8
0
def main():
    # Trick out pygettext since we want to mark template_data as translatable,
    # but we don't want to actually translate it here.
    def _(s):
        return s

    template_data = (
        ('listinfo.html',    _('General list information page')),
        ('subscribe.html',   _('Subscribe results page')),
        ('options.html',     _('User specific options page')),
        ('subscribeack.txt', _('Welcome email text file')),
        ('masthead.txt',     _('Digest masthead')),
        )

    _ = i18n._
    doc = Document()

    # Set up the system default language
    i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    parts = Utils.GetPathPieces()
    if not parts:
        doc.AddItem(Header(2, _("List name is required.")))
        print doc.Format()
        return

    listname = parts[0].lower()
    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError, e:
        # Avoid cross-site scripting attacks
        safelistname = Utils.websafe(listname)
        doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>')))
        # Send this with a 404 status.
        print 'Status: 404 Not Found'
        print doc.Format()
        syslog('error', 'No such list "%s": %s', listname, e)
        return
Пример #9
0
def main():
    # print 'Content-type: text/plain\n\n'
    print 'Content-type: text/plain; charset=us-ascii\n'

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

    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError, 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))
        print doc.Format()
        syslog('error', 'No such list "%s": %s\n', listname, e)
        return
Пример #10
0
def main():
    parts = Utils.GetPathPieces()
    if not parts:
        listinfo_overview()
        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')
        listinfo_overview(_('No such list <em>%(safelistname)s</em>'))
        syslog('error', 'listinfo: No such list "%s": %s', listname, e)
        return

    # See if the user want to see this page in other language
    cgidata = cgi.FieldStorage()
    try:
        language = 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(language):
        language = mlist.preferred_language
    i18n.set_language(language)
    list_listinfo(mlist, language)
Пример #11
0
def main():
    doc = Document()
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    cgidata = cgi.FieldStorage()
    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, e:
        # Avoid cross-site scripting attacks
        safelistname = Utils.websafe(listname)
        title = _('No such list <em>%(safelistname)s</em>')
        doc.SetTitle(title)
        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', 'No such list "%s": %s\n', listname, e)
        return
Пример #12
0
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, 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
Пример #13
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()
Пример #14
0
def main():
    # Trick out pygettext since we want to mark template_data as translatable,
    # but we don't want to actually translate it here.
    def _(s):
        return s

    template_data = (
        ('listinfo.html', _('General list information page')),
        ('subscribe.html', _('Subscribe results page')),
        ('options.html', _('User specific options page')),
        ('subscribeack.txt', _('Welcome email text file')),
        ('masthead.txt', _('Digest masthead')),
        ('postheld.txt', _('User notice of held post')),
        ('approve.txt', _('User notice of held subscription')),
        ('refuse.txt', _('Notice of post refused by moderator')),
        ('invite.txt', _('Invitation to join list')),
        ('verify.txt', _('Request to confirm subscription')),
        ('unsub.txt', _('Request to confirm unsubscription')),
        ('nomoretoday.txt', _('User notice of autoresponse limit')),
        ('postack.txt', _('User post acknowledgement')),
        ('disabled.txt', _('Subscription disabled by bounce warning')),
        ('admlogin.html', _('Admin/moderator login page')),
        ('private.html', _('Private archive login page')),
        ('userpass.txt', _('On demand password reminder')),
    )

    _ = i18n._
    doc = Document()

    # Set up the system default language
    i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    parts = Utils.GetPathPieces()
    if not parts:
        doc.AddItem(Header(2, _("List name is required.")))
        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, _('No such list <em>%(safelistname)s</em>')))
        # Send this with a 404 status.
        print('Status: 404 Not Found')
        print(doc.Format())
        syslog('error', 'edithtml: No such list "%s": %s', listname, e)
        return

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

    # Must be authenticated to get any farther
    cgidata = cgi.FieldStorage()
    try:
        cgidata.getfirst('adminpw', '')
    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

    # CSRF check
    safe_params = ['VARHELP', 'adminpw', 'admlogin']
    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

    # Editing the html for a list is limited to the list admin and site admin.
    if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin),
                                 cgidata.getfirst('adminpw', '')):
        if 'admlogin' 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 (edithtml): list=%s: remote=%s',
                   listname, remote)
        else:
            msg = ''
        Auth.loginpage(mlist, 'admin', msg=msg)
        return

    realname = mlist.real_name
    if len(parts) > 1:
        template_name = parts[1]
        for (template, info) in template_data:
            if template == template_name:
                template_info = _(info)
                doc.SetTitle(
                    _('%(realname)s -- Edit html for %(template_info)s'))
                break
        else:
            # Avoid cross-site scripting attacks
            safetemplatename = Utils.websafe(template_name)
            doc.SetTitle(_('Edit HTML : Error'))
            doc.AddItem(Header(2, _("%(safetemplatename)s: Invalid template")))
            doc.AddItem(mlist.GetMailmanFooter())
            print(doc.Format())
            return
    else:
        doc.SetTitle(_('%(realname)s -- HTML Page Editing'))
        doc.AddItem(Header(1, _('%(realname)s -- HTML Page Editing')))
        doc.AddItem(Header(2, _('Select page to edit:')))
        template_list = UnorderedList()
        for (template, info) in template_data:
            l = Link(mlist.GetScriptURL('edithtml') + '/' + template, _(info))
            template_list.AddItem(l)
        doc.AddItem(FontSize("+2", template_list))
        doc.AddItem(mlist.GetMailmanFooter())
        print(doc.Format())
        return

    try:
        if list(cgidata.keys()):
            if csrf_checked:
                ChangeHTML(mlist, cgidata, template_name, doc)
            else:
                doc.addError(
                    _('The form lifetime has expired. (request forgery check)')
                )
        FormatHTML(mlist, doc, template_name, template_info)
    finally:
        doc.AddItem(mlist.GetMailmanFooter())
        print(doc.Format())
Пример #15
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())
Пример #16
0
def main():
    global _
    doc = Document()
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    method = Utils.GetRequestMethod()
    if method.lower() not in ('get', 'post'):
        title = _('CGI script error')
        doc.SetTitle(title)
        doc.AddItem(Header(2, title))
        doc.addError(_('Invalid request method: %(method)s'))
        doc.AddItem('<hr>')
        doc.AddItem(MailmanLogo())
        print('Status: 405 Method Not Allowed')
        print(doc.Format())
        return

    parts = Utils.GetPathPieces()
    lenparts = parts and len(parts)
    if not parts or lenparts < 1:
        title = _('CGI script error')
        doc.SetTitle(title)
        doc.AddItem(Header(2, title))
        doc.addError(_('Invalid options to CGI script.'))
        doc.AddItem('<hr>')
        doc.AddItem(MailmanLogo())
        print(doc.Format())
        return

    # get the list and user's name
    listname = parts[0].lower()
    # open list
    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError as e:
        # Avoid cross-site scripting attacks
        safelistname = Utils.websafe(listname)
        title = _('CGI script error')
        doc.SetTitle(title)
        doc.AddItem(Header(2, title))
        doc.addError(_('No such list <em>%(safelistname)s</em>'))
        doc.AddItem('<hr>')
        doc.AddItem(MailmanLogo())
        # Send this with a 404 status.
        print('Status: 404 Not Found')
        print(doc.Format())
        syslog('error', 'options: No such list "%s": %s\n', listname, e)
        return

    # The total contents of the user's response
    cgidata = cgi.FieldStorage(keep_blank_values=1)

    # CSRF check
    safe_params = [
        'displang-button', 'language', 'email', 'password', 'login',
        'login-unsub', 'login-remind', 'VARHELP', 'UserOptions'
    ]
    try:
        params = list(cgidata.keys())
    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 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('password'):
        os.environ['HTTP_COOKIE'] = ''
        csrf_checked = True

    # Set the language for the page.  If we're coming from the listinfo cgi,
    # we might have a 'language' key in the cgi data.  That was an explicit
    # preference to view the page in, so we should honor that here.  If that's
    # not available, use the list's default language.
    language = cgidata.getfirst('language')
    if not Utils.IsLanguage(language):
        language = mlist.preferred_language
    i18n.set_language(language)
    doc.set_language(language)

    if lenparts < 2:
        user = cgidata.getfirst('email')
        if not user:
            # If we're coming from the listinfo page and we left the email
            # address field blank, it's not an error.  Likewise if we're
            # coming from anywhere else. Only issue the error if we came
            # via one of our buttons.
            if (cgidata.getfirst('login') or cgidata.getfirst('login-unsub')
                    or cgidata.getfirst('login-remind')):
                doc.addError(_('No address given'))
            loginpage(mlist, doc, None, language)
            print(doc.Format())
            return
    else:
        user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:])))
    # If a user submits a form or URL with post data or query fragments
    # with multiple occurrences of the same variable, we can get a list
    # here.  Be as careful as possible.
    if isinstance(user, list) or isinstance(user, tuple):
        if len(user) == 0:
            user = ''
        else:
            user = user[-1]

    # Avoid cross-site scripting attacks
    safeuser = Utils.websafe(user)
    try:
        Utils.ValidateEmail(user)
    except Errors.EmailAddressError:
        doc.addError(_('Illegal Email Address: %(safeuser)s'))
        loginpage(mlist, doc, None, language)
        print(doc.Format())
        return
    # Sanity check the user, but only give the "no such member" error when
    # using public rosters, otherwise, we'll leak membership information.
    if not mlist.isMember(user) and mlist.private_roster == 0:
        doc.addError(_('No such member: %(safeuser)s.'))
        loginpage(mlist, doc, None, language)
        print(doc.Format())
        return

    # Find the case preserved email address (the one the user subscribed with)
    lcuser = user.lower()
    try:
        cpuser = mlist.getMemberCPAddress(lcuser)
    except Errors.NotAMemberError:
        # This happens if the user isn't a member but we've got private rosters
        cpuser = None
    if lcuser == cpuser:
        cpuser = None

    # And now we know the user making the request, so set things up to for the
    # user's stored preferred language, overridden by any form settings for
    # their new language preference.
    userlang = cgidata.getfirst('language')
    if not Utils.IsLanguage(userlang):
        userlang = mlist.getMemberLanguage(user)
    doc.set_language(userlang)
    i18n.set_language(userlang)

    # Are we processing an unsubscription request from the login screen?
    msgc = _('If you are a list member, a confirmation email has been sent.')
    msga = _("""If you are a list member, your unsubscription request has been
             forwarded to the list administrator for approval.""")
    if 'login-unsub' in cgidata:
        # Because they can't supply a password for unsubscribing, we'll need
        # to do the confirmation dance.
        if mlist.isMember(user):
            # We must acquire the list lock in order to pend a request.
            try:
                mlist.Lock()
                # If unsubs require admin approval, then this request has to
                # be held.  Otherwise, send a confirmation.
                if mlist.unsubscribe_policy:
                    mlist.HoldUnsubscription(user)
                    doc.addError(msga, tag='')
                else:
                    ip = os.environ.get(
                        'HTTP_FORWARDED_FOR',
                        os.environ.get(
                            'HTTP_X_FORWARDED_FOR',
                            os.environ.get('REMOTE_ADDR',
                                           'unidentified origin')))
                    mlist.ConfirmUnsubscription(user, userlang, remote=ip)
                    doc.addError(msgc, tag='')
                mlist.Save()
            finally:
                mlist.Unlock()
        else:
            # Not a member
            if mlist.private_roster == 0:
                # Public rosters
                doc.addError(_('No such member: %(safeuser)s.'))
            else:
                syslog('mischief',
                       'Unsub attempt of non-member w/ private rosters: %s',
                       user)
                if mlist.unsubscribe_policy:
                    doc.addError(msga, tag='')
                else:
                    doc.addError(msgc, tag='')
        loginpage(mlist, doc, user, language)
        print(doc.Format())
        return

    # Are we processing a password reminder from the login screen?
    msg = _("""If you are a list member,
            your password has been emailed to you.""")
    if 'login-remind' in cgidata:
        if mlist.isMember(user):
            mlist.MailUserPassword(user)
            doc.addError(msg, tag='')
        else:
            # Not a member
            if mlist.private_roster == 0:
                # Public rosters
                doc.addError(_('No such member: %(safeuser)s.'))
            else:
                syslog(
                    'mischief',
                    'Reminder attempt of non-member w/ private rosters: %s',
                    user)
                doc.addError(msg, tag='')
        loginpage(mlist, doc, user, language)
        print(doc.Format())
        return

    # Get the password from the form.
    password = cgidata.getfirst('password', '').strip()
    # Check authentication.  We need to know if the credentials match the user
    # or the site admin, because they are the only ones who are allowed to
    # change things globally.  Specifically, the list admin may not change
    # values globally.
    if mm_cfg.ALLOW_SITE_ADMIN_COOKIES:
        user_or_siteadmin_context = (mm_cfg.AuthUser, mm_cfg.AuthSiteAdmin)
    else:
        # Site and list admins are treated equal so that list admin can pass
        # site admin test. :-(
        user_or_siteadmin_context = (mm_cfg.AuthUser, )
    is_user_or_siteadmin = mlist.WebAuthenticate(user_or_siteadmin_context,
                                                 password, user)
    # Authenticate, possibly using the password supplied in the login page
    if not is_user_or_siteadmin and \
       not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
                                  mm_cfg.AuthSiteAdmin),
                                 password, user):
        # Not authenticated, so throw up the login page again.  If they tried
        # to authenticate via cgi (instead of cookie), then print an error
        # message.
        if 'password' in cgidata:
            doc.addError(_('Authentication failed.'))
            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',
                user, listname, remote)
            # So as not to allow membership leakage, prompt for the email
            # address and the password here.
            if mlist.private_roster != 0:
                syslog('mischief',
                       'Login failure with private rosters: %s from %s', user,
                       remote)
                user = None
            # give an HTTP 401 for authentication failure
            print('Status: 401 Unauthorized')
        loginpage(mlist, doc, user, language)
        print(doc.Format())
        return

    # From here on out, the user is okay to view and modify their membership
    # options.  The first set of checks does not require the list to be
    # locked.

    # However, if a form is submitted for a user who has been asynchronously
    # unsubscribed, uncaught NotAMemberError exceptions can be thrown.

    if not mlist.isMember(user):
        loginpage(mlist, doc, user, language)
        print(doc.Format())
        return

    # Before going further, get the result of CSRF check and do nothing
    # if it has failed.
    if csrf_checked == False:
        doc.addError(
            _('The form lifetime has expired. (request forgery check)'))
        options_page(mlist, doc, user, cpuser, userlang)
        print(doc.Format())
        return

    # See if this is VARHELP on topics.
    varhelp = None
    if 'VARHELP' in cgidata:
        varhelp = cgidata['VARHELP'].value
    elif os.environ.get('QUERY_STRING'):
        # POST methods, even if their actions have a query string, don't get
        # put into FieldStorage's keys :-(
        qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP')
        if qs and type(qs) == list:
            varhelp = qs[0]
    if varhelp:
        # Sanitize the topic name.
        varhelp = re.sub('<.*', '', varhelp)
        topic_details(mlist, doc, user, cpuser, userlang, varhelp)
        return

    if 'logout' in cgidata:
        print(mlist.ZapCookie(mm_cfg.AuthUser, user))
        loginpage(mlist, doc, user, language)
        print(doc.Format())
        return

    if 'emailpw' in cgidata:
        mlist.MailUserPassword(user)
        options_page(mlist, doc, user, cpuser, userlang,
                     _('A reminder of your password has been emailed to you.'))
        print(doc.Format())
        return

    if 'othersubs' in cgidata:
        # Only the user or site administrator can view all subscriptions.
        if not is_user_or_siteadmin:
            doc.addError(
                _("""The list administrator may not view the other
            subscriptions for this user."""), _('Note: '))
            options_page(mlist, doc, user, cpuser, userlang)
            print(doc.Format())
            return
        hostname = mlist.host_name
        title = _('List subscriptions for %(safeuser)s on %(hostname)s')
        doc.SetTitle(title)
        doc.AddItem(Header(2, title))
        doc.AddItem(
            _('''Click on a link to visit your options page for the
        requested mailing list.'''))

        # Troll through all the mailing lists that match host_name and see if
        # the user is a member.  If so, add it to the list.
        onlists = []
        for gmlist in lists_of_member(mlist, user) + [mlist]:
            extra = ''
            url = gmlist.GetOptionsURL(user)
            link = Link(url, gmlist.real_name)
            if gmlist.getDeliveryStatus(user) != MemberAdaptor.ENABLED:
                extra += ', ' + _('nomail')
            if user in gmlist.getDigestMemberKeys():
                extra += ', ' + _('digest')
            link = HTMLFormatObject(link, 0) + extra
            onlists.append((gmlist.real_name, link))
        onlists.sort()
        items = OrderedList(*[link for name, link in onlists])
        doc.AddItem(items)
        print(doc.Format())
        return

    if 'change-of-address' in cgidata:
        # We could be changing the user's full name, email address, or both.
        # Watch out for non-ASCII characters in the member's name.
        membername = cgidata.getfirst('fullname')
        # Canonicalize the member's name
        membername = Utils.canonstr(membername, language)
        newaddr = cgidata.getfirst('new-address')
        confirmaddr = cgidata.getfirst('confirm-address')

        oldname = mlist.getMemberName(user)
        set_address = set_membername = 0

        # See if the user wants to change their email address globally.  The
        # list admin is /not/ allowed to make global changes.
        globally = cgidata.getfirst('changeaddr-globally')
        if globally and not is_user_or_siteadmin:
            doc.addError(
                _("""The list administrator may not change the names
            or addresses for this user's other subscriptions.  However, the
            subscription for this mailing list has been changed."""),
                _('Note: '))
            globally = False
        # We will change the member's name under the following conditions:
        # - membername has a value
        # - membername has no value, but they /used/ to have a membername
        if membername and membername != oldname:
            # Setting it to a new value
            set_membername = 1
        if not membername and oldname:
            # Unsetting it
            set_membername = 1
        # We will change the user's address if both newaddr and confirmaddr
        # are non-blank, have the same value, and aren't the currently
        # subscribed email address (when compared case-sensitively).  If both
        # are blank, but membername is set, we ignore it, otherwise we print
        # an error.
        msg = ''
        if newaddr and confirmaddr:
            if newaddr != confirmaddr:
                options_page(mlist, doc, user, cpuser, userlang,
                             _('Addresses did not match!'))
                print(doc.Format())
                return
            if newaddr == cpuser:
                options_page(mlist, doc, user, cpuser, userlang,
                             _('You are already using that email address'))
                print(doc.Format())
                return
            # If they're requesting to subscribe an address which is already a
            # member, and they're /not/ doing it globally, then refuse.
            # Otherwise, we'll agree to do it globally (with a warning
            # message) and let ApprovedChangeMemberAddress() handle already a
            # member issues.
            if mlist.isMember(newaddr):
                safenewaddr = Utils.websafe(newaddr)
                if globally:
                    listname = mlist.real_name
                    msg += _("""\
The new address you requested %(newaddr)s is already a member of the
%(listname)s mailing list, however you have also requested a global change of
address.  Upon confirmation, any other mailing list containing the address
%(safeuser)s will be changed. """)
                    # Don't return
                else:
                    options_page(
                        mlist, doc, user, cpuser, userlang,
                        _('The new address is already a member: %(newaddr)s'))
                    print(doc.Format())
                    return
            set_address = 1
        elif (newaddr or confirmaddr) and not set_membername:
            options_page(mlist, doc, user, cpuser, userlang,
                         _('Addresses may not be blank'))
            print(doc.Format())
            return

        # Standard sigterm handler.
        def sigterm_handler(signum, frame, mlist=mlist):
            mlist.Unlock()
            sys.exit(0)

        signal.signal(signal.SIGTERM, sigterm_handler)
        if set_address:
            if cpuser is None:
                cpuser = user
            # Register the pending change after the list is locked
            msg += _('A confirmation message has been sent to %(newaddr)s. ')
            mlist.Lock()
            try:
                try:
                    mlist.ChangeMemberAddress(cpuser, newaddr, globally)
                    mlist.Save()
                finally:
                    mlist.Unlock()
            except Errors.MMBadEmailError:
                msg = _('Bad email address provided')
            except Errors.MMHostileAddress:
                msg = _('Illegal email address provided')
            except Errors.MMAlreadyAMember:
                msg = _('%(newaddr)s is already a member of the list.')
            except Errors.MembershipIsBanned:
                owneraddr = mlist.GetOwnerEmail()
                msg = _("""%(newaddr)s is banned from this list.  If you
                      think this restriction is erroneous, please contact
                      the list owners at %(owneraddr)s.""")

        if set_membername:
            mlist.Lock()
            try:
                mlist.ChangeMemberName(user, membername, globally)
                mlist.Save()
            finally:
                mlist.Unlock()
            msg += _('Member name successfully changed. ')

        options_page(mlist, doc, user, cpuser, userlang, msg)
        print(doc.Format())
        return

    if 'changepw' in cgidata:
        # Is this list admin and is list admin allowed to change passwords.
        if not (is_user_or_siteadmin
                or mm_cfg.OWNERS_CAN_CHANGE_MEMBER_PASSWORDS):
            doc.addError(
                _("""The list administrator may not change the
                    password for a user."""))
            options_page(mlist, doc, user, cpuser, userlang)
            print(doc.Format())
            return
        newpw = cgidata.getfirst('newpw', '').strip()
        confirmpw = cgidata.getfirst('confpw', '').strip()
        if not newpw or not confirmpw:
            options_page(mlist, doc, user, cpuser, userlang,
                         _('Passwords may not be blank'))
            print(doc.Format())
            return
        if newpw != confirmpw:
            options_page(mlist, doc, user, cpuser, userlang,
                         _('Passwords did not match!'))
            print(doc.Format())
            return

        # See if the user wants to change their passwords globally, however
        # the list admin is /not/ allowed to change passwords globally.
        pw_globally = cgidata.getfirst('pw-globally')
        if pw_globally and not is_user_or_siteadmin:
            doc.addError(
                _("""The list administrator may not change the
            password for this user's other subscriptions.  However, the
            password for this mailing list has been changed."""), _('Note: '))
            pw_globally = False

        mlists = [mlist]

        if pw_globally:
            mlists.extend(lists_of_member(mlist, user))

        for gmlist in mlists:
            change_password(gmlist, user, newpw, confirmpw)

        # Regenerate the cookie so a re-authorization isn't necessary
        print(mlist.MakeCookie(mm_cfg.AuthUser, user))
        options_page(mlist, doc, user, cpuser, userlang,
                     _('Password successfully changed.'))
        print(doc.Format())
        return

    if 'unsub' in cgidata:
        # Was the confirming check box turned on?
        if not cgidata.getfirst('unsubconfirm'):
            options_page(
                mlist, doc, user, cpuser, userlang,
                _('''You must confirm your unsubscription request by turning
                on the checkbox below the <em>Unsubscribe</em> button.  You
                have not been unsubscribed!'''))
            print(doc.Format())
            return

        # Standard signal handler
        def sigterm_handler(signum, frame, mlist=mlist):
            mlist.Unlock()
            sys.exit(0)

        # Okay, zap them.  Leave them sitting at the list's listinfo page.  We
        # must own the list lock, and we want to make sure the user (BAW: and
        # list admin?) is informed of the removal.
        signal.signal(signal.SIGTERM, sigterm_handler)
        mlist.Lock()
        needapproval = False
        try:
            _ = D_
            try:
                mlist.DeleteMember(user,
                                   _('via the member options page'),
                                   userack=1)
            except Errors.MMNeedApproval:
                needapproval = True
            except Errors.NotAMemberError:
                # MAS This except should really be in the outer try so we
                # don't save the list redundantly, but except and finally in
                # the same try requires Python >= 2.5.
                # Setting a switch and making the Save() conditional doesn't
                # seem worth it as the Save() won't change anything.
                pass
            mlist.Save()
        finally:
            _ = i18n._
            mlist.Unlock()
        # Now throw up some results page, with appropriate links.  We can't
        # drop them back into their options page, because that's gone now!
        fqdn_listname = mlist.GetListEmail()
        owneraddr = mlist.GetOwnerEmail()
        url = mlist.GetScriptURL('listinfo', absolute=1)

        title = _('Unsubscription results')
        doc.SetTitle(title)
        doc.AddItem(Header(2, title))
        if needapproval:
            doc.AddItem(
                _("""Your unsubscription request has been received and
            forwarded on to the list moderators for approval.  You will
            receive notification once the list moderators have made their
            decision."""))
        else:
            doc.AddItem(
                _("""You have been successfully unsubscribed from the
            mailing list %(fqdn_listname)s.  If you were receiving digest
            deliveries you may get one more digest.  If you have any questions
            about your unsubscription, please contact the list owners at
            %(owneraddr)s."""))
        doc.AddItem(mlist.GetMailmanFooter())
        print(doc.Format())
        return

    if 'options-submit' in cgidata:
        # Digest action flags
        digestwarn = 0
        cantdigest = 0
        mustdigest = 0

        newvals = []
        # First figure out which options have changed.  The item names come
        # from FormatOptionButton() in HTMLFormatter.py
        for item, flag in (
            ('digest', mm_cfg.Digests),
            ('mime', mm_cfg.DisableMime),
            ('dontreceive', mm_cfg.DontReceiveOwnPosts),
            ('ackposts', mm_cfg.AcknowledgePosts),
            ('disablemail', mm_cfg.DisableDelivery),
            ('conceal', mm_cfg.ConcealSubscription),
            ('remind', mm_cfg.SuppressPasswordReminder),
            ('rcvtopic', mm_cfg.ReceiveNonmatchingTopics),
            ('nodupes', mm_cfg.DontReceiveDuplicates),
        ):
            try:
                newval = int(cgidata.getfirst(item))
            except (TypeError, ValueError):
                newval = None

            # Skip this option if there was a problem or it wasn't changed.
            # Note that delivery status is handled separate from the options
            # flags.
            if newval is None:
                continue
            elif flag == mm_cfg.DisableDelivery:
                status = mlist.getDeliveryStatus(user)
                # Here, newval == 0 means enable, newval == 1 means disable
                if not newval and status != MemberAdaptor.ENABLED:
                    newval = MemberAdaptor.ENABLED
                elif newval and status == MemberAdaptor.ENABLED:
                    newval = MemberAdaptor.BYUSER
                else:
                    continue
            elif newval == mlist.getMemberOption(user, flag):
                continue
            # Should we warn about one more digest?
            if flag == mm_cfg.Digests and \
                   newval == 0 and mlist.getMemberOption(user, flag):
                digestwarn = 1

            newvals.append((flag, newval))

        # The user language is handled a little differently
        if userlang not in mlist.GetAvailableLanguages():
            newvals.append((SETLANGUAGE, mlist.preferred_language))
        else:
            newvals.append((SETLANGUAGE, userlang))

        # Process user selected topics, but don't make the changes to the
        # MailList object; we must do that down below when the list is
        # locked.
        topicnames = cgidata.getvalue('usertopic')
        if topicnames:
            # Some topics were selected.  topicnames can actually be a string
            # or a list of strings depending on whether more than one topic
            # was selected or not.
            if not isinstance(topicnames, list):
                # Assume it was a bare string, so listify it
                topicnames = [topicnames]
            # unquote the topic names
            topicnames = [urllib.parse.unquote_plus(n) for n in topicnames]

        # The standard sigterm handler (see above)
        def sigterm_handler(signum, frame, mlist=mlist):
            mlist.Unlock()
            sys.exit(0)

        # Now, lock the list and perform the changes
        mlist.Lock()
        try:
            signal.signal(signal.SIGTERM, sigterm_handler)
            # `values' is a tuple of flags and the web values
            for flag, newval in newvals:
                # Handle language settings differently
                if flag == SETLANGUAGE:
                    mlist.setMemberLanguage(user, newval)
                # Handle delivery status separately
                elif flag == mm_cfg.DisableDelivery:
                    mlist.setDeliveryStatus(user, newval)
                else:
                    try:
                        mlist.setMemberOption(user, flag, newval)
                    except Errors.CantDigestError:
                        cantdigest = 1
                    except Errors.MustDigestError:
                        mustdigest = 1
            # Set the topics information.
            mlist.setMemberTopics(user, topicnames)
            mlist.Save()
        finally:
            mlist.Unlock()

        # A bag of attributes for the global options
        class Global:
            enable = None
            remind = None
            nodupes = None
            mime = None

            def __bool__(self):
                return len(list(self.__dict__.keys())) > 0

        globalopts = Global()

        # The enable/disable option and the password remind option may have
        # their global flags sets.
        if cgidata.getfirst('deliver-globally'):
            # Yes, this is inefficient, but the list is so small it shouldn't
            # make much of a difference.
            for flag, newval in newvals:
                if flag == mm_cfg.DisableDelivery:
                    globalopts.enable = newval
                    break

        if cgidata.getfirst('remind-globally'):
            for flag, newval in newvals:
                if flag == mm_cfg.SuppressPasswordReminder:
                    globalopts.remind = newval
                    break

        if cgidata.getfirst('nodupes-globally'):
            for flag, newval in newvals:
                if flag == mm_cfg.DontReceiveDuplicates:
                    globalopts.nodupes = newval
                    break

        if cgidata.getfirst('mime-globally'):
            for flag, newval in newvals:
                if flag == mm_cfg.DisableMime:
                    globalopts.mime = newval
                    break

        # Change options globally, but only if this is the user or site admin,
        # /not/ if this is the list admin.
        if globalopts:
            if not is_user_or_siteadmin:
                doc.addError(
                    _("""The list administrator may not change the
                options for this user's other subscriptions.  However the
                options for this mailing list subscription has been
                changed."""), _('Note: '))
            else:
                for gmlist in lists_of_member(mlist, user):
                    global_options(gmlist, user, globalopts)

        # Now print the results
        if cantdigest:
            msg = _('''The list administrator has disabled digest delivery for
            this list, so your delivery option has not been set.  However your
            other options have been set successfully.''')
        elif mustdigest:
            msg = _('''The list administrator has disabled non-digest delivery
            for this list, so your delivery option has not been set.  However
            your other options have been set successfully.''')
        else:
            msg = _('You have successfully set your options.')

        if digestwarn:
            msg += _('You may get one last digest.')

        options_page(mlist, doc, user, cpuser, userlang, msg)
        print(doc.Format())
        return

    if mlist.isMember(user):
        options_page(mlist, doc, user, cpuser, userlang)
    else:
        loginpage(mlist, doc, user, userlang)
    print(doc.Format())
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()
Пример #18
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()
Пример #19
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())