[Mailman-Developers] check_perms bug?...
Barry A. Warsaw
bwarsaw@cnri.reston.va.us
Wed, 5 Apr 2000 19:40:10 -0400 (EDT)
>>>>> "DM" == Dan Mick <Dan.Mick@West.Sun.COM> writes:
DM> Somehow, my /home/mailman/lists directory managed to get set
DM> with permissions check_perms didn't check for; it was set to
DM> d-wx-ws--x/mailman/mailman, which caused mailcmd to be unable
DM> to list the directory (no read perms).
DM> I fixed it once I figured it out, but I had a lot of
DM> check_perms runs in there before I finally figured it
DM> out...mostly because I was confused about what 'x' really
DM> means on a directory. But check_perms could have saved me
DM> time if it had checked for 'owner-and-group-read'; I think it
DM> should.
You're right. Here's the check_perms that will be in 2.0beta2.
-Barry
-------------------- snip snip --------------------
#! /usr/bin/env python
#
# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Check the permissions for the Mailman installation.
Usage: %(PROGRAM)s [-f] [-v] [-h]
With no arguments, just check and report all the files that have bogus
permissions or group ownership. With -f (and run as root), fix all the
permission problems found. With -v be verbose.
"""
import sys
import os
import errno
import getopt
import grp
from stat import *
import paths
from Mailman import mm_cfg
MAILMAN_GRPNAME = 'mailman'
MAILMAN_GID = grp.getgrnam(MAILMAN_GRPNAME)[2]
PROGRAM = sys.argv[0]
class State:
FIX = 0
VERBOSE = 0
ERRORS = 0
STATE = State()
DIRPERMS = S_ISGID | S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH
def statmode(path):
return os.stat(path)[ST_MODE]
def statgidmode(path):
stat = os.stat(path)
return stat[ST_MODE], stat[ST_GID]
def checkwalk(arg, dirname, names):
for name in names:
path = os.path.join(dirname, name)
if arg.VERBOSE:
print 'checking gid and mode for', path
try:
mode, gid = statgidmode(path)
except os.error, (code, msg):
if code == errno.ENOENT:
continue
raise
if gid <> MAILMAN_GID:
try:
groupname = grp.getgrgid(gid)[0]
except KeyError:
groupname = '<anon gid %d>' % gid
arg.ERRORS = arg.ERRORS + 1
print path, 'bad gid (has: %s, expected %s)' % (
groupname, MAILMAN_GRPNAME),
if STATE.FIX:
print '(fixing)'
os.chown(path, -1, MAILMAN_GID)
else:
print
# all directories must be at least rwxrwsr-x. Don't check the private
# archive directory or database directory themselves since these are
# checked in checkarchives below.
private = mm_cfg.PRIVATE_ARCHIVE_FILE_DIR
if path == private or (os.path.commonprefix((path, private)) == private
and os.path.split(path)[1] == 'database'):
continue
if S_ISDIR(mode) and (mode & DIRPERMS) <> DIRPERMS:
arg.ERRORS = arg.ERRORS + 1
print 'directory must be at least 02775:', path,
if STATE.FIX:
print '(fixing)'
os.chmod(path, mode | DIRPERMS)
else:
print
def checkall():
# first check PREFIX
if STATE.VERBOSE:
print 'checking mode for', mm_cfg.PREFIX,
mode = statmode(mm_cfg.PREFIX)
if (mode & DIRPERMS) <> DIRPERMS:
STATE.ERRORS = STATE.ERRORS + 1
print 'directory must be at least 02775:', mm_cfg.PREFIX,
if STATE.FIX:
print '(fixing)'
os.chmod(mm_cfg.PREFIX, mode | DIRPERMS)
else:
print
# check all subdirs
os.path.walk(mm_cfg.PREFIX, checkwalk, STATE)
def checkarchives():
private = mm_cfg.PRIVATE_ARCHIVE_FILE_DIR
if STATE.VERBOSE:
print 'checking perms on', private
# private archives must not be other readable
mode = statmode(private)
if mode & S_IROTH:
STATE.ERRORS = STATE.ERRORS + 1
print private, 'must not be other-readable',
if STATE.FIX:
print '(fixing)'
os.chmod(private, mode & ~S_IROTH)
else:
print
def checkarchivedbs():
# The archives/private/listname/database file must not be other readable
# or executable otherwise those files will be accessible when the archives
# are public. That may not be a horrible breach, but let's close this off
# anyway.
for dir in os.listdir(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR):
if dir[-5:] == '.mbox':
continue
dbdir = os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, dir, 'database')
try:
mode = statmode(dbdir)
except os.error, (code, msg):
if code == errno.ENOENT:
continue
raise
if mode & S_IRWXO:
STATE.ERRORS = STATE.ERRORS + 1
print dbdir, 'must be other 000',
if STATE.FIX:
print '(fixing)'
os.chmod(dbdir, mode & ~S_IRWXO)
else:
print
def checkcgi():
exes = os.listdir(mm_cfg.CGI_DIR)
for f in exes:
path = os.path.join(mm_cfg.CGI_DIR, f)
if STATE.VERBOSE:
print 'checking set-gid for', path
mode = statmode(path)
if mode & S_IXGRP and not mode & S_ISGID:
STATE.ERRORS = STATE.ERRORS + 1
print path, 'must be set-gid',
if STATE.FIX:
print '(fixing)'
os.chmod(path, mode | S_ISGID)
else:
print
def checkmail():
wrapper = os.path.join(mm_cfg.WRAPPER_DIR, 'wrapper')
if STATE.VERBOSE:
print 'checking set-gid for', wrapper
mode = statmode(wrapper)
if not mode & S_ISGID:
STATE.ERRORS = STATE.ERRORS + 1
print wrapper, 'must be set-gid',
if STATE.FIX:
print '(fixing)'
os.chmod(wrapper, mode | S_ISGID)
def checkadminpw():
adminpw = os.path.join(mm_cfg.DATA_DIR, 'adm.pw')
targetmode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP
if STATE.VERBOSE:
print 'checking perms on', adminpw
try:
mode = statmode(adminpw)
except os.error, (code, msg):
# adm.pw may not exist
if code == errno.ENOENT:
return
raise
if mode <> targetmode:
STATE.ERRORS = STATE.ERRORS + 1
print adminpw, 'permissions must be exactly 0640 (got %s)' % oct(mode)
if STATE.FIX:
print '(fixing)'
os.chmod(adminpw, targetmode)
def usage(code=0, msg=''):
print __doc__ % globals()
if msg:
print msg
sys.exit(code)
if __name__ == '__main__':
try:
opts, args = getopt.getopt(sys.argv[1:],
'fvh',
['fix', 'verbose', 'help'])
except getopt.error, msg:
usage(1, msg)
for opt, arg in opts:
if opt in ('-h', '--help'):
usage()
elif opt in ('-f', '--fix'):
STATE.FIX = 1
elif opt in ('-v', '--verbose'):
STATE.VERBOSE = 1
checkall()
checkarchives()
checkarchivedbs()
checkcgi()
checkmail()
checkadminpw()
if not STATE.ERRORS:
print 'No problems found'
else:
print 'Problems found:', STATE.ERRORS
print 'Re-run as root with -f flag until no errors are found'