From de00bb440a9c00441177bc352dcad69cee08c5d6 Mon Sep 17 00:00:00 2001 From: Eugeni Dodonov Date: Wed, 16 Sep 2009 14:12:32 +0000 Subject: moved security enforcing into msec.py plugin --- src/msec/config.py | 63 +--- src/msec/libmsec.py | 656 ------------------------------------------ src/msec/plugins/audit.py | 26 ++ src/msec/plugins/msec.py | 719 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 754 insertions(+), 710 deletions(-) create mode 100644 src/msec/plugins/msec.py diff --git a/src/msec/config.py b/src/msec/config.py index 9f31723..e928ab4 100644 --- a/src/msec/config.py +++ b/src/msec/config.py @@ -52,67 +52,22 @@ PLUGINS_DIR="/usr/share/msec/plugins" # msec callbacks and valid values # OPTION callback valid values -SETTINGS = {'BASE_LEVEL': ("libmsec.base_level", ['*']), - # security checks from audit plugins - 'CHECK_SECURITY': ("libmsec.check_security", ['yes', 'no']), - # security options - 'USER_UMASK': ("libmsec.set_user_umask", ['*']), - 'ROOT_UMASK': ("libmsec.set_root_umask", ['*']), - 'ALLOW_CURDIR_IN_PATH': ("libmsec.allow_curdir_in_path", ['yes', 'no']), - 'WIN_PARTS_UMASK': ("libmsec.set_win_parts_umask", ['*']), - 'ACCEPT_BOGUS_ERROR_RESPONSES': ("libmsec.accept_bogus_error_responses", ['yes', 'no']), - 'ACCEPT_BROADCASTED_ICMP_ECHO': ("libmsec.accept_broadcasted_icmp_echo", ['yes', 'no']), - 'ACCEPT_ICMP_ECHO': ("libmsec.accept_icmp_echo", ['yes', 'no']), - 'ALLOW_AUTOLOGIN': ("libmsec.allow_autologin", ['yes', 'no']), - 'ALLOW_REBOOT': ("libmsec.allow_reboot", ['yes', 'no']), - 'ALLOW_REMOTE_ROOT_LOGIN': ("libmsec.allow_remote_root_login", ['yes', 'no', 'without-password']), - 'ALLOW_ROOT_LOGIN': ("libmsec.allow_root_login", ['yes', 'no']), - 'ALLOW_USER_LIST': ("libmsec.allow_user_list", ['yes', 'no']), - 'ALLOW_X_CONNECTIONS': ("libmsec.allow_x_connections", ['yes', 'no', 'local']), - 'ALLOW_XAUTH_FROM_ROOT': ("libmsec.allow_xauth_from_root", ['yes', 'no']), - 'ALLOW_XSERVER_TO_LISTEN': ("libmsec.allow_xserver_to_listen", ['yes', 'no']), - 'AUTHORIZE_SERVICES': ("libmsec.authorize_services", ['yes', 'no', 'local']), - 'CREATE_SERVER_LINK': ("libmsec.create_server_link", ['no', 'remote', 'local']), - 'ENABLE_AT_CRONTAB': ("libmsec.enable_at_crontab", ['yes', 'no']), - 'ENABLE_CONSOLE_LOG': ("libmsec.enable_console_log", ['yes', 'no']), - 'ENABLE_DNS_SPOOFING_PROTECTION':("libmsec.enable_dns_spoofing_protection", ['yes', 'no']), - 'ENABLE_IP_SPOOFING_PROTECTION': ("libmsec.enable_ip_spoofing_protection", ['yes', 'no']), - 'ENABLE_LOG_STRANGE_PACKETS': ("libmsec.enable_log_strange_packets", ['yes', 'no']), - 'ENABLE_MSEC_CRON': ("libmsec.enable_msec_cron", ['yes', 'no']), - 'ENABLE_SULOGIN': ("libmsec.enable_sulogin", ['yes', 'no']), - 'SECURE_TMP': ("libmsec.secure_tmp", ['yes', 'no']), - 'SHELL_HISTORY_SIZE': ("libmsec.set_shell_history_size", ['*']), - 'SHELL_TIMEOUT': ("libmsec.set_shell_timeout", ['*']), - 'ENABLE_STARTUP_MSEC': ("libmsec.enable_startup_msec", ['yes', 'no']), - 'ENABLE_STARTUP_PERMS': ("libmsec.enable_startup_perms", ['yes', 'no', 'enforce']), - } +SETTINGS = { + 'BASE_LEVEL': ("libmsec.base_level", ['*']), + } # text for disabled options OPTION_DISABLED=_("System default") # settings organizes by category -# system security settings -SETTINGS_SYSTEM = ["ENABLE_STARTUP_MSEC", "ENABLE_STARTUP_PERMS", "ENABLE_MSEC_CRON", - "ENABLE_SULOGIN", "ENABLE_AT_CRONTAB", - "ALLOW_ROOT_LOGIN", "ALLOW_USER_LIST", "ALLOW_AUTOLOGIN", - "ENABLE_CONSOLE_LOG", "CREATE_SERVER_LINK", "ALLOW_XAUTH_FROM_ROOT", - "ALLOW_REBOOT", "SHELL_HISTORY_SIZE", "SHELL_TIMEOUT", "USER_UMASK", "ROOT_UMASK", - "SECURE_TMP", "WIN_PARTS_UMASK", "ALLOW_CURDIR_IN_PATH" - ] -# network security settings -SETTINGS_NETWORK = ["ACCEPT_BOGUS_ERROR_RESPONSES", "ACCEPT_BROADCASTED_ICMP_ECHO", "ACCEPT_ICMP_ECHO", - "ALLOW_REMOTE_ROOT_LOGIN", "ALLOW_X_CONNECTIONS", "ALLOW_XSERVER_TO_LISTEN", - "AUTHORIZE_SERVICES", "ENABLE_DNS_SPOOFING_PROTECTION", "ENABLE_IP_SPOOFING_PROTECTION", - "ENABLE_LOG_STRANGE_PACKETS", - ] +# system security settings - defined by 'msec' plugin +SETTINGS_SYSTEM = [] +# network security settings - defined by 'msec' plugin +SETTINGS_NETWORK = [] # periodic checks - defined by 'audit' plugin SETTINGS_PERIODIC = [] -# checks that support exceptions -CHECKS_WITH_EXCEPTIONS = ["CHECK_PERMS", "CHECK_USER_FILES", "CHECK_SUID_ROOT", "CHECK_SUID_MD5", "CHECK_SGID", - "CHECK_WRITABLE", "CHECK_UNOWNED", "CHECK_OPEN_PORT", "CHECK_FIREWALL", - "CHECK_PASSWD", "CHECK_SHADOW", "CHECK_RPM_PACKAGES", "CHECK_RPM_INTEGRITY", - "CHECK_SHOSTS", "CHECK_USERS", "CHECK_GROUPS" - ] +# checks that support exceptions - defined by 'audit' plugin +CHECKS_WITH_EXCEPTIONS = [] # localized help try: diff --git a/src/msec/libmsec.py b/src/msec/libmsec.py index 9b72591..1aa103e 100755 --- a/src/msec/libmsec.py +++ b/src/msec/libmsec.py @@ -63,49 +63,6 @@ except IOError: # backup file suffix SUFFIX = '.msec' -# list of config files - -ATALLOW = '/etc/at.allow' -AUTOLOGIN = '/etc/sysconfig/autologin' -BASTILLENOLOGIN = '/etc/bastille-no-login' -CRON = '/etc/cron.d/msec' -CRONALLOW = '/etc/cron.allow' -FSTAB = '/etc/fstab' -GDM = '/etc/pam.d/gdm' -GDMCONF = '/etc/X11/gdm/custom.conf' -HALT = '/usr/bin/halt' -HOSTCONF = '/etc/host.conf' -HOSTSDENY = '/etc/hosts.deny' -INITTAB = '/etc/inittab' -ISSUE = '/etc/issue' -ISSUENET = '/etc/issue.net' -KDE = '/etc/pam.d/kde' -KDMRC = '/usr/share/config/kdm/kdmrc' -LILOCONF = '/etc/lilo.conf' -LOGINDEFS = '/etc/login.defs' -MENULST = '/boot/grub/menu.lst' -SHELLCONF = '/etc/security/shell' -MSECBIN = '/usr/sbin/msec' -MSECCRON = '/etc/cron.hourly/msec' -MSEC_XINIT = '/etc/X11/xinit.d/msec' -OPASSWD = '/etc/security/opasswd' -PASSWD = '/etc/pam.d/passwd' -POWEROFF = '/usr/bin/poweroff' -REBOOT = '/usr/bin/reboot' -SECURITYCRON = '/etc/cron.daily/msec' -SECURITYSH = '/usr/share/msec/security.sh' -SERVER = '/etc/security/msec/server' -SHADOW = '/etc/shadow' -SHUTDOWN = '/usr/bin/shutdown' -SHUTDOWNALLOW = '/etc/shutdown.allow' -SSHDCONFIG = '/etc/ssh/sshd_config' -STARTX = '/usr/bin/startx' -SYSCTLCONF = '/etc/sysctl.conf' -SYSLOGCONF = '/etc/syslog.conf' -XDM = '/etc/pam.d/xdm' -XSERVERS = '/etc/X11/xdm/Xservers' -EXPORT = '/root/.xauth/export' - # ConfigFile constants STRING_TYPE = type('') @@ -115,23 +72,6 @@ AFTER=2 # regexps space = re.compile('\s') -# X server -SECURETTY = '/etc/securetty' -STARTX_REGEXP = '(\s*serverargs=".*) -nolisten tcp(.*")' -XSERVERS_REGEXP = '(\s*[^#]+/usr/bin/X .*) -nolisten tcp(.*)' -GDMCONF_REGEXP = '(\s*command=.*/X.*?) -nolisten tcp(.*)$' -KDMRC_REGEXP = re.compile('(.*?)-nolisten tcp(.*)$') -# ctrl-alt-del -CTRALTDEL_REGEXP = '^ca::ctrlaltdel:/sbin/shutdown.*' -# consolehelper -CONSOLE_HELPER = 'consolehelper' -# ssh PermitRootLogin -PERMIT_ROOT_LOGIN_REGEXP = '^\s*PermitRootLogin\s+(no|yes|without-password|forced-commands-only)' -# tcp_wrappers -ALL_REGEXP = '^ALL:ALL:DENY' -ALL_LOCAL_REGEXP = '^ALL:ALL EXCEPT 127\.0\.0\.1:DENY' -# sulogin -SULOGIN_REGEXP = '~~:S:wait:/sbin/sulogin' # {{{ helper functions def move(old, new): @@ -151,12 +91,6 @@ def substitute_re_result(res, s): s = string.replace(s, '@' + str(idx), subst) return s -def invert(param): - """Returns inverse value for param. E.g., yes becomes no, and no becomes yes.""" - if param == "yes": - return "no" - else: - return "yes" # }}} # {{{ Log @@ -727,15 +661,6 @@ class MSEC: self.root = root self.configfiles = ConfigFiles(log, root=root) - # associate helper commands with files - self.configfiles.add_config_assoc(INITTAB, '/sbin/telinit q') - self.configfiles.add_config_assoc('/etc(?:/rc.d)?/init.d/(.+)', '[ -f /var/lock/subsys/@1 ] && @0 reload') - self.configfiles.add_config_assoc(SYSCTLCONF, '/sbin/sysctl -e -p /etc/sysctl.conf') - self.configfiles.add_config_assoc(SSHDCONFIG, '[ -f /var/lock/subsys/sshd ] && /etc/rc.d/init.d/sshd restart') - self.configfiles.add_config_assoc(LILOCONF, '[ `/usr/sbin/detectloader` = LILO ] && /sbin/lilo') - self.configfiles.add_config_assoc(SYSLOGCONF, '[ -f /var/lock/subsys/syslog ] && service syslog reload') - self.configfiles.add_config_assoc('^/etc/issue$', '/usr/bin/killall mingetty') - # plugins self.init_plugins(plugins) @@ -834,587 +759,6 @@ class MSEC: """Defines the base security level, on top of which the current configuration is based.""" pass - def create_server_link(self, param): - ''' Creates the symlink /etc/security/msec/server to point to /etc/security/msec/server.SERVER_LEVEL. The /etc/security/msec/server is used by chkconfig --add to decide to add a service if it is present in the file during the installation of packages. By default, two presets are provided: local (which only enables local services) and remote (which also enables some remote services considered safe). Note that the allowed services must be placed manually into the server.SERVER_LEVEL files when necessary.''' - server = self.configfiles.get_config_file(SERVER) - - if param == "no": - if server.exists(): - self.log.info(_('Allowing unrestricted chkconfig for packages')) - server.unlink() - else: - newpath = "%s.%s" % (SERVER, param) - if server.realpath() != newpath: - self.log.info(_('Restricting chkconfig for packages according to "%s" profile') % param) - server.symlink(newpath) - - def set_root_umask(self, umask): - ''' Set the root umask.''' - msec = self.configfiles.get_config_file(SHELLCONF) - - val = msec.get_shell_variable('UMASK_ROOT') - - if val != umask: - self.log.info(_('Setting root umask to %s') % (umask)) - msec.set_shell_variable('UMASK_ROOT', umask) - - def set_user_umask(self, umask): - ''' Set the user umask.''' - msec = self.configfiles.get_config_file(SHELLCONF) - - val = msec.get_shell_variable('UMASK_USER') - - if val != umask: - self.log.info(_('Setting users umask to %s') % (umask)) - msec.set_shell_variable('UMASK_USER', umask) - - def allow_x_connections(self, arg): - ''' Allow local users to connect to X server. Accepted arguments: yes (all connections are allowed), local (only local connection), no (no connection).''' - - xinit = self.configfiles.get_config_file(MSEC_XINIT) - val = xinit.get_match('/usr/bin/xhost\s*(\+\s*[^#]*)', '@1') - - if val: - if val == '+': - val = "yes" - elif val == "+ localhost": - val = "local" - else: - val = "no" - else: - val = "no" - - if val != arg: - if arg == "yes": - self.log.info(_('Allowing users to connect X server from everywhere')) - xinit.replace_line_matching('/usr/bin/xhost', '/usr/bin/xhost +', 1) - elif arg == "local": - self.log.info(_('Allowing users to connect X server from localhost')) - xinit.replace_line_matching('/usr/bin/xhost', '/usr/bin/xhost + localhost', 1) - elif arg == "no": - self.log.info(_('Restricting X server connection to the console user')) - xinit.remove_line_matching('/usr/bin/xhost', 1) - else: - self.log.error(_('invalid allow_x_connections arg: %s') % arg) - - def allow_xserver_to_listen(self, arg): - ''' Allow X server to accept connections from network on tcp port 6000.''' - - startx = self.configfiles.get_config_file(STARTX) - xservers = self.configfiles.get_config_file(XSERVERS) - gdmconf = self.configfiles.get_config_file(GDMCONF) - kdmrc = self.configfiles.get_config_file(KDMRC) - - val_startx = startx.get_match(STARTX_REGEXP) - val_xservers = xservers.get_match(XSERVERS_REGEXP) - val_gdmconf = gdmconf.get_shell_variable('DisallowTCP') - str = kdmrc.get_shell_variable('ServerArgsLocal', 'X-\*-Core', '^\s*$') - if str: - val_kdmrc = KDMRC_REGEXP.search(str) - else: - val_kdmrc = None - - # TODO: better check for file existance - - if arg == "yes": - if val_startx or val_xservers or val_kdmrc or val_gdmconf != 'false': - self.log.info(_('Allowing the X server to listen to tcp connections')) - if startx.exists(): - startx.replace_line_matching(STARTX_REGEXP, '@1@2') - if xservers.exists(): - xservers.replace_line_matching(XSERVERS_REGEXP, '@1@2', 0, 1) - if gdmconf.exists(): - gdmconf.set_shell_variable('DisallowTCP', 'false', '\[security\]', '^\s*$') - if kdmrc.exists(): - kdmrc.replace_line_matching('^(ServerArgsLocal=.*?)-nolisten tcp(.*)$', '@1@2', 0, 0, 'X-\*-Core', '^\s*$') - else: - if not val_startx or not val_xservers or not val_kdmrc or val_gdmconf != 'true': - self.log.info(_('Forbidding the X server to listen to tcp connection')) - if not val_startx: - startx.exists() and startx.replace_line_matching('serverargs="(.*?)( -nolisten tcp)?"', 'serverargs="@1 -nolisten tcp"') - if not val_xservers: - xservers.exists() and xservers.replace_line_matching('(\s*[^#]+/usr/bin/X .*?)( -nolisten tcp)?$', '@1 -nolisten tcp', 0, 1) - if val_gdmconf != 'true': - gdmconf.exists() and gdmconf.set_shell_variable('DisallowTCP', 'true', '\[security\]', '^\s*$') - if not val_kdmrc: - kdmrc.exists() and kdmrc.replace_line_matching('^(ServerArgsLocal=.*)$', '@1 -nolisten tcp', 'ServerArgsLocal=-nolisten tcp', 0, 'X-\*-Core', '^\s*$') - - def set_shell_timeout(self, val): - ''' Set the shell timeout. A value of zero means no timeout.''' - msec = self.configfiles.get_config_file(SHELLCONF) - try: - timeout = int(val) - except: - self.log.error(_('Invalid shell timeout "%s"') % val) - return - - old = msec.get_shell_variable('TMOUT') - if old: - old = int(old) - - if old != timeout: - self.log.info(_('Setting shell timeout to %s') % timeout) - msec.set_shell_variable('TMOUT', timeout) - - def set_shell_history_size(self, size): - ''' Set shell commands history size. A value of -1 means unlimited.''' - try: - size = int(size) - except: - self.log.error(_('Invalid shell history size "%s"') % size) - return - - msec = self.configfiles.get_config_file(SHELLCONF) - - val = msec.get_shell_variable('HISTFILESIZE') - if val: - val = int(val) - - if size >= 0: - if val != size: - self.log.info(_('Setting shell history size to %s') % size) - msec.set_shell_variable('HISTFILESIZE', size) - else: - if val != None: - self.log.info(_('Removing limit on shell history size')) - msec.remove_line_matching('^HISTFILESIZE=') - - def set_win_parts_umask(self, umask): - ''' Set umask option for mounting vfat and ntfs partitions. If umask is '-1', default system umask is used.''' - fstab = self.configfiles.get_config_file(FSTAB) - try: - test_umask = int(umask) - except: - self.log.error(_('Invalid file system umask "%s"') % umask) - return - if umask == "-1": - fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+)umask=\d+(\s.*)", "@1defaults@3", 0, 1) - fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+)umask=\d+,(.*)", "@1@3", 0, 1) - fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+\S+),umask=\d+(.*)", "@1@3", 0, 1) - else: - fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+\S*)umask=\d+(.*)", "@1umask="+umask+"@3", 0, 1) - fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+)(?!.*umask=)(\S+)(.*)", "@1@3,umask="+umask+"@4", 0, 1) - - def allow_reboot(self, arg): - ''' Allow system reboot and shutdown to local users.''' - shutdownallow = self.configfiles.get_config_file(SHUTDOWNALLOW) - sysctlconf = self.configfiles.get_config_file(SYSCTLCONF) - kdmrc = self.configfiles.get_config_file(KDMRC) - gdmconf = self.configfiles.get_config_file(GDMCONF) - inittab = self.configfiles.get_config_file(INITTAB) - shutdown = self.configfiles.get_config_file(SHUTDOWN) - poweroff = self.configfiles.get_config_file(POWEROFF) - reboot = self.configfiles.get_config_file(REBOOT) - halt = self.configfiles.get_config_file(HALT) - - val_shutdownallow = shutdownallow.exists() - val_shutdown = shutdown.exists() - val_poweroff = poweroff.exists() - val_reboot = reboot.exists() - val_halt = halt.exists() - val_sysctlconf = sysctlconf.get_shell_variable('kernel.sysrq') - val_inittab = inittab.get_match(CTRALTDEL_REGEXP) - val_gdmconf = gdmconf.get_shell_variable('SystemMenu') - oldval_kdmrc = kdmrc.get_shell_variable('AllowShutdown', 'X-:\*-Core', '^\s*$') - - if arg == "yes": - if val_shutdownallow or not val_shutdown or not val_poweroff or not val_reboot or not val_halt: - self.log.info(_('Allowing reboot and shutdown to the console user')) - shutdownallow.exists() and shutdownallow.move(SUFFIX) - shutdown.exists() or shutdown.symlink(CONSOLE_HELPER) - poweroff.exists() or poweroff.symlink(CONSOLE_HELPER) - reboot.exists() or reboot.symlink(CONSOLE_HELPER) - halt.exists() or halt.symlink(CONSOLE_HELPER) - if val_sysctlconf == '0': - self.log.info(_('Allowing SysRq key to the console user')) - sysctlconf.set_shell_variable('kernel.sysrq', 1) - if val_gdmconf == 'false': - self.log.info(_('Allowing Shutdown/Reboot in GDM')) - gdmconf.exists() and gdmconf.set_shell_variable('SystemMenu', 'true', '\[greeter\]', '^\s*$') - if kdmrc.exists(): - if oldval_kdmrc != 'All': - self.log.info(_('Allowing Shutdown/Reboot in KDM')) - kdmrc.set_shell_variable('AllowShutdown', 'All', 'X-:\*-Core', '^\s*$') - if not val_inittab: - self.log.info(_('Allowing Ctrl-Alt-Del from console')) - inittab.exists() and inittab.replace_line_matching(CTRALTDEL_REGEXP, 'ca::ctrlaltdel:/sbin/shutdown -t3 -r now', 1) - else: - if not val_shutdownallow or val_shutdown or val_poweroff or val_reboot or val_halt: - self.log.info(_('Forbidding reboot and shutdown to the console user')) - if not shutdownallow.exists(): - self.configfiles.get_config_file(SHUTDOWNALLOW, SUFFIX).touch() - shutdown.exists() and shutdown.unlink() - poweroff.exists() and poweroff.unlink() - reboot.exists() and reboot.unlink() - halt.exists() and halt.unlink() - if val_sysctlconf != '0': - self.log.info(_('Forbidding SysRq key to the console user')) - sysctlconf.set_shell_variable('kernel.sysrq', 0) - if val_gdmconf != 'false': - self.log.info(_('Forbidding Shutdown/Reboot in GDM')) - gdmconf.exists() and gdmconf.set_shell_variable('SystemMenu', 'false', '\[greeter\]', '^\s*$') - if kdmrc.exists(): - if oldval_kdmrc != 'None': - self.log.info(_('Forbidding Shutdown/Reboot in KDM')) - kdmrc.set_shell_variable('AllowShutdown', 'None', 'X-:\*-Core', '^\s*$') - if val_inittab: - self.log.info(_('Forbidding Ctrl-Alt-Del from console')) - inittab.exists() and inittab.remove_line_matching(CTRALTDEL_REGEXP) - - def allow_user_list(self, arg): - ''' Allow display managers (kdm and gdm) to display list of local users.''' - kdmrc = self.configfiles.get_config_file(KDMRC) - gdmconf = self.configfiles.get_config_file(GDMCONF) - - oldval_gdmconf = gdmconf.get_shell_variable('Browser') - oldval_kdmrc = kdmrc.get_shell_variable('ShowUsers', 'X-\*-Greeter', '^\s*$') - - if arg == "yes": - if kdmrc.exists(): - if oldval_kdmrc != 'NotHidden': - self.log.info(_("Allowing list of users in KDM")) - kdmrc.set_shell_variable('ShowUsers', 'NotHidden', 'X-\*-Greeter', '^\s*$') - if gdmconf.exists(): - if oldval_gdmconf != 'true': - self.log.info(_("Allowing list of users in GDM")) - gdmconf.set_shell_variable('Browser', 'true') - else: - if kdmrc.exists(): - if oldval_kdmrc != 'Selected': - self.log.info(_("Forbidding list of users in KDM")) - kdmrc.set_shell_variable('ShowUsers', 'Selected', 'X-\*-Greeter', '^\s*$') - if gdmconf.exists(): - if oldval_gdmconf != 'false': - self.log.info(_("Forbidding list of users in GDM")) - gdmconf.set_shell_variable('Browser', 'false') - - def allow_remote_root_login(self, arg): - ''' Allow remote root login via sshd. If yes, login is allowed. If without-password, only public-key authentication logins are allowed. See sshd_config(5) man page for more information.''' - sshd_config = self.configfiles.get_config_file(SSHDCONFIG) - - if not sshd_config.exists(): - return - - val = sshd_config.get_match(PERMIT_ROOT_LOGIN_REGEXP, '@1') - - if val != arg: - if arg == "yes": - self.log.info(_('Allowing remote root login')) - sshd_config.exists() and sshd_config.replace_line_matching(PERMIT_ROOT_LOGIN_REGEXP, - 'PermitRootLogin yes', 1) - elif arg == "no": - self.log.info(_('Forbidding remote root login')) - sshd_config.exists() and sshd_config.replace_line_matching(PERMIT_ROOT_LOGIN_REGEXP, - 'PermitRootLogin no', 1) - elif arg == "without-password": - self.log.info(_('Allowing remote root login only by passphrase')) - sshd_config.exists() and sshd_config.replace_line_matching(PERMIT_ROOT_LOGIN_REGEXP, - 'PermitRootLogin without-password', 1) - - def allow_autologin(self, arg): - ''' Allow autologin.''' - autologin = self.configfiles.get_config_file(AUTOLOGIN) - - val = autologin.get_shell_variable('AUTOLOGIN') - - if val != arg: - if arg == "yes": - self.log.info(_('Allowing autologin')) - autologin.set_shell_variable('AUTOLOGIN', 'yes') - else: - self.log.info(_('Forbidding autologin')) - autologin.set_shell_variable('AUTOLOGIN', 'no') - - def password_loader(self, value): - '''Unused''' - self.log.info(_('Activating password in boot loader')) - liloconf = self.configfiles.get_config_file(LILOCONF) - liloconf.exists() and (liloconf.replace_line_matching('^password=', 'password="' + value + '"', 0, 1) or \ - liloconf.insert_after('^boot=', 'password="' + value + '"')) and \ - Perms.chmod(liloconf.path, 0600) - # TODO encrypt password in grub - menulst = self.configfiles.get_config_file(MENULST) - menulst.exists() and (menulst.replace_line_matching('^password\s', 'password "' + value + '"') or \ - menulst.insert_at(0, 'password "' + value + '"')) and \ - Perms.chmod(menulst.path, 0600) - # TODO add yaboot support - - def nopassword_loader(self): - '''Unused''' - self.log.info(_('Removing password in boot loader')) - liloconf = self.configfiles.get_config_file(LILOCONF) - liloconf.exists() and liloconf.remove_line_matching('^password=', 1) - menulst = self.configfiles.get_config_file(MENULST) - menulst.exists() and menulst.remove_line_matching('^password\s') - - def enable_console_log(self, arg, expr='*.*', dev='tty12'): - ''' Log syslog messages on console terminal 12.''' - - syslogconf = self.configfiles.get_config_file(SYSLOGCONF) - - val = syslogconf.get_match('\s*[^#]+/dev/([^ ]+)', '@1') - - if arg == "yes": - if dev != val: - self.log.info(_('Enabling log on console')) - syslogconf.exists() and syslogconf.replace_line_matching('\s*[^#]+/dev/', expr + ' /dev/' + dev, 1) - else: - if val != None: - self.log.info(_('Disabling log on console')) - syslogconf.exists() and syslogconf.remove_line_matching('\s*[^#]+/dev/') - - def check_security(self, arg): - """ Enable daily security checks.""" - cron = self.configfiles.get_config_file(CRON) - cron.remove_line_matching('[^#]+/usr/share/msec/security.sh') - - securitycron = self.configfiles.get_config_file(SECURITYCRON) - - if arg == "yes": - if not securitycron.exists(): - self.log.info(_('Activating daily security check')) - securitycron.symlink(SECURITYSH) - else: - if securitycron.exists(): - self.log.info(_('Disabling daily security check')) - securitycron.unlink() - - def authorize_services(self, arg): - ''' Allow full access to network services controlled by tcp_wrapper (see hosts.deny(5)). If yes, all services are allowed. If local, only connections to local services are authorized. If no, the services must be authorized manually in /etc/hosts.allow (see hosts.allow(5)).''' - - hostsdeny = self.configfiles.get_config_file(HOSTSDENY) - - if hostsdeny.get_match(ALL_REGEXP): - val = "no" - elif hostsdeny.get_match(ALL_LOCAL_REGEXP): - val = "local" - else: - val = "yes" - - if val != arg: - if arg == "yes": - self.log.info(_('Authorizing all services')) - hostsdeny.remove_line_matching(ALL_REGEXP, 1) - hostsdeny.remove_line_matching(ALL_LOCAL_REGEXP, 1) - elif arg == "no": - self.log.info(_('Disabling all services')) - hostsdeny.remove_line_matching(ALL_LOCAL_REGEXP, 1) - hostsdeny.replace_line_matching(ALL_REGEXP, 'ALL:ALL:DENY', 1) - elif arg == "local": - self.log.info(_('Disabling non local services')) - hostsdeny.remove_line_matching(ALL_REGEXP, 1) - hostsdeny.replace_line_matching(ALL_LOCAL_REGEXP, 'ALL:ALL EXCEPT 127.0.0.1:DENY', 1) - - def set_zero_one_variable(self, file, variable, value, one_msg, zero_msg): - ''' Helper function for enable_ip_spoofing_protection, accept_icmp_echo, accept_broadcasted_icmp_echo, - # accept_bogus_error_responses and enable_log_strange_packets.''' - f = self.configfiles.get_config_file(file) - curvalue = f.get_shell_variable(variable) - if value == "yes": - value = "1" - else: - value = "0" - if value != curvalue: - if value == "1": - self.log.info(one_msg) - f.set_shell_variable(variable, 1) - else: - self.log.info(zero_msg) - f.set_shell_variable(variable, 0) - - def enable_ip_spoofing_protection(self, arg, alert=1): - ''' Enable IP spoofing protection.''' - self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.conf.all.rp_filter', arg, 'Enabling ip spoofing protection', 'Disabling ip spoofing protection') - - def enable_dns_spoofing_protection(self, arg, alert=1): - ''' Enable name resolution spoofing protection.''' - hostconf = self.configfiles.get_config_file(HOSTCONF) - - val = hostconf.get_match('nospoof\s+on') - - if arg: - if not val: - self.log.info(_('Enabling name resolution spoofing protection')) - hostconf.replace_line_matching('nospoof', 'nospoof on', 1) - hostconf.replace_line_matching('spoofalert', 'spoofalert on', (alert != 0)) - else: - if val: - self.log.info(_('Disabling name resolution spoofing protection')) - hostconf.remove_line_matching('nospoof') - hostconf.remove_line_matching('spoofalert') - - def accept_icmp_echo(self, arg): - ''' Accept ICMP echo.''' - self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_echo_ignore_all', invert(arg), 'Ignoring icmp echo', 'Accepting icmp echo') - - def accept_broadcasted_icmp_echo(self, arg): - ''' Accept broadcasted ICMP echo.''' - self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_echo_ignore_broadcasts', invert(arg), 'Ignoring broadcasted icmp echo', 'Accepting broadcasted icmp echo') - - def accept_bogus_error_responses(self, arg): - ''' Accept bogus IPv4 error messages.''' - self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_ignore_bogus_error_responses', invert(arg), 'Ignoring bogus icmp error responses', 'Accepting bogus icmp error responses') - - def enable_log_strange_packets(self, arg): - ''' Enable logging of strange network packets.''' - self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.conf.all.log_martians', arg, 'Enabling logging of strange packets', 'Disabling logging of strange packets') - - def enable_sulogin(self, arg): - ''' Ask for root password when going to single user level (man sulogin(8)).''' - inittab = self.configfiles.get_config_file(INITTAB) - - val = inittab.get_match(SULOGIN_REGEXP) - - if arg == "yes": - if not val: - self.log.info(_('Enabling sulogin in single user runlevel')) - inittab.replace_line_matching('[^#]+:S:', '~~:S:wait:/sbin/sulogin', 1) - else: - if val: - self.log.info(_('Disabling sulogin in single user runlevel')) - inittab.remove_line_matching('~~:S:wait:/sbin/sulogin') - - def enable_msec_cron(self, arg): - ''' Perform hourly security check for changes in system configuration.''' - mseccron = self.configfiles.get_config_file(MSECCRON) - - val = mseccron.exists() - - if arg == "yes": - if not val: - self.log.info(_('Enabling msec periodic runs')) - mseccron.symlink(MSECBIN) - else: - if val: - self.log.info(_('Disabling msec periodic runs')) - mseccron.unlink() - - def enable_at_crontab(self, arg): - ''' Enable crontab and at for users. Put allowed users in /etc/cron.allow and /etc/at.allow (see man at(1) and crontab(1)).''' - cronallow = self.configfiles.get_config_file(CRONALLOW) - atallow = self.configfiles.get_config_file(ATALLOW) - - val_cronallow = cronallow.get_match('root') - val_atallow = atallow.get_match('root') - - if arg == "yes": - if val_cronallow or val_atallow: - self.log.info(_('Enabling crontab and at')) - if val_cronallow: - cronallow.exists() and cronallow.move(SUFFIX) - if val_atallow: - atallow.exists() and atallow.move(SUFFIX) - else: - if not val_cronallow or not val_atallow: - self.log.info(_('Disabling crontab and at')) - cronallow.replace_line_matching('root', 'root', 1) - atallow.replace_line_matching('root', 'root', 1) - - def allow_xauth_from_root(self, arg): - ''' Allow to export display when passing from the root account to the other users. See pam_xauth(8) for more details.''' - export = self.configfiles.get_config_file(EXPORT) - - allow = export.get_match('^\*$') - - if arg == 'yes': - if not allow: - self.log.info(_('Allowing export display from root')) - export.insert_at(0, '*') - else: - if allow: - self.log.info(_('Forbidding export display from root')) - export.remove_line_matching('^\*$') - - def allow_root_login(self, arg): - ''' Allow direct root login on terminal.''' - securetty = self.configfiles.get_config_file(SECURETTY) - kde = self.configfiles.get_config_file(KDE) - gdm = self.configfiles.get_config_file(GDM) - gdmconf = self.configfiles.get_config_file(GDMCONF) - xdm = self.configfiles.get_config_file(XDM) - - val = {} - val_kde = kde.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') - val_gdm = gdm.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') - val_xdm = xdm.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') - num = 0 - for n in range(1, 7): - s = 'tty' + str(n) - if securetty.get_match(s): - num = num + 1 - s = 'vc/' + str(n) - if securetty.get_match(s): - num = num + 1 - - if arg == "yes": - if val_kde or val_gdm or val_xdm or num != 12: - self.log.info(_('Allowing direct root login')) - if gdmconf.exists(): - gdmconf.set_shell_variable('ConfigAvailable', 'true', '\[greeter\]', '^\s*$') - - for cnf in [kde, gdm, xdm]: - if cnf.exists(): - cnf.remove_line_matching('^auth\s*required\s*(?:/lib/security/)?pam_listfile.so.*bastille-no-login', 1) - - for n in range(1, 7): - s = 'tty' + str(n) - securetty.replace_line_matching(s, s, 1) - s = 'vc/' + str(n) - securetty.replace_line_matching(s, s, 1) - else: - if gdmconf.exists(): - gdmconf.set_shell_variable('ConfigAvailable', 'false', '\[greeter\]', '^\s*$') - if (kde.exists() and not val_kde) or (gdm.exists() and not val_gdm) or (xdm.exists() and not val_xdm) or num > 0: - self.log.info(_('Forbidding direct root login')) - - bastillenologin = self.configfiles.get_config_file(BASTILLENOLOGIN) - bastillenologin.replace_line_matching('^\s*root', 'root', 1) - - # TODO: simplify this - for cnf in [kde, gdm, xdm]: - if cnf.exists(): - (cnf.replace_line_matching('^auth\s*required\s*(?:/lib/security/)?pam_listfile.so.*bastille-no-login', - 'auth required pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') or - cnf.insert_at(0, 'auth required pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login')) - securetty.remove_line_matching('.+', 1) - - # bogus functions - def secure_tmp(self, param): - """Use secure location for temporary files. If this parameter is set to 'yes', user home directory will be used for temporary files. Otherwise, /tmp will be used.""" - shell = self.configfiles.get_config_file(SHELLCONF) - - val = shell.get_shell_variable('SECURE_TMP') - - if val != param: - if param == 'yes': - self.log.info(_('Using secure location for temporary files')) - else: - self.log.info(_('Not using secure location for temporary files')) - shell.set_shell_variable('SECURE_TMP', param) - pass - - def enable_startup_msec(self, param): - """Enforce MSEC settings on system startup""" - pass - - def enable_startup_perms(self, param): - """Enforce MSEC file directory permissions on system startup. If this parameter is set to 'enforce', system permissions will be enforced automatically, according to system security settings.""" - pass - - def allow_curdir_in_path(self, param): - """Include current directory into user PATH by default""" - msec = self.configfiles.get_config_file(SHELLCONF) - - val = msec.get_shell_variable('ALLOW_CURDIR_IN_PATH') - - if val != param: - if param == 'yes': - self.log.info(_('Allowing including current directory in path')) - msec.set_shell_variable('ALLOW_CURDIR_IN_PATH', param) - else: - self.log.info(_('Not allowing including current directory in path')) - msec.set_shell_variable('ALLOW_CURDIR_IN_PATH', param) - # }}} # {{{ PERMS - permissions handling diff --git a/src/msec/plugins/audit.py b/src/msec/plugins/audit.py index 2e8b839..e2409f5 100644 --- a/src/msec/plugins/audit.py +++ b/src/msec/plugins/audit.py @@ -17,6 +17,7 @@ except IOError: CRON = '/etc/cron.d/msec' CRON_REGEX = '[^#]+/usr/share/msec/promisc_check.sh' CRON_ENTRY = '*/1 * * * * root /usr/share/msec/promisc_check.sh' +SECURITYCRON = '/etc/cron.daily/msec' class audit: def __init__(self, log=None, configfiles=None, root=None): @@ -54,6 +55,8 @@ class audit: config.SETTINGS['MAIL_EMPTY_CONTENT'] = ("audit.mail_empty_content", ['yes', 'no']) config.SETTINGS['SYSLOG_WARN'] = ("audit.syslog_warn", ['yes', 'no']) config.SETTINGS['NOTIFY_WARN'] = ("audit.notify_warn", ['yes', 'no']) + # security checks from audit plugins + config.SETTINGS['CHECK_SECURITY'] = ("audit.check_security", ['yes', 'no']) # preparing msecgui menu for check in ["CHECK_PERMS", "CHECK_USER_FILES", "CHECK_SUID_ROOT", "CHECK_SUID_MD5", "CHECK_SGID", @@ -63,6 +66,13 @@ class audit: "TTY_WARN", "SYSLOG_WARN", "MAIL_EMPTY_CONTENT"]: config.SETTINGS_PERIODIC.append(check) + # checks with exceptions + for check in ["CHECK_PERMS", "CHECK_USER_FILES", "CHECK_SUID_ROOT", "CHECK_SUID_MD5", "CHECK_SGID", + "CHECK_WRITABLE", "CHECK_UNOWNED", "CHECK_OPEN_PORT", "CHECK_FIREWALL", + "CHECK_PASSWD", "CHECK_SHADOW", "CHECK_RPM_PACKAGES", "CHECK_RPM_INTEGRITY", + "CHECK_SHOSTS", "CHECK_USERS", "CHECK_GROUPS"]: + config.CHECKS_WITH_EXCEPTIONS.append(check) + # The following checks are run from crontab. We only have these functions here # to get their descriptions. @@ -177,3 +187,19 @@ class audit: self.log.info(_('Disabling periodic promiscuity check')) cron.remove_line_matching('[^#]+/usr/share/msec/promisc_check.sh') + def check_security(self, arg): + """ Enable daily security checks.""" + cron = self.configfiles.get_config_file(CRON) + cron.remove_line_matching('[^#]+/usr/share/msec/security.sh') + + securitycron = self.configfiles.get_config_file(SECURITYCRON) + + if arg == "yes": + if not securitycron.exists(): + self.log.info(_('Activating daily security check')) + securitycron.symlink(SECURITYSH) + else: + if securitycron.exists(): + self.log.info(_('Disabling daily security check')) + securitycron.unlink() + diff --git a/src/msec/plugins/msec.py b/src/msec/plugins/msec.py new file mode 100644 index 0000000..fd7204d --- /dev/null +++ b/src/msec/plugins/msec.py @@ -0,0 +1,719 @@ +#!/usr/bin/plugin +"""Msec plugin for enforcing security settings""" + +# main plugin class name +PLUGIN = "msec" + +# configuration + +import os +import re +import string +import stat +import sys + +import config + +import gettext +# localization +try: + gettext.install('msec') +except IOError: + _ = str + +# list of config files + +ATALLOW = '/etc/at.allow' +AUTOLOGIN = '/etc/sysconfig/autologin' +BASTILLENOLOGIN = '/etc/bastille-no-login' +CRON = '/etc/cron.d/msec' +CRONALLOW = '/etc/cron.allow' +FSTAB = '/etc/fstab' +GDM = '/etc/pam.d/gdm' +GDMCONF = '/etc/X11/gdm/custom.conf' +HALT = '/usr/bin/halt' +HOSTCONF = '/etc/host.conf' +HOSTSDENY = '/etc/hosts.deny' +INITTAB = '/etc/inittab' +ISSUE = '/etc/issue' +ISSUENET = '/etc/issue.net' +KDE = '/etc/pam.d/kde' +KDMRC = '/usr/share/config/kdm/kdmrc' +LILOCONF = '/etc/lilo.conf' +LOGINDEFS = '/etc/login.defs' +MENULST = '/boot/grub/menu.lst' +SHELLCONF = '/etc/security/shell' +MSECBIN = '/usr/sbin/msec' +MSECCRON = '/etc/cron.hourly/msec' +MSEC_XINIT = '/etc/X11/xinit.d/msec' +OPASSWD = '/etc/security/opasswd' +PASSWD = '/etc/pam.d/passwd' +POWEROFF = '/usr/bin/poweroff' +REBOOT = '/usr/bin/reboot' +SECURITYSH = '/usr/share/msec/security.sh' +SERVER = '/etc/security/msec/server' +SHADOW = '/etc/shadow' +SHUTDOWN = '/usr/bin/shutdown' +SHUTDOWNALLOW = '/etc/shutdown.allow' +SSHDCONFIG = '/etc/ssh/sshd_config' +STARTX = '/usr/bin/startx' +SYSCTLCONF = '/etc/sysctl.conf' +SYSLOGCONF = '/etc/syslog.conf' +XDM = '/etc/pam.d/xdm' +XSERVERS = '/etc/X11/xdm/Xservers' +EXPORT = '/root/.xauth/export' + +# regexps +# X server +SECURETTY = '/etc/securetty' +STARTX_REGEXP = '(\s*serverargs=".*) -nolisten tcp(.*")' +XSERVERS_REGEXP = '(\s*[^#]+/usr/bin/X .*) -nolisten tcp(.*)' +GDMCONF_REGEXP = '(\s*command=.*/X.*?) -nolisten tcp(.*)$' +KDMRC_REGEXP = re.compile('(.*?)-nolisten tcp(.*)$') +# ctrl-alt-del +CTRALTDEL_REGEXP = '^ca::ctrlaltdel:/sbin/shutdown.*' +# consolehelper +CONSOLE_HELPER = 'consolehelper' +# ssh PermitRootLogin +PERMIT_ROOT_LOGIN_REGEXP = '^\s*PermitRootLogin\s+(no|yes|without-password|forced-commands-only)' +# tcp_wrappers +ALL_REGEXP = '^ALL:ALL:DENY' +ALL_LOCAL_REGEXP = '^ALL:ALL EXCEPT 127\.0\.0\.1:DENY' +# sulogin +SULOGIN_REGEXP = '~~:S:wait:/sbin/sulogin' + +def invert(param): + """Returns inverse value for param. E.g., yes becomes no, and no becomes yes.""" + if param == "yes": + return "no" + else: + return "yes" +class msec: + def __init__(self, log=None, configfiles=None, root=None): + """This plugin is responsible for enforcing security settings on the machine.""" + self.log = log + self.configfiles = configfiles + self.root = root + + # associate helper commands with files + self.configfiles.add_config_assoc(INITTAB, '/sbin/telinit q') + self.configfiles.add_config_assoc('/etc(?:/rc.d)?/init.d/(.+)', '[ -f /var/lock/subsys/@1 ] && @0 reload') + self.configfiles.add_config_assoc(SYSCTLCONF, '/sbin/sysctl -e -p /etc/sysctl.conf') + self.configfiles.add_config_assoc(SSHDCONFIG, '[ -f /var/lock/subsys/sshd ] && /etc/rc.d/init.d/sshd restart') + self.configfiles.add_config_assoc(LILOCONF, '[ `/usr/sbin/detectloader` = LILO ] && /sbin/lilo') + self.configfiles.add_config_assoc(SYSLOGCONF, '[ -f /var/lock/subsys/syslog ] && service syslog reload') + self.configfiles.add_config_assoc('^/etc/issue$', '/usr/bin/killall mingetty') + + # security options + config.SETTINGS['USER_UMASK'] = ("msec.set_user_umask", ['*']) + config.SETTINGS['ROOT_UMASK'] = ("msec.set_root_umask", ['*']) + config.SETTINGS['ALLOW_CURDIR_IN_PATH'] = ("msec.allow_curdir_in_path", ['yes', 'no']) + config.SETTINGS['WIN_PARTS_UMASK'] = ("msec.set_win_parts_umask", ['*']) + config.SETTINGS['ACCEPT_BOGUS_ERROR_RESPONSES'] = ("msec.accept_bogus_error_responses", ['yes', 'no']) + config.SETTINGS['ACCEPT_BROADCASTED_ICMP_ECHO'] = ("msec.accept_broadcasted_icmp_echo", ['yes', 'no']) + config.SETTINGS['ACCEPT_ICMP_ECHO'] = ("msec.accept_icmp_echo", ['yes', 'no']) + config.SETTINGS['ALLOW_AUTOLOGIN'] = ("msec.allow_autologin", ['yes', 'no']) + config.SETTINGS['ALLOW_REBOOT'] = ("msec.allow_reboot", ['yes', 'no']) + config.SETTINGS['ALLOW_REMOTE_ROOT_LOGIN'] = ("msec.allow_remote_root_login", ['yes', 'no', 'without-password']) + config.SETTINGS['ALLOW_ROOT_LOGIN'] = ("msec.allow_root_login", ['yes', 'no']) + config.SETTINGS['ALLOW_USER_LIST'] = ("msec.allow_user_list", ['yes', 'no']) + config.SETTINGS['ALLOW_X_CONNECTIONS'] = ("msec.allow_x_connections", ['yes', 'no', 'local']) + config.SETTINGS['ALLOW_XAUTH_FROM_ROOT'] = ("msec.allow_xauth_from_root", ['yes', 'no']) + config.SETTINGS['ALLOW_XSERVER_TO_LISTEN'] = ("msec.allow_xserver_to_listen", ['yes', 'no']) + config.SETTINGS['AUTHORIZE_SERVICES'] = ("msec.authorize_services", ['yes', 'no', 'local']) + config.SETTINGS['CREATE_SERVER_LINK'] = ("msec.create_server_link", ['no', 'remote', 'local']) + config.SETTINGS['ENABLE_AT_CRONTAB'] = ("msec.enable_at_crontab", ['yes', 'no']) + config.SETTINGS['ENABLE_CONSOLE_LOG'] = ("msec.enable_console_log", ['yes', 'no']) + config.SETTINGS['ENABLE_DNS_SPOOFING_PROTECTION'] = ("msec.enable_dns_spoofing_protection", ['yes', 'no']) + config.SETTINGS['ENABLE_IP_SPOOFING_PROTECTION'] = ("msec.enable_ip_spoofing_protection", ['yes', 'no']) + config.SETTINGS['ENABLE_LOG_STRANGE_PACKETS'] = ("msec.enable_log_strange_packets", ['yes', 'no']) + config.SETTINGS['ENABLE_MSEC_CRON'] = ("msec.enable_msec_cron", ['yes', 'no']) + config.SETTINGS['ENABLE_SULOGIN'] = ("msec.enable_sulogin", ['yes', 'no']) + config.SETTINGS['SECURE_TMP'] = ("msec.secure_tmp", ['yes', 'no']) + config.SETTINGS['SHELL_HISTORY_SIZE'] = ("msec.set_shell_history_size", ['*']) + config.SETTINGS['SHELL_TIMEOUT'] = ("msec.set_shell_timeout", ['*']) + config.SETTINGS['ENABLE_STARTUP_MSEC'] = ("msec.enable_startup_msec", ['yes', 'no']) + config.SETTINGS['ENABLE_STARTUP_PERMS'] = ("msec.enable_startup_perms", ['yes', 'no', 'enforce']) + + # system settings + for check in ["ENABLE_STARTUP_MSEC", "ENABLE_STARTUP_PERMS", "ENABLE_MSEC_CRON", + "ENABLE_SULOGIN", "ENABLE_AT_CRONTAB", + "ALLOW_ROOT_LOGIN", "ALLOW_USER_LIST", "ALLOW_AUTOLOGIN", + "ENABLE_CONSOLE_LOG", "CREATE_SERVER_LINK", "ALLOW_XAUTH_FROM_ROOT", + "ALLOW_REBOOT", "SHELL_HISTORY_SIZE", "SHELL_TIMEOUT", "USER_UMASK", "ROOT_UMASK", + "SECURE_TMP", "WIN_PARTS_UMASK", "ALLOW_CURDIR_IN_PATH" + ]: + config.SETTINGS_SYSTEM.append(check) + + # network settings + for check in ["ACCEPT_BOGUS_ERROR_RESPONSES", "ACCEPT_BROADCASTED_ICMP_ECHO", "ACCEPT_ICMP_ECHO", + "ALLOW_REMOTE_ROOT_LOGIN", "ALLOW_X_CONNECTIONS", "ALLOW_XSERVER_TO_LISTEN", + "AUTHORIZE_SERVICES", "ENABLE_DNS_SPOOFING_PROTECTION", "ENABLE_IP_SPOOFING_PROTECTION", + "ENABLE_LOG_STRANGE_PACKETS"]: + config.SETTINGS_NETWORK.append(check) + + def create_server_link(self, param): + ''' Creates the symlink /etc/security/msec/server to point to /etc/security/msec/server.SERVER_LEVEL. The /etc/security/msec/server is used by chkconfig --add to decide to add a service if it is present in the file during the installation of packages. By default, two presets are provided: local (which only enables local services) and remote (which also enables some remote services considered safe). Note that the allowed services must be placed manually into the server.SERVER_LEVEL files when necessary.''' + server = self.configfiles.get_config_file(SERVER) + + if param == "no": + if server.exists(): + self.log.info(_('Allowing unrestricted chkconfig for packages')) + server.unlink() + else: + newpath = "%s.%s" % (SERVER, param) + if server.realpath() != newpath: + self.log.info(_('Restricting chkconfig for packages according to "%s" profile') % param) + server.symlink(newpath) + + def set_root_umask(self, umask): + ''' Set the root umask.''' + msec = self.configfiles.get_config_file(SHELLCONF) + + val = msec.get_shell_variable('UMASK_ROOT') + + if val != umask: + self.log.info(_('Setting root umask to %s') % (umask)) + msec.set_shell_variable('UMASK_ROOT', umask) + + def set_user_umask(self, umask): + ''' Set the user umask.''' + msec = self.configfiles.get_config_file(SHELLCONF) + + val = msec.get_shell_variable('UMASK_USER') + + if val != umask: + self.log.info(_('Setting users umask to %s') % (umask)) + msec.set_shell_variable('UMASK_USER', umask) + + def allow_x_connections(self, arg): + ''' Allow local users to connect to X server. Accepted arguments: yes (all connections are allowed), local (only local connection), no (no connection).''' + + xinit = self.configfiles.get_config_file(MSEC_XINIT) + val = xinit.get_match('/usr/bin/xhost\s*(\+\s*[^#]*)', '@1') + + if val: + if val == '+': + val = "yes" + elif val == "+ localhost": + val = "local" + else: + val = "no" + else: + val = "no" + + if val != arg: + if arg == "yes": + self.log.info(_('Allowing users to connect X server from everywhere')) + xinit.replace_line_matching('/usr/bin/xhost', '/usr/bin/xhost +', 1) + elif arg == "local": + self.log.info(_('Allowing users to connect X server from localhost')) + xinit.replace_line_matching('/usr/bin/xhost', '/usr/bin/xhost + localhost', 1) + elif arg == "no": + self.log.info(_('Restricting X server connection to the console user')) + xinit.remove_line_matching('/usr/bin/xhost', 1) + else: + self.log.error(_('invalid allow_x_connections arg: %s') % arg) + + def allow_xserver_to_listen(self, arg): + ''' Allow X server to accept connections from network on tcp port 6000.''' + + startx = self.configfiles.get_config_file(STARTX) + xservers = self.configfiles.get_config_file(XSERVERS) + gdmconf = self.configfiles.get_config_file(GDMCONF) + kdmrc = self.configfiles.get_config_file(KDMRC) + + val_startx = startx.get_match(STARTX_REGEXP) + val_xservers = xservers.get_match(XSERVERS_REGEXP) + val_gdmconf = gdmconf.get_shell_variable('DisallowTCP') + str = kdmrc.get_shell_variable('ServerArgsLocal', 'X-\*-Core', '^\s*$') + if str: + val_kdmrc = KDMRC_REGEXP.search(str) + else: + val_kdmrc = None + + # TODO: better check for file existance + + if arg == "yes": + if val_startx or val_xservers or val_kdmrc or val_gdmconf != 'false': + self.log.info(_('Allowing the X server to listen to tcp connections')) + if startx.exists(): + startx.replace_line_matching(STARTX_REGEXP, '@1@2') + if xservers.exists(): + xservers.replace_line_matching(XSERVERS_REGEXP, '@1@2', 0, 1) + if gdmconf.exists(): + gdmconf.set_shell_variable('DisallowTCP', 'false', '\[security\]', '^\s*$') + if kdmrc.exists(): + kdmrc.replace_line_matching('^(ServerArgsLocal=.*?)-nolisten tcp(.*)$', '@1@2', 0, 0, 'X-\*-Core', '^\s*$') + else: + if not val_startx or not val_xservers or not val_kdmrc or val_gdmconf != 'true': + self.log.info(_('Forbidding the X server to listen to tcp connection')) + if not val_startx: + startx.exists() and startx.replace_line_matching('serverargs="(.*?)( -nolisten tcp)?"', 'serverargs="@1 -nolisten tcp"') + if not val_xservers: + xservers.exists() and xservers.replace_line_matching('(\s*[^#]+/usr/bin/X .*?)( -nolisten tcp)?$', '@1 -nolisten tcp', 0, 1) + if val_gdmconf != 'true': + gdmconf.exists() and gdmconf.set_shell_variable('DisallowTCP', 'true', '\[security\]', '^\s*$') + if not val_kdmrc: + kdmrc.exists() and kdmrc.replace_line_matching('^(ServerArgsLocal=.*)$', '@1 -nolisten tcp', 'ServerArgsLocal=-nolisten tcp', 0, 'X-\*-Core', '^\s*$') + + def set_shell_timeout(self, val): + ''' Set the shell timeout. A value of zero means no timeout.''' + msec = self.configfiles.get_config_file(SHELLCONF) + try: + timeout = int(val) + except: + self.log.error(_('Invalid shell timeout "%s"') % val) + return + + old = msec.get_shell_variable('TMOUT') + if old: + old = int(old) + + if old != timeout: + self.log.info(_('Setting shell timeout to %s') % timeout) + msec.set_shell_variable('TMOUT', timeout) + + def set_shell_history_size(self, size): + ''' Set shell commands history size. A value of -1 means unlimited.''' + try: + size = int(size) + except: + self.log.error(_('Invalid shell history size "%s"') % size) + return + + msec = self.configfiles.get_config_file(SHELLCONF) + + val = msec.get_shell_variable('HISTFILESIZE') + if val: + val = int(val) + + if size >= 0: + if val != size: + self.log.info(_('Setting shell history size to %s') % size) + msec.set_shell_variable('HISTFILESIZE', size) + else: + if val != None: + self.log.info(_('Removing limit on shell history size')) + msec.remove_line_matching('^HISTFILESIZE=') + + def set_win_parts_umask(self, umask): + ''' Set umask option for mounting vfat and ntfs partitions. If umask is '-1', default system umask is used.''' + fstab = self.configfiles.get_config_file(FSTAB) + try: + test_umask = int(umask) + except: + self.log.error(_('Invalid file system umask "%s"') % umask) + return + if umask == "-1": + fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+)umask=\d+(\s.*)", "@1defaults@3", 0, 1) + fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+)umask=\d+,(.*)", "@1@3", 0, 1) + fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+\S+),umask=\d+(.*)", "@1@3", 0, 1) + else: + fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+\S*)umask=\d+(.*)", "@1umask="+umask+"@3", 0, 1) + fstab.replace_line_matching("(.*\s(vfat|ntfs|ntfs-3g)\s+)(?!.*umask=)(\S+)(.*)", "@1@3,umask="+umask+"@4", 0, 1) + + def allow_reboot(self, arg): + ''' Allow system reboot and shutdown to local users.''' + shutdownallow = self.configfiles.get_config_file(SHUTDOWNALLOW) + sysctlconf = self.configfiles.get_config_file(SYSCTLCONF) + kdmrc = self.configfiles.get_config_file(KDMRC) + gdmconf = self.configfiles.get_config_file(GDMCONF) + inittab = self.configfiles.get_config_file(INITTAB) + shutdown = self.configfiles.get_config_file(SHUTDOWN) + poweroff = self.configfiles.get_config_file(POWEROFF) + reboot = self.configfiles.get_config_file(REBOOT) + halt = self.configfiles.get_config_file(HALT) + + val_shutdownallow = shutdownallow.exists() + val_shutdown = shutdown.exists() + val_poweroff = poweroff.exists() + val_reboot = reboot.exists() + val_halt = halt.exists() + val_sysctlconf = sysctlconf.get_shell_variable('kernel.sysrq') + val_inittab = inittab.get_match(CTRALTDEL_REGEXP) + val_gdmconf = gdmconf.get_shell_variable('SystemMenu') + oldval_kdmrc = kdmrc.get_shell_variable('AllowShutdown', 'X-:\*-Core', '^\s*$') + + if arg == "yes": + if val_shutdownallow or not val_shutdown or not val_poweroff or not val_reboot or not val_halt: + self.log.info(_('Allowing reboot and shutdown to the console user')) + shutdownallow.exists() and shutdownallow.move(SUFFIX) + shutdown.exists() or shutdown.symlink(CONSOLE_HELPER) + poweroff.exists() or poweroff.symlink(CONSOLE_HELPER) + reboot.exists() or reboot.symlink(CONSOLE_HELPER) + halt.exists() or halt.symlink(CONSOLE_HELPER) + if val_sysctlconf == '0': + self.log.info(_('Allowing SysRq key to the console user')) + sysctlconf.set_shell_variable('kernel.sysrq', 1) + if val_gdmconf == 'false': + self.log.info(_('Allowing Shutdown/Reboot in GDM')) + gdmconf.exists() and gdmconf.set_shell_variable('SystemMenu', 'true', '\[greeter\]', '^\s*$') + if kdmrc.exists(): + if oldval_kdmrc != 'All': + self.log.info(_('Allowing Shutdown/Reboot in KDM')) + kdmrc.set_shell_variable('AllowShutdown', 'All', 'X-:\*-Core', '^\s*$') + if not val_inittab: + self.log.info(_('Allowing Ctrl-Alt-Del from console')) + inittab.exists() and inittab.replace_line_matching(CTRALTDEL_REGEXP, 'ca::ctrlaltdel:/sbin/shutdown -t3 -r now', 1) + else: + if not val_shutdownallow or val_shutdown or val_poweroff or val_reboot or val_halt: + self.log.info(_('Forbidding reboot and shutdown to the console user')) + if not shutdownallow.exists(): + self.configfiles.get_config_file(SHUTDOWNALLOW, SUFFIX).touch() + shutdown.exists() and shutdown.unlink() + poweroff.exists() and poweroff.unlink() + reboot.exists() and reboot.unlink() + halt.exists() and halt.unlink() + if val_sysctlconf != '0': + self.log.info(_('Forbidding SysRq key to the console user')) + sysctlconf.set_shell_variable('kernel.sysrq', 0) + if val_gdmconf != 'false': + self.log.info(_('Forbidding Shutdown/Reboot in GDM')) + gdmconf.exists() and gdmconf.set_shell_variable('SystemMenu', 'false', '\[greeter\]', '^\s*$') + if kdmrc.exists(): + if oldval_kdmrc != 'None': + self.log.info(_('Forbidding Shutdown/Reboot in KDM')) + kdmrc.set_shell_variable('AllowShutdown', 'None', 'X-:\*-Core', '^\s*$') + if val_inittab: + self.log.info(_('Forbidding Ctrl-Alt-Del from console')) + inittab.exists() and inittab.remove_line_matching(CTRALTDEL_REGEXP) + + def allow_user_list(self, arg): + ''' Allow display managers (kdm and gdm) to display list of local users.''' + kdmrc = self.configfiles.get_config_file(KDMRC) + gdmconf = self.configfiles.get_config_file(GDMCONF) + + oldval_gdmconf = gdmconf.get_shell_variable('Browser') + oldval_kdmrc = kdmrc.get_shell_variable('ShowUsers', 'X-\*-Greeter', '^\s*$') + + if arg == "yes": + if kdmrc.exists(): + if oldval_kdmrc != 'NotHidden': + self.log.info(_("Allowing list of users in KDM")) + kdmrc.set_shell_variable('ShowUsers', 'NotHidden', 'X-\*-Greeter', '^\s*$') + if gdmconf.exists(): + if oldval_gdmconf != 'true': + self.log.info(_("Allowing list of users in GDM")) + gdmconf.set_shell_variable('Browser', 'true') + else: + if kdmrc.exists(): + if oldval_kdmrc != 'Selected': + self.log.info(_("Forbidding list of users in KDM")) + kdmrc.set_shell_variable('ShowUsers', 'Selected', 'X-\*-Greeter', '^\s*$') + if gdmconf.exists(): + if oldval_gdmconf != 'false': + self.log.info(_("Forbidding list of users in GDM")) + gdmconf.set_shell_variable('Browser', 'false') + + def allow_remote_root_login(self, arg): + ''' Allow remote root login via sshd. If yes, login is allowed. If without-password, only public-key authentication logins are allowed. See sshd_config(5) man page for more information.''' + sshd_config = self.configfiles.get_config_file(SSHDCONFIG) + + if not sshd_config.exists(): + return + + val = sshd_config.get_match(PERMIT_ROOT_LOGIN_REGEXP, '@1') + + if val != arg: + if arg == "yes": + self.log.info(_('Allowing remote root login')) + sshd_config.exists() and sshd_config.replace_line_matching(PERMIT_ROOT_LOGIN_REGEXP, + 'PermitRootLogin yes', 1) + elif arg == "no": + self.log.info(_('Forbidding remote root login')) + sshd_config.exists() and sshd_config.replace_line_matching(PERMIT_ROOT_LOGIN_REGEXP, + 'PermitRootLogin no', 1) + elif arg == "without-password": + self.log.info(_('Allowing remote root login only by passphrase')) + sshd_config.exists() and sshd_config.replace_line_matching(PERMIT_ROOT_LOGIN_REGEXP, + 'PermitRootLogin without-password', 1) + + def allow_autologin(self, arg): + ''' Allow autologin.''' + autologin = self.configfiles.get_config_file(AUTOLOGIN) + + val = autologin.get_shell_variable('AUTOLOGIN') + + if val != arg: + if arg == "yes": + self.log.info(_('Allowing autologin')) + autologin.set_shell_variable('AUTOLOGIN', 'yes') + else: + self.log.info(_('Forbidding autologin')) + autologin.set_shell_variable('AUTOLOGIN', 'no') + + def password_loader(self, value): + '''Unused''' + self.log.info(_('Activating password in boot loader')) + liloconf = self.configfiles.get_config_file(LILOCONF) + liloconf.exists() and (liloconf.replace_line_matching('^password=', 'password="' + value + '"', 0, 1) or \ + liloconf.insert_after('^boot=', 'password="' + value + '"')) and \ + Perms.chmod(liloconf.path, 0600) + # TODO encrypt password in grub + menulst = self.configfiles.get_config_file(MENULST) + menulst.exists() and (menulst.replace_line_matching('^password\s', 'password "' + value + '"') or \ + menulst.insert_at(0, 'password "' + value + '"')) and \ + Perms.chmod(menulst.path, 0600) + # TODO add yaboot support + + def nopassword_loader(self): + '''Unused''' + self.log.info(_('Removing password in boot loader')) + liloconf = self.configfiles.get_config_file(LILOCONF) + liloconf.exists() and liloconf.remove_line_matching('^password=', 1) + menulst = self.configfiles.get_config_file(MENULST) + menulst.exists() and menulst.remove_line_matching('^password\s') + + def enable_console_log(self, arg, expr='*.*', dev='tty12'): + ''' Log syslog messages on console terminal 12.''' + + syslogconf = self.configfiles.get_config_file(SYSLOGCONF) + + val = syslogconf.get_match('\s*[^#]+/dev/([^ ]+)', '@1') + + if arg == "yes": + if dev != val: + self.log.info(_('Enabling log on console')) + syslogconf.exists() and syslogconf.replace_line_matching('\s*[^#]+/dev/', expr + ' /dev/' + dev, 1) + else: + if val != None: + self.log.info(_('Disabling log on console')) + syslogconf.exists() and syslogconf.remove_line_matching('\s*[^#]+/dev/') + + def authorize_services(self, arg): + ''' Allow full access to network services controlled by tcp_wrapper (see hosts.deny(5)). If yes, all services are allowed. If local, only connections to local services are authorized. If no, the services must be authorized manually in /etc/hosts.allow (see hosts.allow(5)).''' + + hostsdeny = self.configfiles.get_config_file(HOSTSDENY) + + if hostsdeny.get_match(ALL_REGEXP): + val = "no" + elif hostsdeny.get_match(ALL_LOCAL_REGEXP): + val = "local" + else: + val = "yes" + + if val != arg: + if arg == "yes": + self.log.info(_('Authorizing all services')) + hostsdeny.remove_line_matching(ALL_REGEXP, 1) + hostsdeny.remove_line_matching(ALL_LOCAL_REGEXP, 1) + elif arg == "no": + self.log.info(_('Disabling all services')) + hostsdeny.remove_line_matching(ALL_LOCAL_REGEXP, 1) + hostsdeny.replace_line_matching(ALL_REGEXP, 'ALL:ALL:DENY', 1) + elif arg == "local": + self.log.info(_('Disabling non local services')) + hostsdeny.remove_line_matching(ALL_REGEXP, 1) + hostsdeny.replace_line_matching(ALL_LOCAL_REGEXP, 'ALL:ALL EXCEPT 127.0.0.1:DENY', 1) + + def set_zero_one_variable(self, file, variable, value, one_msg, zero_msg): + ''' Helper function for enable_ip_spoofing_protection, accept_icmp_echo, accept_broadcasted_icmp_echo, + # accept_bogus_error_responses and enable_log_strange_packets.''' + f = self.configfiles.get_config_file(file) + curvalue = f.get_shell_variable(variable) + if value == "yes": + value = "1" + else: + value = "0" + if value != curvalue: + if value == "1": + self.log.info(one_msg) + f.set_shell_variable(variable, 1) + else: + self.log.info(zero_msg) + f.set_shell_variable(variable, 0) + + def enable_ip_spoofing_protection(self, arg, alert=1): + ''' Enable IP spoofing protection.''' + self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.conf.all.rp_filter', arg, 'Enabling ip spoofing protection', 'Disabling ip spoofing protection') + + def enable_dns_spoofing_protection(self, arg, alert=1): + ''' Enable name resolution spoofing protection.''' + hostconf = self.configfiles.get_config_file(HOSTCONF) + + val = hostconf.get_match('nospoof\s+on') + + if arg: + if not val: + self.log.info(_('Enabling name resolution spoofing protection')) + hostconf.replace_line_matching('nospoof', 'nospoof on', 1) + hostconf.replace_line_matching('spoofalert', 'spoofalert on', (alert != 0)) + else: + if val: + self.log.info(_('Disabling name resolution spoofing protection')) + hostconf.remove_line_matching('nospoof') + hostconf.remove_line_matching('spoofalert') + + def accept_icmp_echo(self, arg): + ''' Accept ICMP echo.''' + self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_echo_ignore_all', invert(arg), 'Ignoring icmp echo', 'Accepting icmp echo') + + def accept_broadcasted_icmp_echo(self, arg): + ''' Accept broadcasted ICMP echo.''' + self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_echo_ignore_broadcasts', invert(arg), 'Ignoring broadcasted icmp echo', 'Accepting broadcasted icmp echo') + + def accept_bogus_error_responses(self, arg): + ''' Accept bogus IPv4 error messages.''' + self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_ignore_bogus_error_responses', invert(arg), 'Ignoring bogus icmp error responses', 'Accepting bogus icmp error responses') + + def enable_log_strange_packets(self, arg): + ''' Enable logging of strange network packets.''' + self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.conf.all.log_martians', arg, 'Enabling logging of strange packets', 'Disabling logging of strange packets') + + def enable_sulogin(self, arg): + ''' Ask for root password when going to single user level (man sulogin(8)).''' + inittab = self.configfiles.get_config_file(INITTAB) + + val = inittab.get_match(SULOGIN_REGEXP) + + if arg == "yes": + if not val: + self.log.info(_('Enabling sulogin in single user runlevel')) + inittab.replace_line_matching('[^#]+:S:', '~~:S:wait:/sbin/sulogin', 1) + else: + if val: + self.log.info(_('Disabling sulogin in single user runlevel')) + inittab.remove_line_matching('~~:S:wait:/sbin/sulogin') + + def enable_msec_cron(self, arg): + ''' Perform hourly security check for changes in system configuration.''' + mseccron = self.configfiles.get_config_file(MSECCRON) + + val = mseccron.exists() + + if arg == "yes": + if not val: + self.log.info(_('Enabling msec periodic runs')) + mseccron.symlink(MSECBIN) + else: + if val: + self.log.info(_('Disabling msec periodic runs')) + mseccron.unlink() + + def enable_at_crontab(self, arg): + ''' Enable crontab and at for users. Put allowed users in /etc/cron.allow and /etc/at.allow (see man at(1) and crontab(1)).''' + cronallow = self.configfiles.get_config_file(CRONALLOW) + atallow = self.configfiles.get_config_file(ATALLOW) + + val_cronallow = cronallow.get_match('root') + val_atallow = atallow.get_match('root') + + if arg == "yes": + if val_cronallow or val_atallow: + self.log.info(_('Enabling crontab and at')) + if val_cronallow: + cronallow.exists() and cronallow.move(SUFFIX) + if val_atallow: + atallow.exists() and atallow.move(SUFFIX) + else: + if not val_cronallow or not val_atallow: + self.log.info(_('Disabling crontab and at')) + cronallow.replace_line_matching('root', 'root', 1) + atallow.replace_line_matching('root', 'root', 1) + + def allow_xauth_from_root(self, arg): + ''' Allow to export display when passing from the root account to the other users. See pam_xauth(8) for more details.''' + export = self.configfiles.get_config_file(EXPORT) + + allow = export.get_match('^\*$') + + if arg == 'yes': + if not allow: + self.log.info(_('Allowing export display from root')) + export.insert_at(0, '*') + else: + if allow: + self.log.info(_('Forbidding export display from root')) + export.remove_line_matching('^\*$') + + def allow_root_login(self, arg): + ''' Allow direct root login on terminal.''' + securetty = self.configfiles.get_config_file(SECURETTY) + kde = self.configfiles.get_config_file(KDE) + gdm = self.configfiles.get_config_file(GDM) + gdmconf = self.configfiles.get_config_file(GDMCONF) + xdm = self.configfiles.get_config_file(XDM) + + val = {} + val_kde = kde.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') + val_gdm = gdm.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') + val_xdm = xdm.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') + num = 0 + for n in range(1, 7): + s = 'tty' + str(n) + if securetty.get_match(s): + num = num + 1 + s = 'vc/' + str(n) + if securetty.get_match(s): + num = num + 1 + + if arg == "yes": + if val_kde or val_gdm or val_xdm or num != 12: + self.log.info(_('Allowing direct root login')) + if gdmconf.exists(): + gdmconf.set_shell_variable('ConfigAvailable', 'true', '\[greeter\]', '^\s*$') + + for cnf in [kde, gdm, xdm]: + if cnf.exists(): + cnf.remove_line_matching('^auth\s*required\s*(?:/lib/security/)?pam_listfile.so.*bastille-no-login', 1) + + for n in range(1, 7): + s = 'tty' + str(n) + securetty.replace_line_matching(s, s, 1) + s = 'vc/' + str(n) + securetty.replace_line_matching(s, s, 1) + else: + if gdmconf.exists(): + gdmconf.set_shell_variable('ConfigAvailable', 'false', '\[greeter\]', '^\s*$') + if (kde.exists() and not val_kde) or (gdm.exists() and not val_gdm) or (xdm.exists() and not val_xdm) or num > 0: + self.log.info(_('Forbidding direct root login')) + + bastillenologin = self.configfiles.get_config_file(BASTILLENOLOGIN) + bastillenologin.replace_line_matching('^\s*root', 'root', 1) + + # TODO: simplify this + for cnf in [kde, gdm, xdm]: + if cnf.exists(): + (cnf.replace_line_matching('^auth\s*required\s*(?:/lib/security/)?pam_listfile.so.*bastille-no-login', + 'auth required pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') or + cnf.insert_at(0, 'auth required pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login')) + securetty.remove_line_matching('.+', 1) + + # bogus functions + def secure_tmp(self, param): + """Use secure location for temporary files. If this parameter is set to 'yes', user home directory will be used for temporary files. Otherwise, /tmp will be used.""" + shell = self.configfiles.get_config_file(SHELLCONF) + + val = shell.get_shell_variable('SECURE_TMP') + + if val != param: + if param == 'yes': + self.log.info(_('Using secure location for temporary files')) + else: + self.log.info(_('Not using secure location for temporary files')) + shell.set_shell_variable('SECURE_TMP', param) + pass + + def enable_startup_msec(self, param): + """Enforce MSEC settings on system startup""" + pass + + def enable_startup_perms(self, param): + """Enforce MSEC file directory permissions on system startup. If this parameter is set to 'enforce', system permissions will be enforced automatically, according to system security settings.""" + pass + + def allow_curdir_in_path(self, param): + """Include current directory into user PATH by default""" + msec = self.configfiles.get_config_file(SHELLCONF) + + val = msec.get_shell_variable('ALLOW_CURDIR_IN_PATH') + + if val != param: + if param == 'yes': + self.log.info(_('Allowing including current directory in path')) + msec.set_shell_variable('ALLOW_CURDIR_IN_PATH', param) + else: + self.log.info(_('Not allowing including current directory in path')) + msec.set_shell_variable('ALLOW_CURDIR_IN_PATH', param) + -- cgit v1.2.1