--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2017 by Frank Brehm, Berlin
+@summary: The base module all maintaining scripts for the home directories
+"""
+from __future__ import absolute_import
+
+# Standard modules
+import sys
+import os
+import logging
+import logging.config
+import re
+import traceback
+import textwrap
+import pwd
+import copy
+import glob
+
+# Third party modules
+import six
+
+# Own modules
+from .global_version import __version__ as __global_version__
+
+from .errors import FunctionNotImplementedError, PpAppError
+
+from .common import pp, terminal_can_colors, to_bytes, to_bool
+
+from .cfg_app import PpCfgAppError, PpConfigApplication
+
+__version__ = '0.1.1'
+LOG = logging.getLogger(__name__)
+
+
+# =============================================================================
+class PpHomesAdminError(PpCfgAppError):
+ pass
+
+
+# =============================================================================
+class PpHomesAdminApp(PpConfigApplication):
+ """
+ Base class for applications maintaining the global Home directories.
+ """
+
+ # /mnt/nfs
+ default_chroot_homedir = os.sep + os.path.join('mnt', 'nfs')
+ # /home
+ default_home_root = os.sep + 'home'
+
+ # /etc/pixelpark/exclude_homes
+ default_exclude_file = os.sep + os.path.join('etc', 'pixelpark', 'exclude_homes')
+
+ comment_re = re.compile(r'\s*#.*')
+
+ # -------------------------------------------------------------------------
+ def __init__(self, appname=None, description=None, version=__version__):
+
+ self.default_mail_recipients = [
+ 'admin.berlin@pixelpark.com'
+ ]
+ self.default_mail_cc = []
+
+ self.chroot_homedir = self.default_chroot_homedir
+ self.home_root_abs = self.default_home_root
+ self.home_root_rel = os.path.relpath(self.home_root_abs, os.sep)
+
+ self.exclude_file = self.default_exclude_file
+
+ self.exclude_dirs = []
+ self.passwd_home_dirs = []
+ self.unnecessary_dirs = []
+
+ super(PpHomesAdminApp, self).__init__(
+ appname=appname, version=version, description=description,
+ cfg_stems='homes-admin'
+ )
+
+ # -------------------------------------------------------------------------
+ def init_arg_parser(self):
+
+ homes_group = self.arg_parser.add_argument_group('Homes administration options.')
+
+ homes_group.add_argument(
+ '-R', '--chroot-dir',
+ metavar='DIR', dest='chroot_homedir',
+ help=("Directory, where the {h!r} share is mounted from the "
+ "NFS server. Maybe '/', default: {d!r}.").format(
+ h=self.default_home_root, d=self.default_chroot_homedir)
+ )
+
+ homes_group.add_argument(
+ '-H', '--homes',
+ metavar='DIR', dest='home_root',
+ help=("The shared directory on the NFS server for all home directories. "
+ "Default: {!r}.").format(self.default_home_root)
+ )
+
+ homes_group.add_argument(
+ '-E', '--exclude-file',
+ metavar='FILE', dest='exclude_file',
+ help=("The file containing all directories underneath {h!r}, which are "
+ "excluded from all operations. Default: {f!r}.").format(
+ h=self.default_home_root, f=self.default_exclude_file)
+ )
+
+ super(PpHomesAdminApp, self).init_arg_parser()
+
+ # -------------------------------------------------------------------------
+ def perform_config(self):
+
+ super(PpHomesAdminApp, self).perform_config()
+
+ for section_name in self.cfg.keys():
+
+ if self.verbose > 3:
+ LOG.debug("Checking config section {!r} ...".format(section_name))
+
+ section = self.cfg[section_name]
+
+ if section_name.lower() not in (
+ 'test-home', 'test_home', 'testhome', 'homes', 'admin') :
+ continue
+
+ if self.verbose > 2:
+ LOG.debug("Evaluating config section {n!r}:\n{s}".format(
+ n=section_name, s=pp(section)))
+
+ if section_name.lower() == 'homes':
+
+ if 'chroot_homedir' in section:
+ v = section['chroot_homedir']
+ if not os.path.isabs(v):
+ msg = (
+ "The chrooted path of the home directories must be an "
+ "absolute pathname (found [{s}]/chroot_homedir "
+ "=> {v!r} in configuration.").format(s=section_name, v=v)
+ raise PpHomesAdminError(msg)
+ self.chroot_homedir = v
+
+ if 'home_root' in section:
+ v = section['home_root']
+ if not os.path.isabs(v):
+ msg = (
+ "The root path of the home directories must be an "
+ "absolute pathname (found [{s}]/home_root "
+ "=> {v!r} in configuration.").format(s=section_name, v=v)
+ raise PpHomesAdminError(msg)
+ self.home_root_abs = v
+
+ elif section_name.lower() == 'admin':
+
+ if 'exclude_file' in section:
+ v = section['exclude_file']
+ if not os.path.isabs(v):
+ msg = (
+ "The path of file of excluded directories must be an "
+ "absolute pathname (found [{s}]/exclude_file "
+ "=> {v!r} in configuration.").format(s=section_name, v=v)
+ raise PpHomesAdminError(msg)
+ self.exclude_file = v
+
+ if hasattr(self.args, 'chroot_homedir') and self.args.chroot_homedir:
+ v = self.args.chroot_homedir
+ if not os.path.isabs(v):
+ msg = (
+ "The chrooted path of the home directories must be an "
+ "absolute pathname (got {!r} as command line parameter).").format(v)
+ raise PpHomesAdminError(msg)
+ self.chroot_homedir = v
+
+ if hasattr(self.args, 'home_root') and self.args.home_root:
+ v = self.args.home_root
+ if not os.path.isabs(v):
+ msg = (
+ "The root path of the home directories must be an "
+ "absolute pathname (got {!r} as command line parameter).").format(v)
+ raise PpHomesAdminError(msg)
+ self.home_root_abs = v
+
+ if hasattr(self.args, 'exclude_file') and self.args.exclude_file:
+ v = self.args.exclude_file
+ if not os.path.isabs(v):
+ msg = (
+ "The path of file of excluded directories must be an "
+ "absolute pathname (got {!r} as command line parameter).").format(v)
+ raise PpHomesAdminError(msg)
+ self.exclude_file = v
+
+ self.home_root_rel = os.path.relpath(self.home_root_abs, os.sep)
+ self.home_root_real = os.path.join(self.chroot_homedir, self.home_root_rel)
+
+ # -------------------------------------------------------------------------
+ def read_exclude_dirs(self):
+
+ LOG.info("Reading exclude file {!r} ...".format(self.exclude_file))
+ upper_dir = os.pardir + os.sep
+
+ if not os.path.exists(self.exclude_file):
+ msg = "Exclude file {!r} does not exists.".format(self.exclude_file)
+ LOG.error(msg)
+ return
+
+ if not os.path.isfile(self.exclude_file):
+ msg = "Exclude file {!r} is not a regular file.".format(self.exclude_file)
+ LOG.error(msg)
+ return
+
+ if not os.access(self.exclude_file, os.R_OK):
+ msg = "No read access to exclude file {!r}.".format(self.exclude_file)
+ LOG.error(msg)
+ return
+
+ open_args = {}
+ if six.PY3:
+ open_args['encoding'] = 'utf-8'
+ open_args['errors'] = 'surrogateescape'
+
+ with open(self.exclude_file, 'r', **open_args) as fh:
+ lnr = 0
+ for line in fh.readlines():
+ lnr += 1
+ line = line.strip()
+ if not line:
+ continue
+ line = self.comment_re.sub('', line)
+ if not line:
+ continue
+ if self.verbose > 3:
+ LOG.debug("Evaluating line {l!r} (file {f!r}, line {lnr}).".format(
+ l=line, f=self.exclude_file, lnr=lnr))
+ tokens = self.whitespace_re.split(line)
+ for token in tokens:
+ if not os.path.isabs(token):
+ LOG.warn((
+ "Entry {e!r} in file {f!r}, line {l}, "
+ "is not an absolute path.").format(
+ e=token, f=self.exclude_file, l=lnr))
+ continue
+ home_relative = os.path.relpath(token, self.home_root_abs)
+ if token == os.sep or home_relative.startswith(upper_dir):
+ LOG.warn((
+ "Entry {e!r} in file {f!r}, line {l}, "
+ "is outside home root {h!r}.").format(
+ e=token, f=self.exclude_file, l=lnr, h=self.home_root_abs))
+ continue
+ if token not in self.exclude_dirs:
+ self.exclude_dirs.append(token)
+
+ self.exclude_dirs.sort(key=str.lower)
+
+ LOG.debug("Found {} directories to exclude.".format(len(self.exclude_dirs)))
+ if self.verbose > 2:
+ LOG.debug("Found directories to exclude:\n{}".format(pp(self.exclude_dirs)))
+
+ # -------------------------------------------------------------------------
+ def read_passwd_homes(self):
+
+ LOG.info("Reading all home directories from 'getent passwd' ...")
+
+ upper_dir = os.pardir + os.sep
+ entries = pwd.getpwall()
+
+ for entry in entries:
+ home = entry.pw_dir
+ if not home:
+ continue
+ home_relative = os.path.relpath(home, self.home_root_abs)
+ if home == os.sep or home_relative.startswith(upper_dir):
+ if self.verbose > 1:
+ LOG.debug((
+ "Home directory {d!r} of user {u!r} "
+ "is outside home root {h!r}.").format(
+ d=home, u=entry.pw_name, h=self.home_root_abs))
+ continue
+ if home not in self.passwd_home_dirs:
+ self.passwd_home_dirs.append(home)
+
+ self.passwd_home_dirs.sort(key=str.lower)
+
+ LOG.debug("Found {} home directories in passwd.".format(len(self.passwd_home_dirs)))
+ if self.verbose > 2:
+ LOG.debug("Home directories in passwd:\n{}".format(pp(self.passwd_home_dirs)))
+
+ # -------------------------------------------------------------------------
+ def check_homes(self):
+
+ LOG.info("Checking for unnecessary home directories ...")
+
+ glob_pattern = os.path.join(self.home_root_real, '*')
+ all_home_entries = glob.glob(glob_pattern)
+
+ for path in all_home_entries:
+ if not os.path.isdir(path):
+ continue
+ home_rel = os.sep + os.path.relpath(path, self.chroot_homedir)
+ if self.verbose > 2:
+ LOG.debug("Checking {p!r} ({h!r}) ...".format(
+ p=path, h=home_rel))
+ if home_rel in self.passwd_home_dirs:
+ continue
+ if home_rel in self.exclude_dirs:
+ continue
+ LOG.debug("Marking {!r} as unnecessary.".format(home_rel))
+ self.unnecessary_dirs.append(home_rel)
+
+ self.unnecessary_dirs.sort(key=str.lower)
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+
+ pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list
from .common import pp, terminal_can_colors, to_bytes, to_bool
-from .cfg_app import PpCfgAppError, PpConfigApplication
+from .cfg_app import PpCfgAppError
-__version__ = '0.4.4'
+from .homes_admin import PpHomesAdminError, PpHomesAdminApp
+
+__version__ = '0.5.1'
LOG = logging.getLogger(__name__)
# =============================================================================
-class PpTestHomeError(PpCfgAppError):
+class PpTestHomeError(PpHomesAdminError):
pass
# =============================================================================
-class PpTestHomeApp(PpConfigApplication):
+class PpTestHomeApp(PpHomesAdminApp):
"""
Class for the 'test-home' application to check for unnacessary home directories.
"""
- # /mnt/nfs
- default_chroot_homedir = os.sep + os.path.join('mnt', 'nfs')
- # /home
- default_home_root = os.sep + 'home'
-
- # /etc/pixelpark/exclude_homes
- default_exclude_file = os.sep + os.path.join('etc', 'pixelpark', 'exclude_homes')
-
- comment_re = re.compile(r'\s*#.*')
-
# -------------------------------------------------------------------------
def __init__(self, appname=None, version=__version__):
- self.default_mail_recipients = [
- 'admin.berlin@pixelpark.com'
- ]
- self.default_mail_cc = []
-
self.default_reply_to = 'noreply@pixelpark.com'
- self.chroot_homedir = self.default_chroot_homedir
- self.home_root_abs = self.default_home_root
- self.home_root_rel = os.path.relpath(self.home_root_abs, os.sep)
-
- self.exclude_file = self.default_exclude_file
-
- self.exclude_dirs = []
- self.passwd_home_dirs = []
- self.unnecessary_dirs = []
-
description = textwrap.dedent('''\
This scripts detects unnecessary home directories - without an
appropriate home directory in the passwd database and not excluded
super(PpTestHomeApp, self).__init__(
appname=appname, version=version, description=description,
- cfg_stems='test-home'
)
- self.initialized = True
-
- # -------------------------------------------------------------------------
- def perform_config(self):
-
- super(PpTestHomeApp, self).perform_config()
+ if not self.mail_recipients:
+ self.exit(5)
- for section_name in self.cfg.keys():
-
- if self.verbose > 3:
- LOG.debug("Checking config section {!r} ...".format(section_name))
-
- if section_name.lower() not in ('test-home', 'test_home', 'testhome') :
- continue
-
- section = self.cfg[section_name]
- if self.verbose > 2:
- LOG.debug("Evaluating config section {n!r}:\n{s}".format(
- n=section_name, s=pp(section)))
-
- if 'chroot_homedir' in section:
- v = section['chroot_homedir']
- if not os.path.isabs(v):
- msg = (
- "The chrooted path of the home directories must be an "
- "absolute pathname (found [{s}]/chroot_homedir "
- "=> {v!r} in configuration.").format(s=section_name, v=v)
- raise PpMkHomeError(msg)
- self.chroot_homedir = v
-
- if 'home_root' in section:
- v = section['home_root']
- if not os.path.isabs(v):
- msg = (
- "The root path of the home directories must be an "
- "absolute pathname (found [{s}]/home_root "
- "=> {v!r} in configuration.").format(s=section_name, v=v)
- raise PpMkHomeError(msg)
- self.home_root_abs = v
-
- if 'exclude_file' in section:
- v = section['exclude_file']
- if not os.path.isabs(v):
- msg = (
- "The path of file of excluded directories must be an "
- "absolute pathname (found [{s}]/exclude_file "
- "=> {v!r} in configuration.").format(s=section_name, v=v)
- raise PpMkHomeError(msg)
- self.exclude_file = v
-
- self.home_root_rel = os.path.relpath(self.home_root_abs, os.sep)
- self.home_root_real = os.path.join(self.chroot_homedir, self.home_root_rel)
+ self.initialized = True
# -------------------------------------------------------------------------
def _run(self):
self.check_homes()
self.send_results()
- # -------------------------------------------------------------------------
- def read_exclude_dirs(self):
-
- LOG.info("Reading exclude file {!r} ...".format(self.exclude_file))
- upper_dir = os.pardir + os.sep
-
- if not os.path.exists(self.exclude_file):
- msg = "Exclude file {!r} does not exists.".format(self.exclude_file)
- LOG.error(msg)
- return
-
- if not os.path.isfile(self.exclude_file):
- msg = "Exclude file {!r} is not a regular file.".format(self.exclude_file)
- LOG.error(msg)
- return
-
- if not os.access(self.exclude_file, os.R_OK):
- msg = "No read access to exclude file {!r}.".format(self.exclude_file)
- LOG.error(msg)
- return
-
- open_args = {}
- if six.PY3:
- open_args['encoding'] = 'utf-8'
- open_args['errors'] = 'surrogateescape'
-
- with open(self.exclude_file, 'r', **open_args) as fh:
- lnr = 0
- for line in fh.readlines():
- lnr += 1
- line = line.strip()
- if not line:
- continue
- line = self.comment_re.sub('', line)
- if not line:
- continue
- if self.verbose > 3:
- LOG.debug("Evaluating line {l!r} (file {f!r}, line {lnr}).".format(
- l=line, f=self.exclude_file, lnr=lnr))
- tokens = self.whitespace_re.split(line)
- for token in tokens:
- if not os.path.isabs(token):
- LOG.warn((
- "Entry {e!r} in file {f!r}, line {l}, "
- "is not an absolute path.").format(
- e=token, f=self.exclude_file, l=lnr))
- continue
- home_relative = os.path.relpath(token, self.home_root_abs)
- if token == os.sep or home_relative.startswith(upper_dir):
- LOG.warn((
- "Entry {e!r} in file {f!r}, line {l}, "
- "is outside home root {h!r}.").format(
- e=token, f=self.exclude_file, l=lnr, h=self.home_root_abs))
- continue
- if token not in self.exclude_dirs:
- self.exclude_dirs.append(token)
-
- self.exclude_dirs.sort(key=str.lower)
-
- LOG.debug("Found {} directories to exclude.".format(len(self.exclude_dirs)))
- if self.verbose > 2:
- LOG.debug("Found directories to exclude:\n{}".format(pp(self.exclude_dirs)))
-
- # -------------------------------------------------------------------------
- def read_passwd_homes(self):
-
- LOG.info("Reading all home directories from 'getent passwd' ...")
-
- upper_dir = os.pardir + os.sep
- entries = pwd.getpwall()
-
- for entry in entries:
- home = entry.pw_dir
- if not home:
- continue
- home_relative = os.path.relpath(home, self.home_root_abs)
- if home == os.sep or home_relative.startswith(upper_dir):
- if self.verbose > 1:
- LOG.debug((
- "Home directory {d!r} of user {u!r} "
- "is outside home root {h!r}.").format(
- d=home, u=entry.pw_name, h=self.home_root_abs))
- continue
- if home not in self.passwd_home_dirs:
- self.passwd_home_dirs.append(home)
-
- self.passwd_home_dirs.sort(key=str.lower)
-
- LOG.debug("Found {} home directories in passwd.".format(len(self.passwd_home_dirs)))
- if self.verbose > 2:
- LOG.debug("Home directories in passwd:\n{}".format(pp(self.passwd_home_dirs)))
-
- # -------------------------------------------------------------------------
- def check_homes(self):
-
- LOG.info("Checking for unnecessary home directories ...")
-
- glob_pattern = os.path.join(self.home_root_real, '*')
- all_home_entries = glob.glob(glob_pattern)
-
- for path in all_home_entries:
- if not os.path.isdir(path):
- continue
- home_rel = os.sep + os.path.relpath(path, self.chroot_homedir)
- if self.verbose > 2:
- LOG.debug("Checking {p!r} ({h!r}) ...".format(
- p=path, h=home_rel))
- if home_rel in self.passwd_home_dirs:
- continue
- if home_rel in self.exclude_dirs:
- continue
- LOG.debug("Marking {!r} as unnecessary.".format(home_rel))
- self.unnecessary_dirs.append(home_rel)
-
- self.unnecessary_dirs.sort(key=str.lower)
-
# -------------------------------------------------------------------------
def send_results(self):