~msapiro/mailman/topics

« back to all changes in this revision

Viewing changes to Mailman/Cgi/admin.py

  • Committer: Mark Sapiro
  • Date: 2014-12-03 23:47:23 UTC
  • mfrom: (1006.1.356 2.2)
  • Revision ID: mark@msapiro.net-20141203234723-3pcwx85xua4n84yx
Merged the 2.2 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
 
1
# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
2
2
#
3
3
# This program is free software; you can redistribute it and/or
4
4
# modify it under the terms of the GNU General Public License
41
41
from Mailman.Cgi import Auth
42
42
from Mailman.Logging.Syslog import syslog
43
43
from Mailman.Utils import sha_new
 
44
from Mailman.CSRFcheck import csrf_check
44
45
 
45
46
# Set up i18n
46
47
_ = i18n._
55
56
    True = 1
56
57
    False = 0
57
58
 
 
59
AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin)
 
60
 
58
61
 
59
62
 
60
63
def main():
71
74
    except Errors.MMListError, e:
72
75
        # Avoid cross-site scripting attacks
73
76
        safelistname = Utils.websafe(listname)
 
77
        # Send this with a 404 status.
 
78
        print 'Status: 404 Not Found'
74
79
        admin_overview(_('No such list <em>%(safelistname)s</em>'))
75
 
        syslog('error', 'admin.py access for non-existent list: %s',
76
 
               listname)
 
80
        syslog('error', 'admin: No such list "%s": %s\n',
 
81
               listname, e)
77
82
        return
78
83
    # Now that we know what list has been requested, all subsequent admin
79
84
    # pages are shown in that list's preferred language.
81
86
    # If the user is not authenticated, we're done.
82
87
    cgidata = cgi.FieldStorage(keep_blank_values=1)
83
88
 
 
89
    # CSRF check
 
90
    safe_params = ['VARHELP', 'adminpw', 'admlogin',
 
91
                   'letter', 'chunk', 'findmember',
 
92
                   'legend']
 
93
    params = cgidata.keys()
 
94
    if set(params) - set(safe_params):
 
95
        csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
 
96
    else:
 
97
        csrf_checked = True
 
98
    # if password is present, void cookie to force password authentication.
 
99
    if cgidata.getvalue('adminpw'):
 
100
        os.environ['HTTP_COOKIE'] = ''
 
101
        csrf_checked = True
 
102
 
84
103
    if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
85
104
                                  mm_cfg.AuthSiteAdmin),
86
105
                                 cgidata.getvalue('adminpw', '')):
105
124
 
106
125
    # Is this a log-out request?
107
126
    if category == 'logout':
 
127
        # site-wide admin should also be able to logout.
 
128
        if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site':
 
129
            print mlist.ZapCookie(mm_cfg.AuthSiteAdmin)
108
130
        print mlist.ZapCookie(mm_cfg.AuthListAdmin)
109
131
        Auth.loginpage(mlist, 'admin', frontpage=1)
110
132
        return
169
191
        signal.signal(signal.SIGTERM, sigterm_handler)
170
192
 
171
193
        if cgidata.keys():
172
 
            # There are options to change
173
 
            change_options(mlist, category, subcat, cgidata, doc)
 
194
            if csrf_checked:
 
195
                # There are options to change
 
196
                change_options(mlist, category, subcat, cgidata, doc)
 
197
            else:
 
198
                doc.addError(
 
199
                  _('The form lifetime has expired. (request forgery check)'))
174
200
            # Let the list sanity check the changed values
175
201
            mlist.CheckValues()
176
202
        # Additional sanity checks
182
208
                non-digest delivery or your mailing list will basically be
183
209
                unusable.'''), tag=_('Warning: '))
184
210
 
185
 
        if not mlist.digestable and mlist.getDigestMemberKeys():
 
211
        dm = mlist.getDigestMemberKeys()
 
212
        if not mlist.digestable and dm:
186
213
            doc.addError(
187
214
                _('''You have digest members, but digests are turned
188
 
                off. Those people will not receive mail.'''),
 
215
                off. Those people will not receive mail.
 
216
                Affected member(s) %(dm)r.'''),
189
217
                tag=_('Warning: '))
190
 
        if not mlist.nondigestable and mlist.getRegularMemberKeys():
 
218
        rm = mlist.getRegularMemberKeys()
 
219
        if not mlist.nondigestable and rm:
191
220
            doc.addError(
192
221
                _('''You have regular list members but non-digestified mail is
193
 
                turned off.  They will receive mail until you fix this
194
 
                problem.'''), tag=_('Warning: '))
 
222
                turned off.  They will receive non-digestified mail until you
 
223
                fix this problem. Affected member(s) %(rm)r.'''),
 
224
                tag=_('Warning: '))
195
225
        # Glom up the results page and print it out
196
226
        show_results(mlist, doc, category, subcat, cgidata)
197
227
        print doc.Format()
231
261
    for name in listnames:
232
262
        mlist = MailList.MailList(name, lock=0)
233
263
        if mlist.advertised:
234
 
            if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
235
 
                   mlist.web_page_url.find('/%s/' % hostname) == -1:
 
264
            if mm_cfg.VIRTUAL_HOST_OVERVIEW and (
 
265
                   mlist.web_page_url.find('/%s/' % hostname) == -1 and
 
266
                   mlist.web_page_url.find('/%s:' % hostname) == -1):
236
267
                # List is for different identity of this host - skip it.
237
268
                continue
238
269
            else:
243
274
    if msg:
244
275
        greeting = FontAttr(msg, color="ff5060", size="+1")
245
276
    else:
246
 
        greeting = _("Welcome!")
 
277
        greeting = FontAttr(_('Welcome!'), size='+2')
247
278
 
248
279
    welcome = []
249
280
    mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format()
352
383
        url = '%s/%s/%s' % (mlist.GetScriptURL('admin'), category, subcat)
353
384
    else:
354
385
        url = '%s/%s' % (mlist.GetScriptURL('admin'), category)
355
 
    form = Form(url)
 
386
    form = Form(url, mlist=mlist, contexts=AUTH_CONTEXTS)
356
387
    valtab = Table(cellspacing=3, cellpadding=4, width='100%')
357
388
    add_options_table_item(mlist, category, subcat, valtab, item, detailsp=0)
358
389
    form.AddItem(valtab)
398
429
        encoding = 'multipart/form-data'
399
430
    if subcat:
400
431
        form = Form('%s/%s/%s' % (adminurl, category, subcat),
401
 
                    encoding=encoding)
 
432
                    encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS)
402
433
    else:
403
 
        form = Form('%s/%s' % (adminurl, category), encoding=encoding)
 
434
        form = Form('%s/%s' % (adminurl, category), 
 
435
                    encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS)
404
436
    # This holds the two columns of links
405
437
    linktable = Table(valign='top', width='100%')
406
438
    linktable.AddRow([Center(Bold(_("Configuration Categories"))),
1120
1152
                continue
1121
1153
            start = chunkmembers[i*chunksz]
1122
1154
            end = chunkmembers[min((i+1)*chunksz, last)-1]
1123
 
            link = Link(url + 'chunk=%d' % i, _('from %(start)s to %(end)s'))
 
1155
            link = Link(url + 'chunk=%d' % i + findfrag,
 
1156
                        _('from %(start)s to %(end)s'))
1124
1157
            buttons.append(link)
1125
1158
        buttons = UnorderedList(*buttons)
1126
1159
        container.AddItem(footer + buttons.Format() + '<p>')
1248
1281
                   PasswordBox('confirmmodpw', size=20)])
1249
1282
    # Add these tables to the overall password table
1250
1283
    table.AddRow([atable, mtable])
 
1284
    table.AddRow([_("""\
 
1285
In addition to the above passwords you may specify a password for
 
1286
pre-approving posts to the list. Either of the above two passwords can
 
1287
be used in an Approved: header or first body line pseudo-header to
 
1288
pre-approve a post that would otherwise be held for moderation. In
 
1289
addition, the password below, if set, can be used for that purpose and
 
1290
no other.""")])
 
1291
    table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
 
1292
    # Set up the post password table
 
1293
    ptable = Table(border=0, cellspacing=3, cellpadding=4,
 
1294
                   bgcolor=mm_cfg.WEB_ADMINPW_COLOR)
 
1295
    ptable.AddRow([Label(_('Enter new poster password:')),
 
1296
                   PasswordBox('newpostpw', size=20)])
 
1297
    ptable.AddRow([Label(_('Confirm poster password:')),
 
1298
                   PasswordBox('confirmpostpw', size=20)])
 
1299
    table.AddRow([ptable])
1251
1300
    return table
1252
1301
 
1253
1302
 
1278
1327
            # password doesn't get you into these pages.
1279
1328
        else:
1280
1329
            doc.addError(_('Moderator passwords did not match'))
 
1330
    # Handle changes to the list poster password.  Do this before checking
 
1331
    # the new admin password, since the latter will force a reauthentication.
 
1332
    new = cgidata.getvalue('newpostpw', '').strip()
 
1333
    confirm = cgidata.getvalue('confirmpostpw', '').strip()
 
1334
    if new or confirm:
 
1335
        if new == confirm:
 
1336
            mlist.post_password = sha_new(new).hexdigest()
 
1337
            # No re-authentication necessary because the poster's
 
1338
            # password doesn't get you into these pages.
 
1339
        else:
 
1340
            doc.addError(_('Poster passwords did not match'))
1281
1341
    # Handle changes to the list administrator password
1282
1342
    new = cgidata.getvalue('newpw', '').strip()
1283
1343
    confirm = cgidata.getvalue('confirmpw', '').strip()
1378
1438
        removals += cgidata['unsubscribees_upload'].value
1379
1439
    if removals:
1380
1440
        names = filter(None, [n.strip() for n in removals.splitlines()])
1381
 
        send_unsub_notifications = int(
1382
 
            cgidata['send_unsub_notifications_to_list_owner'].value)
1383
 
        userack = int(
1384
 
            cgidata['send_unsub_ack_to_this_batch'].value)
 
1441
        send_unsub_notifications = safeint(
 
1442
            'send_unsub_notifications_to_list_owner',
 
1443
            mlist.admin_notify_mchanges)
 
1444
        userack = safeint(
 
1445
            'send_unsub_ack_to_this_batch',
 
1446
            mlist.send_goodbye_msg)
1385
1447
        unsubscribe_errors = []
1386
1448
        unsubscribe_success = []
1387
1449
        for addr in names:
1405
1467
            doc.AddItem('<p>')
1406
1468
    # See if this was a moderation bit operation
1407
1469
    if cgidata.has_key('allmodbit_btn'):
1408
 
        val = cgidata.getvalue('allmodbit_val')
1409
 
        try:
1410
 
            val = int(val)
1411
 
        except VallueError:
1412
 
            val = None
 
1470
        val = safeint('allmodbit_val')
1413
1471
        if val not in (0, 1):
1414
1472
            doc.addError(_('Bad moderation flag value'))
1415
1473
        else: