Re: checking permissions for mailman

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Scott Berry wrote:
> Tod could you send me a copy of your check_perms please.  I tried
> changing that but it still provides me with the same error.

It's attached.  You can check that the only difference in the
attached file and the installed is the _( change by using diff.  That
way you don't have to worry that I've slipped in something nefarious.

diff -u /usr/lib/mailman/bin/check_perms /path/to/attached/check_perms

The ouput should look like this:

--- check_perms~        2007-01-29 09:20:43.000000000 -0500
+++ check_perms 2007-07-08 12:09:34.000000000 -0400
@@ -213,7 +213,7 @@
     # 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 _("""\
+        print C_("""\
 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

The minus represents lines from the original file, the plus is for
lines from the new file.

> Also what permission is check_perms supposed to be set at?

It should be 755, owner root, group mailman.  You can check what it
should be using rpm like so:

$ rpm -qlv mailman | grep /usr/lib/mailman/bin/check_perms
-rwxr-xr-x 1 root mailman 12904 Jan 29 09:20 /usr/lib/mailman/bin/check_perms

If you're using SELinux you should be careful if you copy the attached
file that the security contexts get set properly.  I'm sure there are
a few ways to do this.  I think one of the easier ways is:

# restorecon -v /usr/lib/mailman/bin/check_perms

And, if you're using SELinux, there may be other issues you'll run
into while getting mailman and postfix integrated.  I did when I tried
a quick test in FC6.  I didn't take the time to resolve those issues
then because I was only doing some quick testing.  The production
systems I have using mailman have not yet been upgraded to anything
new enough to have SELinux on them yet.  (They will be soon though and
I'm planning to work out whatever issues I have then so that I can
keep SELinux enabled.)

-- 
Todd        OpenPGP -> KeyID: 0xBEAF0CE3 | URL: www.pobox.com/~tmz/pgp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes I think I understand everything, then I regain
consciousness.

#! /usr/bin/python
#
# Copyright (C) 1998-2005 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 getopt
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 C_

# 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

try:
    True, False
except NameError:
    True = 1
    False = 0



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



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 C_('    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 = getgrgid(gid)[0]
            except KeyError:
                groupname = '<anon gid %d>' % gid
            arg.ERRORS += 1
            print C_('%(path)s bad group (has: %(groupname)s, '
                     'expected %(MAILMAN_GROUP)s)'),
            if STATE.FIX:
                print C_('(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 C_('directory permissions must be %(octperms)s: %(path)s'),
            if STATE.FIX:
                print C_('(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 C_('source perms must be %(octperms)s: %(path)s'),
                arg.ERRORS += 1
                if STATE.FIX:
                    print C_('(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 C_('article db files must be %(octperms)s: %(path)s'),
                arg.ERRORS += 1
                if STATE.FIX:
                    print C_('(fixing)')
                    os.chmod(path, mode | ARTICLEFILEPERMS)
                else:
                    print

def checkall():
    # first check PREFIX
    if STATE.VERBOSE:
        prefix = mm_cfg.PREFIX
        print C_('checking mode for %(prefix)s')
    dirs = {}
    for d in (mm_cfg.PREFIX, mm_cfg.EXEC_PREFIX, mm_cfg.VAR_PREFIX,
              mm_cfg.CONFIG_DIR, mm_cfg.DATA_DIR, mm_cfg.LOCK_DIR,
              mm_cfg.LOG_DIR, mm_cfg.QUEUE_DIR, mm_cfg.PID_DIR):
        dirs[d] = True
    for d in dirs.keys():
        try:
            mode = statmode(d)
        except OSError, e:
            if e.errno <> errno.ENOENT: raise
            print C_('WARNING: directory does not exist: %(d)s')
            continue
        if (mode & DIRPERMS) <> DIRPERMS:
            STATE.ERRORS += 1
            print C_('directory must be at least 02775: %(d)s'),
            if STATE.FIX:
                print C_('(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 C_('checking perms on %(private)s')
    # private archives must not be other readable
    mode = statmode(private)
    if mode & S_IROTH:
        STATE.ERRORS += 1
        print C_('%(private)s must not be other-readable'),
        if STATE.FIX:
            print C_('(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 C_("""\
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.""")

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 C_('mbox file must be at least 0660:'), mboxfile
            if STATE.FIX:
                print C_('(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 C_('%(dbdir)s "other" perms must be 000'),
            if STATE.FIX:
                print C_('(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 C_('checking cgi-bin permissions')
    exes = os.listdir(cgidir)
    for f in exes:
        path = os.path.join(cgidir, f)
        if STATE.VERBOSE:
            print C_('    checking set-gid for %(path)s')
        mode = statmode(path)
        if mode & S_IXGRP and not mode & S_ISGID:
            STATE.ERRORS += 1
            print C_('%(path)s must be set-gid'),
            if STATE.FIX:
                print C_('(fixing)')
                os.chmod(path, mode | S_ISGID)
            else:
                print

def checkmail():
    wrapper = os.path.join(mm_cfg.WRAPPER_DIR, 'mailman')
    if STATE.VERBOSE:
        print C_('checking set-gid for %(wrapper)s')
    mode = statmode(wrapper)
    if not mode & S_ISGID:
        STATE.ERRORS += 1
        print C_('%(wrapper)s must be set-gid'),
        if STATE.FIX:
            print C_('(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 C_('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 C_('%(pwfile)s permissions must be exactly 0640 '
                     '(got %(octmode)s)'),
            if STATE.FIX:
                print C_('(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 C_('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 C_('    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 C_('file permissions must be at least 660: %(path)s'),
                if STATE.FIX:
                    print C_('(fixing)')
                    os.chmod(path, mode | targetmode)
                else:
                    print



def usage(code, msg=''):
    if code:
        fd = sys.stderr
    else:
        fd = sys.stdout
    print >> fd, C_(__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 = True
        elif opt in ('-v', '--verbose'):
            STATE.VERBOSE = True

    checkall()
    checkarchives()
    checkarchivedbs()
    checkcgi()
    checkmail()
    checkdata()
    checkadminpw()
    checkmta()

    if not STATE.ERRORS:
        print C_('No problems found')
    else:
        print C_('Problems found:'), STATE.ERRORS
        print C_('Re-run as %(MAILMAN_USER)s (or root) with -f flag to fix')

Attachment: pgp2y9KM2ENIO.pgp
Description: PGP signature


[Index of Archives]     [Current Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Yosemite News]     [Yosemite Photos]     [KDE Users]     [Fedora Tools]     [Fedora Docs]

  Powered by Linux