diff options
Diffstat (limited to 'bin/check_perms')
-rwxr-xr-x | bin/check_perms | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/bin/check_perms b/bin/check_perms new file mode 100755 index 00000000..44fbe547 --- /dev/null +++ b/bin/check_perms @@ -0,0 +1,362 @@ +#! @PYTHON@ +# +# Copyright (C) 1998,1999,2000,2001,2002 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 pwd +import grp +from stat import * + +try: + import paths +except ImportError: + print '''Could not import paths! + +This probably means that you are trying to run check_perms from the source +directory. You must run this from the installation directory instead. +''' + raise +from Mailman import mm_cfg +from Mailman.mm_cfg import MAILMAN_USER, MAILMAN_GROUP +from Mailman.i18n import _ + +# Let KeyErrors percolate +MAILMAN_GID = grp.getgrnam(MAILMAN_GROUP)[2] +MAILMAN_UID = pwd.getpwnam(MAILMAN_USER)[2] + +PROGRAM = sys.argv[0] + +# Gotta check the archives/private/*/database/* files + + + +class State: + FIX = 0 + VERBOSE = 0 + ERRORS = 0 + +STATE = State() + +DIRPERMS = S_ISGID | S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH +QFILEPERMS = S_ISGID | S_IRWXU | S_IRWXG +PYFILEPERMS = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH +ARTICLEFILEPERMS = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP + + + +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)s') + try: + mode, gid = statgidmode(path) + except OSError, e: + if e.errno <> errno.ENOENT: raise + continue + if gid <> MAILMAN_GID: + try: + groupname = grp.getgrgid(gid)[0] + except KeyError: + groupname = '<anon gid %d>' % gid + arg.ERRORS += 1 + print _('%(path)s bad group (has: %(groupname)s, ' + 'expected %(MAILMAN_GROUP)s)'), + 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() and checkarchivedbs() 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 + # The directories under qfiles should have a more limited permission + if os.path.commonprefix((path, mm_cfg.QUEUE_DIR)) == mm_cfg.QUEUE_DIR: + targetperms = QFILEPERMS + octperms = oct(targetperms) + else: + targetperms = DIRPERMS + octperms = oct(targetperms) + if S_ISDIR(mode) and (mode & targetperms) <> targetperms: + arg.ERRORS += 1 + print _('directory permissions must be %(octperms)s: %(path)s'), + if STATE.FIX: + print _('(fixing)') + os.chmod(path, mode | targetperms) + else: + print + elif os.path.splitext(path)[1] in ('.py', '.pyc', '.pyo'): + octperms = oct(PYFILEPERMS) + if mode & PYFILEPERMS <> PYFILEPERMS: + print _('source perms must be %(octperms)s: %(path)s'), + arg.ERRORS += 1 + if STATE.FIX: + print _('(fixing)') + os.chmod(path, mode | PYFILEPERMS) + else: + print + elif path.endswith('-article'): + # Article files must be group writeable + octperms = oct(ARTICLEFILEPERMS) + if mode & ARTICLEFILEPERMS <> ARTICLEFILEPERMS: + print _('article db files must be %(octperms)s: %(path)s'), + arg.ERRORS += 1 + if STATE.FIX: + print _('(fixing)') + os.chmod(path, mode | ARTICLEFILEPERMS) + else: + print + +def checkall(): + # first check PREFIX + if STATE.VERBOSE: + prefix = mm_cfg.PREFIX + print _('checking mode for %(prefix)s') + dirs = {} + for d in (mm_cfg.PREFIX, mm_cfg.EXEC_PREFIX, mm_cfg.VAR_PREFIX): + dirs[d] = 1 + for d in dirs.keys(): + mode = statmode(d) + if (mode & DIRPERMS) <> DIRPERMS: + STATE.ERRORS += 1 + print _('directory must be at least 02775: %(d)s'), + if STATE.FIX: + print _('(fixing)') + os.chmod(d, mode | DIRPERMS) + else: + print + # check all subdirs + os.path.walk(d, checkwalk, STATE) + + +def checkarchives(): + private = mm_cfg.PRIVATE_ARCHIVE_FILE_DIR + if STATE.VERBOSE: + print _('checking perms on %(private)s') + # private archives must not be other readable + mode = statmode(private) + if mode & S_IROTH: + STATE.ERRORS += 1 + print _('%(private)s must not be other-readable'), + if STATE.FIX: + print _('(fixing)') + os.chmod(private, mode & ~S_IROTH) + else: + print + + +MBOXPERMS = S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR + + +def checkmboxfile(mboxdir): + absdir = os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, mboxdir) + for f in os.listdir(absdir): + if not f.endswith('.mbox'): + continue + mboxfile = os.path.join(absdir, f) + mode = statmode(mboxfile) + if (mode & MBOXPERMS) <> MBOXPERMS: + STATE.ERRORS = STATE.ERRORS + 1 + print _('mbox file must be at least 0660:'), mboxfile + if STATE.FIX: + print _('(fixing)') + os.chmod(mboxfile, mode | MBOXPERMS) + 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.endswith('.mbox'): + checkmboxfile(dir) + dbdir = os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, dir, 'database') + try: + mode = statmode(dbdir) + except OSError, e: + if e.errno not in (errno.ENOENT, errno.ENOTDIR): raise + continue + if mode & S_IRWXO: + STATE.ERRORS += 1 + print _('%(dbdir)s "other" perms must be 000'), + if STATE.FIX: + print _('(fixing)') + os.chmod(dbdir, mode & ~S_IRWXO) + else: + print + + +def checkcgi(): + cgidir = os.path.join(mm_cfg.EXEC_PREFIX, 'cgi-bin') + if STATE.VERBOSE: + print _('checking cgi-bin permissions') + exes = os.listdir(cgidir) + for f in exes: + path = os.path.join(cgidir, f) + if STATE.VERBOSE: + print _(' checking set-gid for %(path)s') + mode = statmode(path) + if mode & S_IXGRP and not mode & S_ISGID: + STATE.ERRORS += 1 + print _('%(path)s 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, 'mailman') + if STATE.VERBOSE: + print _('checking set-gid for %(wrapper)s') + mode = statmode(wrapper) + if not mode & S_ISGID: + STATE.ERRORS += 1 + print _('%(wrapper)s must be set-gid'), + if STATE.FIX: + print _('(fixing)') + os.chmod(wrapper, mode | S_ISGID) + +def checkadminpw(): + for pwfile in (os.path.join(mm_cfg.DATA_DIR, 'adm.pw'), + os.path.join(mm_cfg.DATA_DIR, 'creator.pw')): + targetmode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP + if STATE.VERBOSE: + print _('checking permissions on %(pwfile)s') + try: + mode = statmode(pwfile) + except OSError, e: + if e.errno <> errno.ENOENT: raise + return + if mode <> targetmode: + STATE.ERRORS += 1 + octmode = oct(mode) + print _('%(pwfile)s permissions must be exactly 0640 ' + '(got %(octmode)s)'), + if STATE.FIX: + print _('(fixing)') + os.chmod(pwfile, targetmode) + else: + print + +def checkmta(): + if mm_cfg.MTA: + modname = 'Mailman.MTA.' + mm_cfg.MTA + __import__(modname) + try: + sys.modules[modname].checkperms(STATE) + except AttributeError: + pass + +def checkdata(): + targetmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP + checkfiles = ('config.pck', 'config.pck.last', + 'config.db', 'config.db.last', + 'next-digest', 'next-digest-topics', + 'request.db', 'request.db.tmp') + if STATE.VERBOSE: + print _('checking permissions on list data') + # BAW: This needs to be converted to the Site module abstraction + for dir in os.listdir(mm_cfg.LIST_DATA_DIR): + for file in checkfiles: + path = os.path.join(mm_cfg.LIST_DATA_DIR, dir, file) + if STATE.VERBOSE: + print _(' checking permissions on: %(path)s') + try: + mode = statmode(path) + except OSError, e: + if e.errno <> errno.ENOENT: raise + continue + if (mode & targetmode) <> targetmode: + STATE.ERRORS += 1 + print _('file permissions must be at least 660: %(path)s'), + if STATE.FIX: + print _('(fixing)') + os.chmod(path, mode | targetmode) + else: + print + + + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print >> fd, _(__doc__) + if msg: + print >> fd, 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(0) + elif opt in ('-f', '--fix'): + STATE.FIX = 1 + elif opt in ('-v', '--verbose'): + STATE.VERBOSE = 1 + + checkall() + checkarchives() + checkarchivedbs() + checkcgi() + checkmail() + checkdata() + checkadminpw() + checkmta() + + if not STATE.ERRORS: + print _('No problems found') + else: + print _('Problems found:'), STATE.ERRORS + print _('Re-run as %(MAILMAN_USER)s (or root) with -f flag to fix') |