aboutsummaryrefslogtreecommitdiffstats
path: root/bin/check_perms
diff options
context:
space:
mode:
Diffstat (limited to 'bin/check_perms')
-rwxr-xr-xbin/check_perms362
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')