diff options
| author | bwarsaw | 2006-09-24 16:49:18 +0000 |
|---|---|---|
| committer | bwarsaw | 2006-09-24 16:49:18 +0000 |
| commit | 3da41d89a8c827383b2256b965fa1be8b179b9cd (patch) | |
| tree | 04f49d3dab6e586be4769eab29d10618bf8378ea /Mailman/bin/check_perms.py | |
| parent | ad16650e9806bcf864325626da59b93e64ce47ea (diff) | |
| download | mailman-3da41d89a8c827383b2256b965fa1be8b179b9cd.tar.gz mailman-3da41d89a8c827383b2256b965fa1be8b179b9cd.tar.zst mailman-3da41d89a8c827383b2256b965fa1be8b179b9cd.zip | |
Diffstat (limited to 'Mailman/bin/check_perms.py')
| -rwxr-xr-x | Mailman/bin/check_perms.py | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/Mailman/bin/check_perms.py b/Mailman/bin/check_perms.py new file mode 100755 index 000000000..c56171b49 --- /dev/null +++ b/Mailman/bin/check_perms.py @@ -0,0 +1,412 @@ +# Copyright (C) 1998-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# 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 os +import sys +import pwd +import grp +import errno +import optparse + +from stat import * + +from Mailman import Version +from Mailman.configuration import config +from Mailman.i18n import _ + +__i18n_templates__ = True + + +# XXX Need to check the archives/private/*/database/* files + + + +class State: + FIX = False + VERBOSE = False + 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 +MBOXPERMS = S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR + + + +def statmode(path): + return os.stat(path).st_mode + + +def statgidmode(path): + stat = os.stat(path) + return stat.st_mode, stat.st_gid + + +seen = {} + +# libc's getgrgid re-opens /etc/group each time :( +_gidcache = {} + +def getgrgid(gid): + data = _gidcache.get(gid) + if data is None: + data = grp.getgrgid(gid) + _gidcache[gid] = data + return data + + + +def checkwalk(arg, dirname, names): + # Short-circuit duplicates + if seen.has_key(dirname): + return + seen[dirname] = True + 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 OSError, e: + if e.errno <> errno.ENOENT: raise + continue + if gid <> MAILMAN_GID: + try: + groupname = getgrgid(gid)[0] + except KeyError: + groupname = '<anon gid %d>' % gid + arg.ERRORS += 1 + print _( + '$path bad group (has: $groupname, expected $MAILMAN_GROUP)'), + 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 = config.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, config.QUEUE_DIR)) == config.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: $path'), + 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: $path'), + 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: $path'), + arg.ERRORS += 1 + if STATE.FIX: + print _('(fixing)') + os.chmod(path, mode | ARTICLEFILEPERMS) + else: + print + + + +def checkall(): + # first check PREFIX + if STATE.VERBOSE: + prefix = config.PREFIX + print _('checking mode for $prefix') + dirs = {} + for d in (config.PREFIX, config.EXEC_PREFIX, config.VAR_PREFIX, + config.LOG_DIR): + dirs[d] = True + for d in dirs.keys(): + try: + mode = statmode(d) + except OSError, e: + if e.errno <> errno.ENOENT: raise + print _('WARNING: directory does not exist: $d') + continue + if (mode & DIRPERMS) <> DIRPERMS: + STATE.ERRORS += 1 + print _('directory must be at least 02775: $d'), + if STATE.FIX: + print _('(fixing)') + os.chmod(d, mode | DIRPERMS) + else: + print + # check all subdirs + os.path.walk(d, checkwalk, STATE) + + + +def checkarchives(): + private = config.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 += 1 + print _('$private must not be other-readable'), + if STATE.FIX: + print _('(fixing)') + os.chmod(private, mode & ~S_IROTH) + else: + print + # In addition, on a multiuser system you may want to hide the private + # archives so other users can't read them. + if mode & S_IXOTH: + print _("""\ +Warning: Private archive directory is other-executable (o+x). + This could allow other users on your system to read private archives. + If you're on a shared multiuser system, you should consult the + installation manual on how to fix this.""") + + + +def checkmboxfile(mboxdir): + absdir = os.path.join(config.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(config.PRIVATE_ARCHIVE_FILE_DIR): + if dir.endswith('.mbox'): + checkmboxfile(dir) + dbdir = os.path.join(config.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 "other" perms must be 000'), + if STATE.FIX: + print _('(fixing)') + os.chmod(dbdir, mode & ~S_IRWXO) + else: + print + + + +def checkcgi(): + cgidir = os.path.join(config.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') + mode = statmode(path) + if mode & S_IXGRP and not mode & S_ISGID: + 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(config.WRAPPER_DIR, 'mailman') + if STATE.VERBOSE: + print _('checking set-gid for $wrapper') + mode = statmode(wrapper) + if not mode & S_ISGID: + STATE.ERRORS += 1 + print _('$wrapper must be set-gid'), + if STATE.FIX: + print _('(fixing)') + os.chmod(wrapper, mode | S_ISGID) + + + +def checkadminpw(): + for pwfile in (os.path.join(config.DATA_DIR, 'adm.pw'), + os.path.join(config.DATA_DIR, 'creator.pw')): + targetmode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP + if STATE.VERBOSE: + print _('checking permissions on $pwfile') + 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 permissions must be exactly 0640 (got $octmode)'), + if STATE.FIX: + print _('(fixing)') + os.chmod(pwfile, targetmode) + else: + print + + +def checkmta(): + if config.MTA: + modname = 'Mailman.MTA.' + config.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') + for dir in os.listdir(config.LIST_DATA_DIR): + for file in checkfiles: + path = os.path.join(config.LIST_DATA_DIR, dir, file) + if STATE.VERBOSE: + print _(' checking permissions on: $path') + 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'), + if STATE.FIX: + print _('(fixing)') + os.chmod(path, mode | targetmode) + else: + print + + + +def parseargs(): + parser = optparse.OptionParser(version=Version.MAILMAN_VERSION, + usage=_("""\ +%prog [options] + +Check the permissions of all Mailman files. With no options, just report the +permission and ownership problems found.""")) + parser.add_option('-f', '--fix', + default=False, action='store_true', + help=_("""\ +Fix all permission and ownership problems found. With this option, you must +run check_perms as root.""")) + parser.add_option('-v', '--verbose', + default=False, action='store_true', + help=_('Produce more verbose output')) + parser.add_option('-C', '--config', + help=_('Alternative configuration file to use')) + opts, args = parser.parse_args() + if args: + parser.print_help() + print >> sys.stderr, _('Unexpected arguments') + sys.exit(1) + return parser, opts, args + + + +def main(): + global MAILMAN_USER, MAILMAN_GROUP, MAILMAN_UID, MAILMAN_GID + + parser, opts, args = parseargs() + STATE.FIX = opts.fix + STATE.VERBOSE = opts.verbose + + config.load(opts.config) + + MAILMAN_USER = config.MAILMAN_USER + MAILMAN_GROUP = config.MAILMAN_GROUP + # Let KeyErrors percolate + MAILMAN_GID = grp.getgrnam(MAILMAN_GROUP).gr_gid + MAILMAN_UID = pwd.getpwnam(MAILMAN_USER).pw_uid + + 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 (or root) with -f flag to fix') + + +if __name__ == '__main__': + main() |
