aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEugeni Dodonov <eugeni@mandriva.org>2009-01-06 21:31:46 +0000
committerEugeni Dodonov <eugeni@mandriva.org>2009-01-06 21:31:46 +0000
commitff31c9236b1fd7465ea9687fc735e8af882e780e (patch)
treeeec89033b4ad0b2459fbb91fa6dd39077eeaf407 /src
parentab984707253940bf5ced3a379699e8d0dc757fa6 (diff)
downloadmsec-ff31c9236b1fd7465ea9687fc735e8af882e780e.tar
msec-ff31c9236b1fd7465ea9687fc735e8af882e780e.tar.gz
msec-ff31c9236b1fd7465ea9687fc735e8af882e780e.tar.bz2
msec-ff31c9236b1fd7465ea9687fc735e8af882e780e.tar.xz
msec-ff31c9236b1fd7465ea9687fc735e8af882e780e.zip
Updated to working version of new msec.
Conflicts: Makefile cron-sh/security_check.sh share/msec.py
Diffstat (limited to 'src')
-rw-r--r--src/msec/.svnignore6
-rw-r--r--src/msec/CHANGES73
-rw-r--r--src/msec/Makefile34
-rw-r--r--src/msec/README87
-rwxr-xr-xsrc/msec/compile.py17
-rw-r--r--src/msec/config.py273
-rwxr-xr-xsrc/msec/help_draksec.py74
-rwxr-xr-xsrc/msec/libmsec.py1763
-rwxr-xr-xsrc/msec/man.py197
-rwxr-xr-xsrc/msec/msec34
-rwxr-xr-xsrc/msec/msec.py141
-rwxr-xr-xsrc/msec/msecgui34
-rwxr-xr-xsrc/msec/msecgui.py725
-rwxr-xr-xsrc/msec/msecperms34
-rwxr-xr-xsrc/msec/msecperms.py152
-rw-r--r--src/msec/version.py1
-rw-r--r--src/msec_find/Makefile2
-rw-r--r--src/promisc_check/Makefile2
18 files changed, 3647 insertions, 2 deletions
diff --git a/src/msec/.svnignore b/src/msec/.svnignore
new file mode 100644
index 0000000..17005aa
--- /dev/null
+++ b/src/msec/.svnignore
@@ -0,0 +1,6 @@
+*.pyo
+*.pyc
+*.flog
+mseclib.man
+mseclib.py
+level.*
diff --git a/src/msec/CHANGES b/src/msec/CHANGES
new file mode 100644
index 0000000..22e546a
--- /dev/null
+++ b/src/msec/CHANGES
@@ -0,0 +1,73 @@
+changes in version 0.30
+=======================
+
+ * don't lower security if the admin has already augmented it (when called without argument).
+ * splitted functions that worked on multiple levels.
+
+changes between version 0.18 and 0.19
+=====================================
+
+msec utility changes:
+
+ * no password in level 0
+
+Periodic security checks changes:
+
+ * config file is now in /var/lib/msec/security.conf and can
+be overriden by /etc/security/msec/security.conf.
+
+changes between version 0.17 and 0.18
+=====================================
+
+msec utility changes:
+
+ * allow /etc/security/msec/level.local to override the default
+setting of the level.
+ * promisc_check.sh works now.
+ * added mseclib man page.
+
+changes between version 0.16 and 0.17
+=====================================
+
+msec utility changes:
+
+ * handle shell timeout (level 4 and 5)
+ * limit shell history (level 4 and 5)
+ * su only for wheel group (level 5)
+ * sulogin for single user mode (level 4 and 5)
+ * various sysctl.conf settings for icmp and network parameters
+ * password aging (level 4 and 5)
+ * suppress /etc/issue.net (level 4 and 5) and /etc/issue (level 5)
+ * removed manipulation of the groups of users
+ * removed removal of services
+ * logging in syslog according to the guideline for explanations in tools
+ * more correct prevention of direct root logins
+ * rewritten in python
+
+msec can be used to change level and it's also run hourly by cron to
+maintain the security level on the system. Only the minimum of changes
+on the filesystem are applied and the minimum of programs started.
+
+Periodic security checks changes:
+
+ * added rpm database checks (rpm -va and rpm -qa)
+ * report when a user other than root is at uid 0
+ * diff_check reports even when the log is empty
+ * use chkrootkit if present.
+
+Permissions settings changes:
+
+ * /
+ * removed audio group handling because it has always conflicted with pam_console
+ * handle /var/log sub-directories in a generic manner
+ * /etc/rc.d/init.d/*
+ * corrected ssh and ping related paths
+ * /etc/sysconfig
+ * /proc
+ * corrected gcc files
+ * rpm related files to avoid exposing what is installed
+ * /var/lock/subsys
+ * added a local.perm to allow modifications without modifying level perms
+ * corrected all the inconsistencies between levels to be able to change and come back
+without problem
+ * rewritten in python
diff --git a/src/msec/Makefile b/src/msec/Makefile
new file mode 100644
index 0000000..8ff37b0
--- /dev/null
+++ b/src/msec/Makefile
@@ -0,0 +1,34 @@
+#---------------------------------------------------------------
+# Project : Mandriva Linux
+# Module : share
+# File : Makefile
+# Version : $Id$
+# Author : Frederic Lepied
+# Created On : Sat Jan 26 20:17:55 2002
+#---------------------------------------------------------------
+
+MAN=../../man/C/msec.8
+PFILES=msecperms.py compile.py libmsec.py man.py
+
+all: compile man help
+
+compile:
+ ./compile.py '/usr/share/msec/' *.py
+
+man: $(MAN)
+
+help: libmsec.py help_draksec.py
+ ./help_draksec.py
+
+$(MAN): libmsec.py man.py
+ rm -f $@
+ ./man.py libmsec > $@
+
+clean:
+ rm -f *.pyc *.pyo mseclib.py *~ help.*
+
+# Local variables:
+# mode: makefile
+# End:
+#
+# Makefile ends here
diff --git a/src/msec/README b/src/msec/README
new file mode 100644
index 0000000..4bb3846
--- /dev/null
+++ b/src/msec/README
@@ -0,0 +1,87 @@
+******************
+Configurations files in /etc/security/msec/
+Shell scripts in /usr/share/msec.
+******************
+
+Suggestions & comments:
+flepied@mandriva.com
+
+******************
+Doc of the rewritting in python:
+
+ 0 1 2 3 4 5
+root umask 022 022 022 022 022 077
+shell timeout 0 0 0 0 3600 900
+deny services none none none none local all
+su only for wheel grp no no no no no yes
+user umask 022 022 022 022 077 077
+shell history size default default default default 10 10
+direct root login yes yes yes yes no no
+remote root login yes yes yes yes no no
+sulogin for single user no no no no yes yes
+user list in [kg]dm yes yes yes yes no no
+promisc check no no no no yes yes
+ignore icmp echo no no no no yes yes
+ignore broadcasted icmp echo no no no no yes yes
+ignore bogus error responses no no no no yes yes
+enable libsafe no no no no yes yes
+allow reboot by user yes yes yes yes no no
+allow crontab/at yes yes yes yes no no
+password aging no no no no 60 30
+allow autologin yes yes yes no no no
+console log no no no yes yes yes
+issues yes yes yes local local no
+ip spoofing protection no no no yes yes yes
+dns spoofing protection no no no yes yes yes
+log stange ip packets no no no yes yes yes
+periodic security check no yes yes yes yes yes
+allow X connections yes local local no no no
+allow xauth from root yes yes yes yes no no
+X server listen to tcp tcp tcp tcp local local
+run msec by cron yes yes yes yes yes yes
+
+Periodic security checks by level:
+
+ 0 1 2 3 4 5
+CHECK_SECURITY no yes yes yes yes yes
+CHECK_PERMS no no no yes yes yes
+CHECK_SUID_ROOT no no yes yes yes yes
+CHECK_SUID_MD5 no no yes yes yes yes
+CHECK_SGID no no yes yes yes yes
+CHECK_WRITABLE no no yes yes yes yes
+CHECK_UNOWNED no no no no yes yes
+CHECK_PROMISC no no no no yes yes
+CHECK_OPEN_PORT no no no yes yes yes
+CHECK_PASSWD no no no yes yes yes
+CHECK_SHADOW no no no yes yes yes
+TTY_WARN no no no no yes yes
+MAIL_WARN no no no yes yes yes
+SYSLOG_WARN no no yes yes yes yes
+RPM_CHECK no no no yes yes yes
+CHKROOTKIT_CHECK no no no yes yes yes
+
+These variables are configured by the user:
+
+MAIL_USER the user to send the dayly reports. If not set, the email is
+sent to root.
+
+PERM_LEVEL is used to determine which file to use to fix
+permissions/owners/groups (from /usr/share/msec/perm.$PERM_LEVEL). If
+not set, the SECURE_LEVEL is used instead. If the file
+/etc/security/msec/perm.local exists, it's used too. The syntax for
+each line if the following:
+
+<file specification> <owner> <permission> [force]
+
+<file specification> can be any glob to specify one or multiple
+files/diretories.
+
+<owner> must be in the form <user>.<group> or <user>. (force only
+user) or .<group> (force only group) or current (keep current user and
+group).
+
+<permission> is an octal number representing the access rights or
+current to keep the current permissions.
+
+If force is present as a 4th argument, it means that msec will enforce
+the permission even if the previous permission was lower.
diff --git a/src/msec/compile.py b/src/msec/compile.py
new file mode 100755
index 0000000..c42a0c2
--- /dev/null
+++ b/src/msec/compile.py
@@ -0,0 +1,17 @@
+#!/usr/bin/python -O
+#############################################################################
+# File : compile.py
+# Package : rpmlint
+# Author : Frederic Lepied
+# Created on : Sat Oct 23 23:40:21 1999
+# Version : $Id$
+# Purpose : byte compile all python files given in arguments.
+#############################################################################
+
+import py_compile
+import sys
+
+for f in sys.argv[2:]:
+ py_compile.compile(f, f+"o", sys.argv[1] + f)
+
+# compile.py ends here
diff --git a/src/msec/config.py b/src/msec/config.py
new file mode 100644
index 0000000..ec55de1
--- /dev/null
+++ b/src/msec/config.py
@@ -0,0 +1,273 @@
+#!/usr/bin/python -O
+"""This is the configuration file for msec.
+The following variables are defined here:
+ SECURITY_LEVELS: list of supported security levels
+ SECURITYCONF: location of msec configuration file
+ SECURITYLOG: log file for msec messages
+ SETTINGS: all security settings, with correspondent options for each
+ level, callback functions, and regexp of valid parameters.
+
+A helper function load_defaults parses the SETTINGS variable.
+
+The MsecConfig class processes the main msec configuration file.
+"""
+
+import gettext
+import sys
+import traceback
+import re
+
+# security levels
+SECURITY_LEVELS = [ "none", "default", "secure" ]
+DEFAULT_LEVEL="default"
+SECURITY_LEVEL="/etc/security/msec/level.%s"
+
+# msec configuration file
+SECURITYCONF = '/etc/security/msec/security.conf'
+
+# permissions
+PERMCONF = '/etc/security/msec/perms.conf'
+PERMISSIONS_LEVEL = '/etc/security/msec/perm.%s' # for level
+
+# logging
+SECURITYLOG = '/var/log/msec.log'
+
+# localization
+try:
+ cat = gettext.Catalog('msec')
+ _ = cat.gettext
+except IOError:
+ _ = str
+
+# shared strings
+MODIFICATIONS_FOUND = _('Modified system files')
+MODIFICATIONS_NOT_FOUND = _('No changes in system files')
+
+# msec callbacks and valid values
+# OPTION callback valid values
+SETTINGS = {'CHECK_SECURITY' : ("check_security", ['yes', 'no']),
+ 'CHECK_PERMS' : ("check_perms", ['yes', 'no']),
+ 'CHECK_USER_FILES' : ("check_user_files", ['yes', 'no']),
+ 'CHECK_SUID_ROOT' : ("check_suid_root", ['yes', 'no']),
+ 'CHECK_SUID_MD5' : ("check_suid_md5", ['yes', 'no']),
+ 'CHECK_SGID' : ("check_sgid", ['yes', 'no']),
+ 'CHECK_WRITABLE' : ("check_writable", ['yes', 'no']),
+ 'CHECK_UNOWNED' : ("check_unowned", ['yes', 'no']),
+ 'CHECK_PROMISC' : ("check_promisc", ['yes', 'no']),
+ 'CHECK_OPEN_PORT' : ("check_open_port", ['yes', 'no']),
+ 'CHECK_PASSWD' : ("check_passwd", ['yes', 'no']),
+ 'CHECK_SHADOW' : ("check_shadow", ['yes', 'no']),
+ 'CHECK_CHKROOTKIT' : ("check_chkrootkit", ['yes', 'no']),
+ 'CHECK_RPM' : ("check_rpm", ['yes', 'no']),
+ 'CHECK_SHOSTS' : ("check_shosts", ['yes', 'no']),
+ # notifications
+ 'TTY_WARN' : ("tty_warn", ['yes', 'no']),
+ 'MAIL_WARN' : ("mail_warn", ['yes', 'no']),
+ 'MAIL_USER' : ("mail_user", ['*']),
+ 'MAIL_EMPTY_CONTENT': ("mail_empty_content", ['yes', 'no']),
+ 'SYSLOG_WARN' : ("syslog_warn", ['yes', 'no']),
+ 'NOTIFY_WARN' : ("notify_warn", ['yes', 'no']),
+ # security options
+ 'USER_UMASK': ("set_user_umask", ['*']),
+ 'ROOT_UMASK': ("set_root_umask", ['*']),
+ 'WIN_PARTS_UMASK': ("set_win_parts_umask", ['no', '*']),
+ 'ACCEPT_BOGUS_ERROR_RESPONSES': ("accept_bogus_error_responses", ['yes', 'no']),
+ 'ACCEPT_BROADCASTED_ICMP_ECHO': ("accept_broadcasted_icmp_echo", ['yes', 'no']),
+ 'ACCEPT_ICMP_ECHO': ("accept_icmp_echo", ['yes', 'no']),
+ 'ALLOW_AUTOLOGIN': ("allow_autologin", ['yes', 'no']),
+ 'ALLOW_REBOOT': ("allow_reboot", ['yes', 'no']),
+ 'ALLOW_REMOTE_ROOT_LOGIN': ("allow_remote_root_login", ['yes', 'no', 'without_password']),
+ 'ALLOW_ROOT_LOGIN': ("allow_root_login", ['yes', 'no']),
+ 'ALLOW_USER_LIST': ("allow_user_list", ['yes', 'no']),
+ 'ALLOW_X_CONNECTIONS': ("allow_x_connections", ['yes', 'no', 'local']),
+ 'ALLOW_XAUTH_FROM_ROOT': ("allow_xauth_from_root", ['yes', 'no']),
+ 'ALLOW_XSERVER_TO_LISTEN': ("allow_xserver_to_listen", ['yes', 'no']),
+ 'AUTHORIZE_SERVICES': ("authorize_services", ['yes', 'no', 'local']),
+ 'CREATE_SERVER_LINK': ("create_server_link", ['no', 'default', 'secure']),
+ 'ENABLE_AT_CRONTAB': ("enable_at_crontab", ['yes', 'no']),
+ 'ENABLE_CONSOLE_LOG': ("enable_console_log", ['yes', 'no']),
+ 'ENABLE_DNS_SPOOFING_PROTECTION':("enable_ip_spoofing_protection", ['yes', 'no']),
+ 'ENABLE_IP_SPOOFING_PROTECTION': ("enable_dns_spoofing_protection", ['yes', 'no']),
+ 'ENABLE_LOG_STRANGE_PACKETS': ("enable_log_strange_packets", ['yes', 'no']),
+ 'ENABLE_MSEC_CRON': ("enable_msec_cron", ['yes', 'no']),
+ 'ENABLE_PAM_ROOT_FROM_WHEEL': ("enable_pam_root_from_wheel", ['yes', 'no']),
+ 'ENABLE_SUDO': ("enable_sudo", ['yes', 'no', 'wheel']),
+ 'ENABLE_PAM_WHEEL_FOR_SU': ("enable_pam_wheel_for_su", ['yes', 'no']),
+ 'ENABLE_SULOGIN': ("enable_sulogin", ['yes', 'no']),
+ 'ENABLE_APPARMOR': ("enable_apparmor", ['yes', 'no']),
+ 'ENABLE_POLICYKIT': ("enable_policykit", ['yes', 'no']),
+ # password stuff
+ 'ENABLE_PASSWORD': ("enable_password", ['yes', 'no']),
+ 'PASSWORD_HISTORY': ("password_history", ['*']),
+ # format: min length, num upper, num digits
+ 'PASSWORD_LENGTH': ("password_length", ['*']),
+ 'SHELL_HISTORY_SIZE': ("set_shell_history_size", ['*']),
+ 'SHELL_TIMEOUT': ("set_shell_timeout", ['*']),
+ }
+
+def find_callback(param):
+ '''Finds a callback for security option'''
+ if param not in SETTINGS:
+ return None
+ else:
+ callback, valid_params = SETTINGS[param]
+ return callback
+
+def find_valid_params(param):
+ '''Finds valid parameters for security option'''
+ if param not in SETTINGS:
+ return None
+ else:
+ callback, valid_params = SETTINGS[param]
+ return valid_params
+
+# helper functions
+def load_defaults(log, level):
+ """Loads default configuration for given security level, returning a
+ MsecConfig instance.
+ """
+ config = MsecConfig(log, config=SECURITY_LEVEL % level)
+ config.load()
+ return config
+
+def load_default_perms(log, level):
+ """Loads default permissions for given security level, returning a
+ MsecConfig instance.
+ """
+ config = PermConfig(log, config=PERMISSIONS_LEVEL % level)
+ config.load()
+ return config
+
+# {{{ MsecConfig
+class MsecConfig:
+ """Msec configuration parser"""
+ def __init__(self, log, config=SECURITYCONF):
+ self.config = config
+ self.options = {}
+ self.comments = []
+ self.log = log
+
+ def load(self):
+ """Loads and parses configuration file"""
+ try:
+ fd = open(self.config)
+ except:
+ self.log.error(_("Unable to load configuration file %s: %s") % (self.config, sys.exc_value[1]))
+ return False
+ for line in fd.readlines():
+ line = line.strip()
+ if line[0] == "#":
+ # comment
+ self.comments.append(line)
+ continue
+ try:
+ option, val = line.split("=", 1)
+ self.options[option] = val
+ except:
+ self.log.warn(_("Bad config option: %s") % line)
+ continue
+ fd.close()
+ return True
+
+ def get(self, option, default=None):
+ """Gets a configuration option, or defines it if not defined"""
+ if option not in self.options:
+ self.options[option] = default
+ return self.options[option]
+
+ def remove(self, option):
+ """Removes a configuration option."""
+ if option in self.options:
+ del self.options[option]
+
+ def set(self, option, value):
+ """Sets a configuration option"""
+ self.options[option] = value
+
+ def list_options(self):
+ """Sorts and returns configuration parameters"""
+ sortedparams = self.options.keys()
+ if sortedparams:
+ sortedparams.sort()
+ return sortedparams
+
+ def save(self):
+ """Saves configuration. Comments go on top"""
+ try:
+ fd = open(self.config, "w")
+ except:
+ self.log.error(_("Unable to save %s: %s") % (self.config, sys.exc_value))
+ return False
+ for comment in self.comments:
+ print >>fd, comment
+ # sorting keys
+ for option in self.list_options():
+ print >>fd, "%s=%s" % (option, self.options[option])
+ return True
+# }}}
+
+# {{{ PermConfig
+class PermConfig(MsecConfig):
+ """Msec file permission parser"""
+ def __init__(self, log, config=PERMCONF):
+ self.config = config
+ self.options = {}
+ self.options_order = []
+ self.comments = []
+ self.log = log
+ self.regexp = re.compile("^([^\s]*)\s*([a-z]*)\.([a-z]*)\s*([\d]?\d\d\d)\s*(force)?$")
+
+ def load(self):
+ """Loads and parses configuration file"""
+ try:
+ fd = open(self.config)
+ except:
+ self.log.error(_("Unable to load configuration file %s: %s") % (self.config, sys.exc_value))
+ return False
+ for line in fd.readlines():
+ line = line.strip()
+ if line[0] == "#":
+ # comment
+ self.comments.append(line)
+ continue
+ try:
+ res = self.regexp.findall(line)
+ if res:
+ if len(res[0]) == 5:
+ file, user, group, perm, force = res[0]
+ else:
+ force = None
+ file, user, group, perm = res[0]
+ self.options[file] = (user, group, perm, force)
+ self.options_order.append(file)
+ except:
+ traceback.print_exc()
+ self.log.warn(_("Bad config option: %s") % line)
+ continue
+ fd.close()
+ return True
+
+ def list_options(self):
+ """Sorts and returns configuration parameters"""
+ return self.options_order
+
+ def save(self):
+ """Saves configuration. Comments go on top"""
+ try:
+ fd = open(self.config, "w")
+ except:
+ self.log.error(_("Unable to save %s: %s") % (self.config, sys.exc_value))
+ return False
+ for comment in self.comments:
+ print >>fd, comment
+ # sorting keys
+ for file in self.options_order:
+ user, group, perm, force = self.options[file]
+ if force:
+ force = "\tforce"
+ else:
+ force = ""
+ print >>fd, "%s\t%s.%s\t%s%s" % (file, user, group, perm, force)
+ return True
+# }}}
+
diff --git a/src/msec/help_draksec.py b/src/msec/help_draksec.py
new file mode 100755
index 0000000..48f0df0
--- /dev/null
+++ b/src/msec/help_draksec.py
@@ -0,0 +1,74 @@
+#!/usr/bin/python
+#
+# This script creates DrakSec help strings from libmsec code docstrings.
+#
+
+import sys
+import imp
+import inspect
+import re
+
+import config
+from libmsec import MSEC, Log
+
+help_perl_file = 'help.pm'
+help_python_file = 'help.py'
+
+header_perl = '''package security::help;
+# !! THIS FILE WAS AUTO-GENERATED BY draksec_help.py !!
+# !! DO NOT MODIFY HERE, MODIFY IN THE *MSEC* CVS !!
+
+use strict;
+use common;
+
+our %help = (
+'''
+
+header_python = '''# libmsec documentation for python.
+
+import gettext
+
+# localization
+try:
+ cat = gettext.Catalog('msec')
+ _ = cat.gettext
+except IOError:
+ _ = str
+
+HELP = {
+'''
+
+footer_perl = ''');
+'''
+
+footer_python = '''}
+'''
+
+### strings used in the rewritting
+function_str_perl = ''''%s' => N("%s"),
+'''
+
+function_str_python = '''
+ '%s': _("%s"), '''
+
+help_perl = open(help_perl_file, "w")
+help_python = open(help_python_file, "w")
+
+# process all configuration parameters
+log = Log(log_syslog=False, log_file=False)
+msec = MSEC(log)
+
+print >>help_perl, header_perl
+print >>help_python, header_python
+
+for variable in config.SETTINGS:
+ callback, params = config.SETTINGS[variable]
+ func = msec.get_action(callback)
+ if func:
+ print >>help_perl, function_str_perl % (variable, func.__doc__.strip())
+ print >>help_python, function_str_python % (variable, func.__doc__.strip())
+
+print >>help_perl, footer_perl
+print >>help_python, footer_python
+
+# draksec_help.py ends here
diff --git a/src/msec/libmsec.py b/src/msec/libmsec.py
new file mode 100755
index 0000000..80de3fc
--- /dev/null
+++ b/src/msec/libmsec.py
@@ -0,0 +1,1763 @@
+#!/usr/bin/python -O
+"""This is the main msec module, responsible for all msec operations.
+
+The following classes are defined here:
+
+ ConfigFile: an individual config file. This class is responsible for
+ configuration modification, variable searching and replacing,
+ and so on.
+
+ ConfigFiles: this file contains the entire set of modifications performed
+ by msec, stored in list of ConfigFile instances. When required, all
+ changes are commited back to physical files. This way, no real
+ change occurs on the system until the msec app explicitly tells
+ to do so.
+
+ Log: logging class, that supports logging to terminal, a fixed log file,
+ and syslog. A single log instance can be shared by all other
+ classes.
+
+ MSEC: main msec class. It contains the callback functions for all msec
+ operations.
+
+All configuration variables, and config file names are defined here as well.
+"""
+
+#---------------------------------------------------------------
+# Project : Mandriva Linux
+# Module : mseclib
+# File : libmsec.py
+# Version : $Id$
+# Author : Eugeni Dodonov
+# Original Author : Frederic Lepied
+# Created On : Mon Dec 10 22:52:17 2001
+# Purpose : low-level msec functions
+#---------------------------------------------------------------
+
+import os
+import grp
+import gettext
+import pwd
+import re
+import string
+import commands
+import time
+import stat
+import traceback
+import sys
+import glob
+
+# logging
+import logging
+from logging.handlers import SysLogHandler
+
+# configuration
+import config
+
+# localization
+try:
+ cat = gettext.Catalog('msec')
+ _ = cat.gettext
+except IOError:
+ _ = str
+
+# 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'
+LDSOPRELOAD = '/etc/ld.so.preload'
+LILOCONF = '/etc/lilo.conf'
+LOGINDEFS = '/etc/login.defs'
+MENULST = '/boot/grub/menu.lst'
+SHELLCONF = '/etc/sysconfig/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'
+SECURETTY = '/etc/securetty'
+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'
+SIMPLE_ROOT_AUTHEN = '/etc/pam.d/simple_root_authen'
+SSHDCONFIG = '/etc/ssh/sshd_config'
+STARTX = '/usr/bin/startx'
+SU = '/etc/pam.d/su'
+SYSCTLCONF = '/etc/sysctl.conf'
+SYSLOGCONF = '/etc/syslog.conf'
+SYSTEM_AUTH = '/etc/pam.d/system-auth'
+XDM = '/etc/pam.d/xdm'
+XSERVERS = '/etc/X11/xdm/Xservers'
+EXPORT = '/root/.xauth/export'
+
+# ConfigFile constants
+STRING_TYPE = type('')
+
+BEFORE=0
+INSIDE=1
+AFTER=2
+
+# regexps
+space = re.compile('\s')
+# X server
+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)'
+# pam
+SUCCEED_MATCH = '^auth\s+sufficient\s+pam_succeed_if.so\s+use_uid\s+user\s+ingroup\s+wheel\s*$'
+SUCCEED_LINE = 'auth sufficient pam_succeed_if.so use_uid user ingroup wheel'
+# cron
+CRON_ENTRY = '*/1 * * * * root /usr/share/msec/promisc_check.sh'
+CRON_REGEX = '[^#]+/usr/share/msec/promisc_check.sh'
+# tcp_wrappers
+ALL_REGEXP = '^ALL:ALL:DENY'
+ALL_LOCAL_REGEXP = '^ALL:ALL EXCEPT 127\.0\.0\.1:DENY'
+# password stuff
+LENGTH_REGEXP = re.compile('^(password\s+required\s+(?:/lib/security/)?pam_cracklib.so.*?)\sminlen=([0-9]+)\s(.*)')
+NDIGITS_REGEXP = re.compile('^(password\s+required\s+(?:/lib/security/)?pam_cracklib.so.*?)\sdcredit=([0-9]+)\s(.*)')
+UCREDIT_REGEXP = re.compile('^(password\s+required\s+(?:/lib/security/)?pam_cracklib.so.*?)\sucredit=([0-9]+)\s(.*)')
+PASSWORD_REGEXP = '^\s*auth\s+sufficient\s+(?:/lib/security/)?pam_permit.so'
+UNIX_REGEXP = re.compile('(^\s*password\s+sufficient\s+(?:/lib/security/)?pam_unix.so.*)\sremember=([0-9]+)(.*)')
+PAM_TCB_REGEXP = re.compile('(^\s*password\s+sufficient\s+(?:/lib/security/)?pam_tcb.so.*)')
+# sulogin
+SULOGIN_REGEXP = '~~:S:wait:/sbin/sulogin'
+
+# {{{ helper functions
+def move(old, new):
+ """Renames files, deleting existent ones when necessary."""
+ try:
+ os.unlink(new)
+ except OSError:
+ pass
+ try:
+ os.rename(old, new)
+ except:
+ error('rename %s %s: %s' % (old, new, str(sys.exc_value)))
+
+def substitute_re_result(res, s):
+ for idx in range(0, (res.lastindex or 0) + 1):
+ subst = res.group(idx) or ''
+ s = string.replace(s, '@' + str(idx), subst)
+ return s
+# }}}
+
+# {{{ Log
+class Log:
+ """Logging class. Logs to both syslog and log file"""
+ def __init__(self,
+ app_name="msec",
+ log_syslog=True,
+ log_file=True,
+ log_level = logging.INFO,
+ log_facility=SysLogHandler.LOG_AUTHPRIV,
+ syslog_address="/dev/log",
+ log_path="/var/log/msec.log",
+ interactive=True):
+ self.log_facility = log_facility
+ self.log_path = log_path
+
+ # buffer
+ self.buffer = None
+
+ # common logging stuff
+ self.logger = logging.getLogger(app_name)
+
+ # syslog
+ if log_syslog:
+ self.syslog_h = SysLogHandler(facility=log_facility, address=syslog_address)
+ formatter = logging.Formatter('%(name)s: %(levelname)s: %(message)s')
+ self.syslog_h.setFormatter(formatter)
+ self.logger.addHandler(self.syslog_h)
+
+ # log to file
+ if log_file:
+ self.file_h = logging.FileHandler(self.log_path)
+ formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
+ self.file_h.setFormatter(formatter)
+ self.logger.addHandler(self.file_h)
+
+ # interactive logging
+ if interactive:
+ self.interactive_h = logging.StreamHandler(sys.stderr)
+ formatter = logging.Formatter('%(levelname)s: %(message)s')
+ self.interactive_h.setFormatter(formatter)
+ self.logger.addHandler(self.interactive_h)
+
+ self.logger.setLevel(log_level)
+
+ def info(self, message):
+ """Informative message (normal msec operation)"""
+ if self.buffer:
+ self.buffer["info"].append(message)
+ else:
+ self.logger.info(message)
+
+ def error(self, message):
+ """Error message (security has changed: authentication, passwords, etc)"""
+ if self.buffer:
+ self.buffer["error"].append(message)
+ else:
+ self.logger.error(message)
+
+ def debug(self, message):
+ """Debugging message"""
+ if self.buffer:
+ self.buffer["debug"].append(message)
+ else:
+ self.logger.debug(message)
+
+ def critical(self, message):
+ """Critical message (big security risk, e.g., rootkit, etc)"""
+ if self.buffer:
+ self.buffer["critical"].append(message)
+ else:
+ self.logger.critical(message)
+
+ def warn(self, message):
+ """Warning message (slight security change, permissions change, etc)"""
+ if self.buffer:
+ self.buffer["warn"].append(message)
+ else:
+ self.logger.warn(message)
+
+ def start_buffer(self):
+ """Beginns message buffering"""
+ self.buffer = {"info": [], "error": [], "debug": [], "critical": [], "warn": []}
+
+ def get_buffer(self):
+ """Returns buffered messages"""
+ messages = self.buffer.copy()
+ del self.buffer
+ self.buffer = None
+ return messages
+
+# }}}
+
+# {{{ ConfigFiles - stores references to all configuration files
+class ConfigFiles:
+ """This class is responsible to store references to all configuration files,
+ mark them as changed, and update on disk when necessary"""
+ def __init__(self, log):
+ """Initializes list of ConfigFiles"""
+ self.files = {}
+ self.modified_files = []
+ self.action_assoc = []
+ self.log = log
+
+ def add(self, file, path):
+ """Appends a path to list of files"""
+ self.files[path] = file
+
+ def modified(self, path):
+ """Marks a file as modified"""
+ if not path in self.modified_files:
+ self.modified_files.append(path)
+
+ def get_config_file(self, path, suffix=None):
+ """Retreives corresponding config file"""
+ try:
+ return self.files[path]
+ except KeyError:
+ return ConfigFile(path, self, self.log, suffix=suffix)
+
+ def add_config_assoc(self, regex, action):
+ """Adds association between a file and an action"""
+ self.log.debug("Adding custom command '%s' for '%s'" % (action, regex))
+ self.action_assoc.append((re.compile(regex), action))
+
+ def write_files(self, commit=True):
+ """Writes all files back to disk"""
+ for f in self.files.values():
+ self.log.debug("Attempting to write %s" % f.path)
+ if commit:
+ f.write()
+
+ if len(self.modified_files) > 0:
+ self.log.info("%s: %s" % (config.MODIFICATIONS_FOUND, " ".join(self.modified_files)))
+ else:
+ self.log.info(config.MODIFICATIONS_NOT_FOUND)
+
+ for f in self.modified_files:
+ for a in self.action_assoc:
+ res = a[0].search(f)
+ if res:
+ s = substitute_re_result(res, a[1])
+ if commit:
+ self.log.info(_('%s modified so launched command: %s') % (f, s))
+ cmd = commands.getstatusoutput(s)
+ cmd = [0, '']
+ if cmd[0] == 0:
+ if cmd[1]:
+ self.log.info(cmd[1])
+ else:
+ self.log.error(cmd[1])
+ else:
+ self.log.info(_('%s modified so should have run command: %s') % (f, s))
+
+# }}}
+
+# {{{ ConfigFile - an individual config file
+class ConfigFile:
+ """This class represents an individual config file.
+ All config files are stored in meta (which is ConfigFiles).
+ All operations are performed in memory, and written when required"""
+ def __init__(self, path, meta, log, root='', suffix=None):
+ """Initializes a config file, and put reference to meta (ConfigFiles)"""
+ self.meta=meta
+ self.path = root + path
+ self.is_modified = 0
+ self.is_touched = 0
+ self.is_deleted = 0
+ self.is_moved = 0
+ self.suffix = suffix
+ self.lines = None
+ self.sym_link = None
+ self.log = log
+ self.meta.add(self, path)
+
+ def get_lines(self):
+ if self.lines == None:
+ file=None
+ try:
+ file = open(self.path, 'r')
+ except IOError:
+ if self.suffix:
+ try:
+ moved = self.path + self.suffix
+ file = open(moved, 'r')
+ move(moved, self.path)
+ self.meta.modified(self.path)
+ except IOError:
+ self.lines = []
+ else:
+ self.lines = []
+ if file:
+ self.lines = string.split(file.read(), "\n")
+ file.close()
+ return self.lines
+
+ def append(self, value):
+ lines = self.lines
+ l = len(lines)
+ if l > 0 and lines[l - 1] == '':
+ lines.insert(l - 1, value)
+ else:
+ lines.append(value)
+ lines.append('')
+
+ def modified(self):
+ self.is_modified = 1
+ self.meta.modified(self.path)
+ return self
+
+ def touch(self):
+ self.is_touched = 1
+ return self
+
+ def symlink(self, link):
+ self.sym_link = link
+ return self
+
+ def exists(self):
+ return os.path.lexists(self.path)
+ #return os.path.exists(self.path) or (self.suffix and os.path.exists(self.path + self.suffix))
+
+ def realpath(self):
+ return os.path.realpath(self.path)
+
+ def move(self, suffix):
+ self.suffix = suffix
+ self.is_moved = 1
+
+ def unlink(self):
+ self.is_deleted = 1
+ self.lines=[]
+ return self
+
+ def write(self):
+ if self.is_deleted:
+ if self.exists():
+ try:
+ os.unlink(self.path)
+ except:
+ error('unlink %s: %s' % (self.path, str(sys.exc_value)))
+ self.log.info(_('deleted %s') % (self.path,))
+ elif self.is_modified:
+ content = string.join(self.lines, "\n")
+ dirname = os.path.dirname(self.path)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ file = open(self.path, 'w')
+ file.write(content)
+ file.close()
+ self.meta.modified(self.path)
+ elif self.is_touched:
+ if os.path.exists(self.path):
+ try:
+ os.utime(self.path, None)
+ except:
+ self.log.error('utime %s: %s' % (self.path, str(sys.exc_value)))
+ elif self.suffix and os.path.exists(self.path + self.suffix):
+ move(self.path + self.suffix, self.path)
+ try:
+ os.utime(self.path, None)
+ except:
+ self.log.error('utime %s: %s' % (self.path, str(sys.exc_value)))
+ else:
+ self.lines = []
+ self.is_modified = 1
+ file = open(self.path, 'w')
+ file.close()
+ self.log.info(_('touched file %s') % (self.path,))
+ elif self.sym_link:
+ done = 0
+ if self.exists():
+ full = os.lstat(self.path)
+ if stat.S_ISLNK(full[stat.ST_MODE]):
+ link = os.readlink(self.path)
+ # to be fixed: resolv relative symlink
+ done = (link == self.sym_link)
+ if not done:
+ try:
+ os.unlink(self.path)
+ except:
+ self.log.error('unlink %s: %s' % (self.path, str(sys.exc_value)))
+ self.log.info(_('deleted %s') % (self.path,))
+ if not done:
+ try:
+ os.symlink(self.sym_link, self.path)
+ except:
+ self.log.error('symlink %s %s: %s' % (self.sym_link, self.path, str(sys.exc_value)))
+ self.log.info(_('made symbolic link from %s to %s') % (self.sym_link, self.path))
+
+ if self.is_moved:
+ move(self.path, self.path + self.suffix)
+ self.log.info(_('moved file %s to %s') % (self.path, self.path + self.suffix))
+ self.meta.modified(self.path)
+ self.is_touched = 0
+ self.is_modified = 0
+ self.is_deleted = 0
+ self.is_moved = 0
+
+ def set_shell_variable(self, var, value, start=None, end=None):
+ regex = re.compile('^' + var + '="?([^#"]+)"?(.*)')
+ lines = self.get_lines()
+ idx=0
+ value=str(value)
+ start_regexp = start
+
+ if start:
+ status = BEFORE
+ start = re.compile(start)
+ else:
+ status = INSIDE
+
+ if end:
+ end = re.compile(end)
+
+ idx = None
+ for idx in range(0, len(lines)):
+ line = lines[idx]
+ if status == BEFORE:
+ if start.search(line):
+ status = INSIDE
+ else:
+ continue
+ elif end and end.search(line):
+ break
+ res = regex.search(line)
+ if res:
+ if res.group(1) != value:
+ if space.search(value):
+ lines[idx] = var + '="' + value + '"' + res.group(2)
+ else:
+ lines[idx] = var + '=' + value + res.group(2)
+ self.modified()
+ self.log.debug(_('set variable %s to %s in %s') % (var, value, self.path,))
+ return self
+ if status == BEFORE:
+ # never found the start delimiter
+ self.log.warning(_('WARNING: never found regexp %s in %s, not writing changes') % (start_regexp, self.path))
+ return self
+ if space.search(value):
+ s = var + '="' + value + '"'
+ else:
+ s = var + '=' + value
+ if idx == None or idx == len(lines):
+ self.append(s)
+ else:
+ lines.insert(idx, s)
+
+ self.modified()
+ self.log.info(_('set variable %s to %s in %s') % (var, value, self.path,))
+ return self
+
+ def get_shell_variable(self, var, start=None, end=None):
+ # if file does not exists, fail quickly
+ if not self.exists():
+ return None
+ if end:
+ end=re.compile(end)
+ if start:
+ start=re.compile(start)
+ regex = re.compile('^' + var + '="?([^#"]+)"?(.*)')
+ lines = self.get_lines()
+ llen = len(lines)
+ start_idx = 0
+ end_idx = llen
+ if start:
+ found = 0
+ for idx in range(0, llen):
+ if start.search(lines[idx]):
+ start_idx = idx
+ found = 1
+ break
+ if found:
+ for idx in range(start_idx, llen):
+ if end.search(lines[idx]):
+ end_idx = idx
+ break
+ else:
+ start_idx = 0
+ for idx in range(end_idx - 1, start_idx - 1, -1):
+ res = regex.search(lines[idx])
+ if res:
+ return res.group(1)
+ return None
+
+ def get_match(self, regex, replace=None):
+ # if file does not exists, fail quickly
+ if not self.exists():
+ return None
+ r=re.compile(regex)
+ lines = self.get_lines()
+ for idx in range(0, len(lines)):
+ res = r.search(lines[idx])
+ if res:
+ if replace:
+ s = substitute_re_result(res, replace)
+ return s
+ else:
+ return lines[idx]
+ return None
+
+ def replace_line_matching(self, regex, value, at_end_if_not_found=0, all=0, start=None, end=None):
+ # if at_end_if_not_found is a string its value will be used as the string to inster
+ r=re.compile(regex)
+ lines = self.get_lines()
+ matches = 0
+
+ if start:
+ status = BEFORE
+ start = re.compile(start)
+ else:
+ status = INSIDE
+
+ if end:
+ end = re.compile(end)
+
+ idx = None
+ for idx in range(0, len(lines)):
+ line = lines[idx]
+ if status == BEFORE:
+ if start.search(line):
+ status = INSIDE
+ else:
+ continue
+ elif end and end.search(line):
+ break
+ res = r.search(line)
+ if res:
+ s = substitute_re_result(res, value)
+ matches = matches + 1
+ if s != line:
+ self.log.debug(_("replaced in %s the line %d:\n%s\nwith the line:\n%s") % (self.path, idx, line, s))
+ lines[idx] = s
+ self.modified()
+ if not all:
+ return matches
+ if matches == 0 and at_end_if_not_found:
+ if type(at_end_if_not_found) == STRING_TYPE:
+ value = at_end_if_not_found
+ self.log.debug(_("appended in %s the line:\n%s") % (self.path, value))
+ if idx == None or idx == len(lines):
+ self.append(value)
+ else:
+ lines.insert(idx, value)
+ self.modified()
+ matches = matches + 1
+ return matches
+
+ def insert_after(self, regex, value, at_end_if_not_found=0, all=0):
+ matches = 0
+ r=re.compile(regex)
+ lines = self.get_lines()
+ for idx in range(0, len(lines)):
+ res = r.search(lines[idx])
+ if res:
+ s = substitute_re_result(res, value)
+ self.log.debug(_("inserted in %s after the line %d:\n%s\nthe line:\n%s") % (self.path, idx, lines[idx], s))
+ lines.insert(idx+1, s)
+ self.modified()
+ matches = matches + 1
+ if not all:
+ return matches
+ if matches == 0 and at_end_if_not_found:
+ self.log.debug(_("appended in %s the line:\n%s") % (self.path, value))
+ self.append(value)
+ self.modified()
+ matches = matches + 1
+ return matches
+
+ def insert_before(self, regex, value, at_top_if_not_found=0, all=0):
+ matches = 0
+ r=re.compile(regex)
+ lines = self.get_lines()
+ for idx in range(0, len(lines)):
+ res = r.search(lines[idx])
+ if res:
+ s = substitute_re_result(res, value)
+ self.log.debug(_("inserted in %s before the line %d:\n%s\nthe line:\n%s") % (self.path, idx, lines[idx], s))
+ lines.insert(idx, s)
+ self.modified()
+ matches = matches + 1
+ if not all:
+ return matches
+ if matches == 0 and at_top_if_not_found:
+ self.log.debug(_("inserted at the top of %s the line:\n%s") % (self.path, value))
+ lines.insert(0, value)
+ self.modified()
+ matches = matches + 1
+ return matches
+
+ def insert_at(self, idx, value):
+ lines = self.get_lines()
+ try:
+ lines.insert(idx, value)
+ self.log.debug(_("inserted in %s at the line %d:\n%s") % (self.path, idx, value))
+ self.modified()
+ return 1
+ except KeyError:
+ return 0
+
+ def remove_line_matching(self, regex, all=0):
+ matches = 0
+ r=re.compile(regex)
+ lines = self.get_lines()
+ for idx in range(len(lines) - 1, -1, -1):
+ res = r.search(lines[idx])
+ if res:
+ self.log.debug(_("removing in %s the line %d:\n%s") % (self.path, idx, lines[idx]))
+ lines.pop(idx)
+ self.modified()
+ matches = matches + 1
+ if not all:
+ return matches
+ return matches
+# }}}
+
+# {{{ MSEC - main class
+class MSEC:
+ """Main msec class. Contains all functions and performs the actions"""
+ def __init__(self, log):
+ """Initializes config files and associations"""
+ # all config files
+ self.log = log
+ self.configfiles = ConfigFiles(log)
+
+ # 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')
+
+ # TODO: add a common function to check parameters
+
+ def reset(self):
+ """Resets the configuration"""
+ self.log.debug("Resetting msec data.")
+ self.configfiles = ConfigFiles(self.log)
+
+ def get_action(self, name):
+ """Determines correspondent function for requested action."""
+ try:
+ func = getattr(self, name)
+ return func
+ except:
+ return None
+
+ def commit(self, really_commit=True):
+ """Commits changes"""
+ if not really_commit:
+ self.log.info(_("In check-only mode, nothing is written back to disk."))
+ self.configfiles.write_files(really_commit)
+
+ def apply(self, curconfig):
+ '''Applies configuration from a MsecConfig instance'''
+ # first, reset previous msec data
+ self.reset()
+ # process all options
+ for opt in curconfig.list_options():
+ # Determines correspondent function
+ action = None
+ callback = config.find_callback(opt)
+ valid_params = config.find_valid_params(opt)
+ if callback:
+ action = self.get_action(callback)
+ if not action:
+ # The required functionality is not supported
+ self.log.info(_("'%s' is not available in this version") % opt)
+ continue
+ self.log.debug("Processing action %s: %s(%s)" % (opt, callback, curconfig.get(opt)))
+ # validating parameters
+ param = curconfig.get(opt)
+ if param not in valid_params and '*' not in valid_params:
+ self.log.error(_("Invalid parameter for %s: '%s'. Valid parameters: '%s'.") % (opt,
+ param,
+ valid_values[opt]))
+ continue
+ action(curconfig.get(opt))
+
+ 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.'''
+ __params__ = ["no", "default", "secure"]
+
+ 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/Forbid X connections. 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):
+ ''' The argument specifies if clients are authorized to connect to the X server on the tcp port 6000 or not.'''
+
+ 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 val_kdmrc:
+ if not val_kdmrc.get_match('^ServerArgsLocal=.* -nolisten tcp'):
+ 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"') % size)
+ 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. A value of None means default umask.'''
+ fstab = self.configfiles.get_config_file(FSTAB)
+
+ if umask == "no":
+ fstab.replace_line_matching("(.*\s(vfat|ntfs)\s+)umask=\d+(\s.*)", "@1defaults@3", 0, 1)
+ fstab.replace_line_matching("(.*\s(vfat|ntfs)\s+)umask=\d+,(.*)", "@1@3", 0, 1)
+ fstab.replace_line_matching("(.*\s(vfat|ntfs)\s+\S+),umask=\d+(.*)", "@1@3", 0, 1)
+ else:
+ fstab.replace_line_matching("(.*\s(vfat|ntfs)\s+\S*)umask=\d+(.*)", "@1umask=0@3", 0, 1)
+ fstab.replace_line_matching("(.*\s(vfat|ntfs)\s+)(?!.*umask=)(\S+)(.*)", "@1@3,umask=0@4", 0, 1)
+
+ def allow_reboot(self, arg):
+ ''' Allow/Forbid 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.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.remove_line_matching(CTRALTDEL_REGEXP)
+
+ def allow_user_list(self, arg):
+ ''' Allow/Forbid the list of users on the system on display managers (kdm and gdm).'''
+ 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_root_login(self, arg):
+ ''' Allow/Forbid direct root login.'''
+ 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)
+
+ def allow_remote_root_login(self, arg):
+ ''' Allow/Forbid remote root login via sshd. You can specify yes, no and without-password. See sshd_config(5) man page for more information.'''
+ sshd_config = self.configfiles.get_config_file(SSHDCONFIG)
+
+ 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 enable_pam_wheel_for_su(self, arg):
+ ''' Enabling su only from members of the wheel group or allow su from any user.'''
+ su = self.configfiles.get_config_file(SU)
+
+ val = su.get_match('^auth\s+required\s+(?:/lib/security/)?pam_wheel.so\s+use_uid\s*$')
+
+ if arg == "yes":
+ if not val:
+ self.log.info(_('Allowing su only from wheel group members'))
+ try:
+ ent = grp.getgrnam('wheel')
+ except KeyError:
+ error(_('no wheel group'))
+ return
+ members = ent[3]
+ if members == [] or members == ['root']:
+ self.log.error(_('wheel group is empty'))
+ return
+ if su.exists():
+ (su.replace_line_matching('^[#\s]*auth\s+required\s+(?:/lib/security/)?pam_wheel.so\s+use_uid\s*$',
+ 'auth required pam_wheel.so use_uid') or \
+ su.insert_after('^auth\s+required', 'auth required pam_wheel.so use_uid'))
+ else:
+ if val:
+ self.log.info(_('Allowing su for all'))
+ if su.exists():
+ su.replace_line_matching('^auth\s+required\s+(?:/lib/security/)?pam_wheel.so\s+use_uid\s*$',
+ '# auth required pam_wheel.so use_uid')
+
+ def enable_pam_root_from_wheel(self, arg):
+ ''' Allow root access without password for the members of the wheel group.'''
+ su = self.configfiles.get_config_file(SU)
+ simple = self.configfiles.get_config_file(SIMPLE_ROOT_AUTHEN)
+
+ if not su.exists():
+ return
+
+ val = su.get_match(SUCCEED_MATCH)
+
+ val_simple = simple.get_match(SUCCEED_MATCH)
+
+ if arg == "yes":
+ if not val or not val_simple:
+ self.log.info(_('Allowing transparent root access for wheel group members'))
+ if not val:
+ print "here2"
+ su.insert_before('^auth\s+sufficient', SUCCEED_LINE)
+ if simple.exists() and not val_simple:
+ simple.insert_before('^auth\s+sufficient', SUCCEED_LINE)
+ else:
+ if val or val_simple:
+ self.log.info(_('Disabling transparent root access for wheel group members'))
+ if val:
+ su.remove_line_matching(SUCCEED_MATCH)
+ if simple.exists() and val_simple:
+ simple.remove_line_matching(SUCCEED_MATCH)
+
+ def allow_autologin(self, arg):
+ ''' Allow/Forbid 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'):
+ ''' Enable/Disable syslog reports to console 12. \\fIexpr\\fP is the expression describing what to log (see syslog.conf(5) for more details) and dev the device to report the log.'''
+
+ 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 enable_security_check(self, arg):
+ ''' Activate/Disable daily security check.'''
+ 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):
+ ''' Configure access to tcp_wrappers services (see hosts.deny(5)). If arg = yes, all services are authorized. If arg = local, only local ones are, and if arg = no, no services are authorized. In this case, To authorize the services you need, use /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/Disable IP spoofing protection.'''
+ # the alert argument is kept for backward compatibility
+ 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/Disable name resolution spoofing protection. If \\fIalert\\fP is true, also reports to syslog.'''
+ 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/Refuse icmp echo.'''
+ self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_echo_ignore_all', arg, 'Ignoring icmp echo', 'Accepting icmp echo')
+
+ def accept_broadcasted_icmp_echo(self, arg):
+ ''' Accept/Refuse broadcasted icmp echo.'''
+ self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_echo_ignore_broadcasts', arg, 'Ignoring broadcasted icmp echo', 'Accepting broadcasted icmp echo')
+
+ def accept_bogus_error_responses(self, arg):
+ ''' Accept/Refuse bogus IPv4 error messages.'''
+ self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.icmp_ignore_bogus_error_responses', arg, 'Ignoring bogus icmp error responses', 'Accepting bogus icmp error responses')
+
+ def enable_log_strange_packets(self, arg):
+ ''' Enable/Disable the logging of IPv4 strange 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 password_length(self, arg):
+ ''' Set the password minimum length and minimum number of digit and minimum number of capitalized letters.'''
+
+ try:
+ length, ndigits, nupper = arg.split(",")
+ length = int(length)
+ ndigits = int(ndigits)
+ nupper = int(nupper)
+ except:
+ self.log.error(_('Invalid password length "%s". Use "length,ndigits,nupper" as parameter') % arg)
+ return
+
+ passwd = self.configfiles.get_config_file(SYSTEM_AUTH)
+
+ val_length = val_ndigits = val_ucredit = 999999
+
+ if passwd.exists():
+ val_length = passwd.get_match(LENGTH_REGEXP, '@2')
+ if val_length:
+ val_length = int(val_length)
+
+ val_ndigits = passwd.get_match(NDIGITS_REGEXP, '@2')
+ if val_ndigits:
+ val_ndigits = int(val_ndigits)
+
+ val_ucredit = passwd.get_match(UCREDIT_REGEXP, '@2')
+ if val_ucredit:
+ val_ucredit = int(val_ucredit)
+
+ if passwd.exists() and (val_length != length or val_ndigits != ndigits or val_ucredit != nupper):
+ self.log.info(_('Setting minimum password length %d') % length)
+ (passwd.replace_line_matching(LENGTH_REGEXP,
+ '@1 minlen=%s @3' % length) or \
+ passwd.replace_line_matching('^password\s+required\s+(?:/lib/security/)?pam_cracklib.so.*',
+ '@0 minlen=%s ' % length))
+
+ (passwd.replace_line_matching(NDIGITS_REGEXP,
+ '@1 dcredit=%s @3' % ndigits) or \
+ passwd.replace_line_matching('^password\s+required\s+(?:/lib/security/)?pam_cracklib.so.*',
+ '@0 dcredit=%s ' % ndigits))
+
+ (passwd.replace_line_matching(UCREDIT_REGEXP,
+ '@1 ucredit=%s @3' % nupper) or \
+ passwd.replace_line_matching('^password\s+required\s+(?:/lib/security/)?pam_cracklib.so.*',
+ '@0 ucredit=%s ' % nupper))
+
+ def enable_password(self, arg):
+ ''' Use password to authenticate users. Take EXTREMELY care when disabling passwords, as it will leave the machine COMPLETELY vulnerable.'''
+ system_auth = self.configfiles.get_config_file(SYSTEM_AUTH)
+
+ val = system_auth.get_match(PASSWORD_REGEXP)
+
+ if arg == "yes":
+ if val:
+ self.log.info(_('Using password to authenticate users'))
+ system_auth.remove_line_matching(PASSWORD_REGEXP)
+ else:
+ if not val:
+ self.log.info(_('Don\'t use password to authenticate users'))
+ system_auth.replace_line_matching(PASSWORD_REGEXP, 'auth sufficient pam_permit.so') or \
+ system_auth.insert_before('auth\s+sufficient', 'auth sufficient pam_permit.so')
+
+ def password_history(self, arg):
+ ''' Set the password history length to prevent password reuse. This is not supported by pam_tcb. '''
+
+ system_auth = self.configfiles.get_config_file(SYSTEM_AUTH)
+
+ pam_tcb = system_auth.get_match(PAM_TCB_REGEXP)
+ if pam_tcb:
+ self.log.info(_('Password history not supported with pam_tcb.'))
+ return
+
+ # verify parameter validity
+ # max
+ try:
+ history = int(arg)
+ except:
+ self.log.error(_('Invalid maximum password history length: "%s"') % arg)
+ return
+
+ if system_auth.exists():
+ val = system_auth.get_match(UNIX_REGEXP, '@2')
+
+ if val and val != '':
+ val = int(val)
+ else:
+ val = 0
+ else:
+ val = 0
+
+ if history != val:
+ if history > 0:
+ self.log.info(_('Setting password history to %d.') % history)
+ system_auth.replace_line_matching(UNIX_REGEXP, '@1 remember=%d@3' % history) or \
+ system_auth.replace_line_matching('(^\s*password\s+sufficient\s+(?:/lib/security/)?pam_unix.so.*)', '@1 remember=%d' % history)
+ opasswd = self.configfiles.get_config_file(OPASSWD)
+ opasswd.exists() or opasswd.touch()
+ else:
+ self.log.info(_('Disabling password history'))
+ system_auth.replace_line_matching(UNIX_REGEXP, '@1@3')
+
+ def enable_sulogin(self, arg):
+ ''' Enable/Disable sulogin(8) in single user level.'''
+ 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')
+
+ # Do we need this?
+ def enable_msec_cron(self, arg):
+ ''' Enable/Disable msec hourly security check.'''
+ 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/Disable 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/forbid 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 check_promisc(self, param):
+ ''' Activate/Disable ethernet cards promiscuity check.'''
+ cron = self.configfiles.get_config_file(CRON)
+
+ val = cron.get_match(CRON_REGEX)
+
+ if param == "yes":
+ if val != CRON_ENTRY:
+ self.log.info(_('Activating periodic promiscuity check'))
+ cron.replace_line_matching(CRON_REGEX, CRON_ENTRY, 1)
+ else:
+ if val:
+ self.log.info(_('Disabling periodic promiscuity check'))
+ cron.remove_line_matching('[^#]+/usr/share/msec/promisc_check.sh')
+
+ # The following checks are run from crontab. We only have these functions here
+ # to get their descriptions.
+
+ def check_security(self, param):
+ """ Enables daily security checks."""
+ pass
+
+ def check_perms(self, param):
+ """ Enables periodic permission checking for system files."""
+ pass
+
+ def check_user_files(self, param):
+ """ Enables permission checking on users' files that should not be owned by someone else, or writable."""
+ pass
+
+ def check_suid_root(self, param):
+ """ Enables checking for additions/removals of suid root files."""
+ pass
+
+ def check_suid_md5(self, param):
+ """ Enables checksum verification for suid files."""
+ pass
+
+ def check_sgid(self, param):
+ """ Enables checking for additions/removals of sgid files."""
+ pass
+
+ def check_writable(self, param):
+ """ Enables checking for files/directories writable by everybody."""
+ pass
+
+ def check_unowned(self, param):
+ """ Enables checking for unowned files."""
+ pass
+
+ def check_open_port(self, param):
+ """ Enables checking for open network ports."""
+ pass
+
+ def check_passwd(self, param):
+ """ Enables password-related checks, such as empty passwords and strange super-user accounts."""
+ pass
+
+ def check_shadow(self, param):
+ """ Enables checking for empty passwords."""
+ pass
+
+ def check_chkrootkit(self, param):
+ """ Enables checking for known rootkits using chkrootkit."""
+ pass
+
+ def check_rpm(self, param):
+ """ Enables verification of installed packages."""
+ pass
+
+ def tty_warn(self, param):
+ """ Enables periodic security check results to terminal."""
+ pass
+
+ def mail_warn(self, param):
+ """ Enables security results submission by email."""
+ pass
+
+ def mail_empty_content(self, param):
+ """ Enables sending of empty mail reports."""
+ pass
+
+ def syslog_warn(self, param):
+ """ Enables logging to system log."""
+ pass
+
+ def mail_user(self, param):
+ """ Defines email to receive security notifications."""
+ pass
+
+ def check_shosts(self, param):
+ """ Enables checking for dangerous options in users' .rhosts/.shosts files."""
+ pass
+# }}}
+
+# {{{ PERMS - permissions handling
+class PERMS:
+ """Permission checking/enforcing."""
+ def __init__(self, log):
+ """Initializes internal variables"""
+ self.log = log
+ self.USER = {}
+ self.GROUP = {}
+ self.USERID = {}
+ self.GROUPID = {}
+ self.files = {}
+ self.fs_regexp = self.build_non_localfs_regexp()
+
+ def get_user_id(self, name):
+ '''Caches and retreives user id correspondent to name'''
+ try:
+ return self.USER[name]
+ except KeyError:
+ try:
+ self.USER[name] = pwd.getpwnam(name)[2]
+ except KeyError:
+ error(_('user name %s not found') % name)
+ self.USER[name] = -1
+ return self.USER[name]
+
+ def get_user_name(self, id):
+ '''Caches and retreives user name correspondent to id'''
+ try:
+ return self.USERID[id]
+ except KeyError:
+ try:
+ self.USERID[id] = pwd.getpwuid(id)[0]
+ except KeyError:
+ error(_('user name not found for id %d') % id)
+ self.USERID[id] = str(id)
+ return self.USERID[id]
+
+ def get_group_id(self, name):
+ '''Caches and retreives group id correspondent to name'''
+ try:
+ return self.GROUP[name]
+ except KeyError:
+ try:
+ self.GROUP[name] = grp.getgrnam(name)[2]
+ except KeyError:
+ error(_('group name %s not found') % name)
+ self.GROUP[name] = -1
+ return self.GROUP[name]
+
+ def get_group_name(self, id):
+ '''Caches and retreives group name correspondent to id'''
+ try:
+ return self.GROUPID[id]
+ except KeyError:
+ try:
+ self.GROUPID[id] = grp.getgrgid(id)[0]
+ except KeyError:
+ error(_('group name not found for id %d') % id)
+ self.GROUPID[id] = str(id)
+ return self.GROUPID[id]
+
+ def build_non_localfs_regexp(self,
+ non_localfs = ['nfs', 'codafs', 'smbfs', 'cifs', 'autofs']):
+ """Build a regexp that matches all the non local filesystems"""
+ try:
+ file = open('/proc/mounts', 'r')
+ except IOError:
+ self.log.error(_('Unable to check /proc/mounts. Assuming all file systems are local.'))
+ return None
+
+ regexp = None
+
+ for line in file.readlines():
+ fields = string.split(line)
+ if fields[2] in non_localfs:
+ if regexp:
+ regexp = regexp + '|' + fields[1]
+ else:
+ regexp = '^(' + fields[1]
+
+ file.close()
+
+ if not regexp:
+ return None
+ else:
+ return re.compile(regexp + ')')
+
+ def commit(self, really_commit=True, enforce=False):
+ """Commits changes.
+ If enforce is True, the permissions on all files are enforced."""
+ if not really_commit:
+ self.log.info(_("In check-only mode, nothing is written back to disk."))
+
+ if len(self.files) > 0:
+ self.log.info("%s: %s" % (config.MODIFICATIONS_FOUND, " ".join(self.files)))
+ else:
+ self.log.info(config.MODIFICATIONS_NOT_FOUND)
+
+
+ for file in self.files:
+ newperm, newuser, newgroup, force = self.files[file]
+ # are we in enforcing mode?
+ if enforce:
+ force = True
+
+ if newuser != None:
+ self.log.info(_("Enforcing user on %s to %s") % (file, self.get_user_name(newuser)))
+ if force and really_commit:
+ try:
+ os.chown(file, newuser, -1)
+ except:
+ self.log.error(_("Error changing user on %s: %s") % (file, sys.exc_value))
+ if newgroup != None:
+ self.log.info(_("Enforcing group on %s to %s") % (file, self.get_group_name(newgroup)))
+ if force and really_commit:
+ try:
+ os.chown(file, -1, newgroup)
+ except:
+ self.log.error(_("Error changing group on %s: %s") % (file, sys.exc_value))
+ # permissions should be last, as chown resets them
+ # on suid files
+ if newperm != None:
+ self.log.info(_("Enforcing permissions on %s to %o") % (file, newperm))
+ if force and really_commit:
+ try:
+ os.chmod(file, newperm)
+ except:
+ self.log.error(_("Error changing permissions on %s: %s") % (file, sys.exc_value))
+
+
+ def check_perms(self, perms):
+ '''Checks permissions for all entries in perms (PermConfig).'''
+
+ for file in perms.list_options():
+ user_s, group_s, perm_s, force = perms.get(file)
+
+ # permission
+ if perm_s == 'current':
+ perm = -1
+ else:
+ try:
+ perm = int(perm_s, 8)
+ except ValueError:
+ self.log.error(_("bad permissions for '%s': '%s'") % (file, perm_s))
+ continue
+
+ # user
+ if user_s == 'current':
+ user = -1
+ else:
+ user = self.get_user_id(user_s)
+
+ # group
+ if group_s == 'current':
+ group = -1
+ else:
+ group = self.get_group_id(group_s)
+
+ # now check the permissions
+ for f in glob.glob(file):
+ # get file properties
+ f = os.path.realpath(f)
+ try:
+ full = os.lstat(f)
+ except OSError:
+ continue
+
+ if self.fs_regexp and self.fs_regexp.search(f):
+ self.log.info(_('Non local file: "%s". Nothing changed.') % fields[0])
+ continue
+
+ curperm = perm
+ mode = stat.S_IMODE(full[stat.ST_MODE])
+
+ if perm != -1 and stat.S_ISDIR(full[stat.ST_MODE]):
+ if curperm & 0400:
+ curperm = curperm | 0100
+ if curperm & 0040:
+ curperm = curperm | 0010
+ if curperm & 0004:
+ curperm = curperm | 0001
+
+ curuser = full[stat.ST_UID]
+ curgroup = full[stat.ST_GID]
+ curperm = mode
+ # checking for subdirectory permissions
+ if f != '/' and f[-1] == '/':
+ f = f[:-1]
+ if f[-2:] == '/.':
+ f = f[:-2]
+ # check for changes
+ newperm = None
+ newuser = None
+ newgroup = None
+ if perm != -1 and perm != curperm:
+ newperm = perm
+ if user != -1 and user != curuser:
+ newuser = user
+ if group != -1 and group != curgroup:
+ newgroup = group
+ if newperm != None or newuser != None or newgroup != None:
+ self.files[f] = (newperm, newuser, newgroup, force)
+ self.log.debug("Updating %s (matched by '%s')" % (f, file))
+ else:
+ # see if any other rule put this file into the list
+ if f in self.files:
+ self.log.debug("Removing previously selected %s (matched by '%s')" % (f, file))
+ del self.files[f]
+ return self.files
+# }}}
+
+
+if __name__ == "__main__":
+ # this should never ever be run directly
+ print >>sys.stderr, """This file should not be run directly."""
+
diff --git a/src/msec/man.py b/src/msec/man.py
new file mode 100755
index 0000000..ce8fb33
--- /dev/null
+++ b/src/msec/man.py
@@ -0,0 +1,197 @@
+#!/usr/bin/python
+#---------------------------------------------------------------
+# Project : Mandriva Linux
+# Module : share
+# File : man.py
+# Version : $Id$
+# Author : Frederic Lepied
+# Created On : Sat Jan 26 17:38:39 2002
+# Purpose : loads a python module and creates a man page from
+# the doc strings of the functions.
+#---------------------------------------------------------------
+
+import sys
+import imp
+import inspect
+
+import config
+from libmsec import MSEC, Log
+try:
+ from version import version
+except:
+ version = "(development version)"
+
+header = '''.ds q \N'34'
+.TH msec %s msec "Mandriva Linux"
+.SH NAME
+msec \- Mandriva Linux security tools
+.SH SYNOPSIS
+.nf
+.B msec [options]
+.B msecperms [options]
+.B msecgui [options]
+.fi
+.SH DESCRIPTION
+.B msec
+is responsible to maintain system security in Mandriva. It supports different security
+configurations, which can be organized into several security levels. Currently, three
+preconfigured security levels are provided:
+
+.TP
+\\fBnone\\fR
+this level aims to provide the most basic security. It should be used when you want to
+manage all aspects of system security on your own.
+
+.TP
+\\fBdefault\\fR
+this is the default security level, which configures a reasonably safe set of security
+features. It activates several periodic system checks, and sends the results of their
+execution by email (by default, the local 'root' account is used).
+
+.TP
+\\fBsecure\\fR
+this level is configured to provide maximum system security, even at the cost of limiting
+the remote access to the system, and local user permissions. It also runs a wider set of
+periodic checks, enforces the local password settings, and periodically checks if the
+system security settings, configured by msec, were modified directly or by some other
+application.
+
+.PP
+
+The security settings are stored in \\fB/etc/security/msec/security.conf\\fR
+file, and default settings for each predefined level are stored in
+\\fB/etc/security/msec/level.LEVEL\\fR. Permissions for files and directories
+that should be enforced or checked for changes are stored in
+\\fB/etc/security/msec/perms.conf\\fR, and default permissions for each
+predefined level are stored in \\fB/etc/security/msec/perm.LEVEL\\fR. Note
+that user-modified parameters take precedence over default level settings. For
+example, when default level configuration forbids direct root logins, this
+setting can be overridden by the user.
+
+.PP
+
+The following options are supported by msec applications:
+
+.TP
+\\fBmsec\\fR:
+.PP
+
+This is the console version of msec. It is responsible for system security configuration
+and checking and transitions between security levels.
+
+When executed without parameters, msec will read the system configuration file
+(/etc/security/msec/security.conf), and enforce the specified security
+settings. The operations are logged to \\fB/var/log/msec.log\\fP file, and also
+to syslog, using \\fBLOG_AUTHPRIV\\fR facility. Please note that msec should
+by run as root.
+
+\\fB\-h, --help\\fR
+ This option will display the list of supported command line options.
+
+\\fB\-l, --level <level>\\fR
+ List the default configuration for given security level.
+
+\\fB\-f, --force <level>\\fR
+ Apply the specified security level to the system, overwritting all
+local changes. This is necessary to initialize a security level, either on first
+install, on when a change to a different level is required.
+
+\\fB\-d\\fR
+ Enable debugging messages.
+
+\\fB\-p, --pretend\\fR
+ Verify the actions that will be performed by msec, without actually
+doing anything to the system. In this mode of operation, msec performs all the
+required tasks, except effectively writting data back to disk.
+
+.TP
+\\fBmsecperms\\fR:
+.PP
+
+This application is responsible for system permission checking and enforcements.
+
+When executed without parameters, msecperms will read the permissions
+configuration file (/etc/security/msec/perms.conf), and enforce the specified
+security settings. The operations are logged to \\fB/var/log/msec.log\\fP file,
+and also to syslog, using \\fBLOG_AUTHPRIV\\fR facility. Please note that msecperms
+should by run as root.
+
+\\fB\-h, --help\\fR
+ This option will display the list of supported command line options.
+
+\\fB\-l, --level <level>\\fR
+ List the default configuration for given security level.
+
+\\fB\-f, --force <level>\\fR
+ Apply the specified security level to the system, overwritting all
+local changes. This is necessary to initialize a security level, either on first
+install, on when a change to a different level is required.
+
+\\fB\-e, --enforce\\fR
+ Enforce the default permissions on all files.
+
+\\fB\-d\\fR
+ Enable debugging messages.
+
+\\fB\-p, --pretend\\fR
+ Verify the actions that will be performed by msec, without actually
+doing anything to the system. In this mode of operation, msec performs all the
+required tasks, except effectively writting data back to disk.
+
+.TP
+\\fBmsecgui\\fR:
+.PP
+
+This is the GTK version of msec. It acts as frontend to all msec functionalities.
+
+\\fB\-h, --help\\fR
+ This option will display the list of supported command line options.
+
+\\fB\-d\\fR
+ Enable debugging messages.
+
+.SH "SECURITY OPTIONS"
+
+The following security options are supported by msec:
+
+''' % version
+
+footer = '''.RE
+.SH NOTES
+Msec applications must be run by root.
+.SH AUTHORS
+Frederic Lepied <flepied@mandriva.com>
+
+Eugeni Dodonov <eugeni@mandriva.com>
+'''
+
+### strings used in the rewritting
+function_str = '''
+.TP 4
+.B \\fI%s\\fP
+%s
+
+MSEC parameter: \\fI%s\\fP
+
+Accepted values: \\fI%s\\fP
+'''
+
+### code
+
+# process all configuration parameters
+log = Log(log_syslog=False, log_file=False)
+msec = MSEC(log)
+
+#print >>sys.stderr, dir(msec.create_server_link)
+
+print header
+
+for variable in config.SETTINGS:
+ callback, params = config.SETTINGS[variable]
+ func = msec.get_action(callback)
+ if func:
+ print function_str % (callback, func.__doc__.strip(), variable, ", ".join(params))
+
+print footer
+
+# man.py ends here
diff --git a/src/msec/msec b/src/msec/msec
new file mode 100755
index 0000000..48527bb
--- /dev/null
+++ b/src/msec/msec
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Wrapper for msec.py
+#
+
+if [ "`whoami`" != "root" ]; then
+ echo 'msec: sorry, you must be root !'
+ exit 1
+fi
+
+LCK=/var/run/msec.pid
+
+function cleanup() {
+ rm -f $LCK
+}
+
+if [ -f $LCK ]; then
+ if [ -d /proc/`cat $LCK` ]; then
+ exit 0
+ else
+ rm -f $LCK
+ fi
+fi
+
+echo -n $$ > $LCK
+
+trap cleanup 0
+
+MSEC=/usr/share/msec/msec.py
+OPT="$@"
+
+$MSEC $OPT
+
+# msec ends here
diff --git a/src/msec/msec.py b/src/msec/msec.py
new file mode 100755
index 0000000..010f9ce
--- /dev/null
+++ b/src/msec/msec.py
@@ -0,0 +1,141 @@
+#!/usr/bin/python -O
+"""This is the main msec module.
+It checks/sets the security levels, configures security variables,
+and works as a frontend to libmsec.
+"""
+
+import sys
+import os
+import string
+import getopt
+import gettext
+import imp
+import re
+
+# config
+import config
+
+# version
+try:
+ from version import version
+except:
+ version = "development version"
+
+# libmsec
+from libmsec import MSEC, Log
+
+import logging
+
+# localization
+try:
+ cat = gettext.Catalog('msec')
+ _ = cat.gettext
+except IOError:
+ _ = str
+
+# {{{ usage
+def usage():
+ """Prints help message"""
+ print """Msec: Mandriva Security Center (%s).
+
+When run without parameters, msec will read the configuration from
+/etc/security/msec/msec.conf, and enforce the specified security settings.
+If no configuration file is found on the system, a default configuration
+will be created.
+
+Arguments to msec:
+ -h, --help displays this helpful message.
+ -l, --level <level> displays configuration for specified security
+ level.
+ -f, --force <level> force new level, overwriting user settings.
+ -d enable debugging messages.
+ -p, --pretend only pretend to change the level, perform no real
+ actions. Use this to see what operations msec
+ will perform.
+""" % version
+# }}}
+
+if __name__ == "__main__":
+ # default options
+ force_level = False
+ log_level = logging.INFO
+ commit = True
+ level = config.DEFAULT_LEVEL
+
+ # parse command line
+ try:
+ opt, args = getopt.getopt(sys.argv[1:], 'hl:f:dp', ['help', 'list', 'force', 'debug', 'pretend'])
+ except getopt.error:
+ usage()
+ sys.exit(1)
+ for o in opt:
+ # help
+ if o[0] == '-h' or o[0] == '--help':
+ usage()
+ sys.exit(0)
+ # list
+ elif o[0] == '-l' or o[0] == '--list':
+ level = o[1]
+ log = Log(interactive=True, log_syslog=False, log_file=False)
+ levelconf = config.load_defaults(log, level)
+ params = levelconf.list_options()
+ if not params:
+ print >>sys.stderr, _("Invalid security level '%s'.") % level
+ sys.exit(1)
+ for item in params:
+ print "%s=%s" % (item, levelconf.get(item) )
+ sys.exit(0)
+ # force new level
+ elif o[0] == '-f' or o[0] == '--force':
+ level = o[1]
+ force_level = True
+ # debugging
+ elif o[0] == '-d' or o[0] == '--debug':
+ log_level = logging.DEBUG
+ # check-only mode
+ elif o[0] == '-p' or o[0] == '--pretend':
+ commit = False
+
+ # verifying use id
+ if os.geteuid() != 0:
+ print >>sys.stderr, _("Msec: Mandriva Security Center (%s)\n") % version
+ print >>sys.stderr, _("Error: This application must be executed by root!")
+ print >>sys.stderr, _("Run with --help to get help.")
+ sys.exit(1)
+
+ # configuring logging
+ interactive = sys.stdin.isatty()
+ if interactive:
+ # logs to file and to terminal
+ log = Log(log_path=config.SECURITYLOG, interactive=True, log_syslog=False, log_level=log_level)
+ else:
+ log = Log(log_path=config.SECURITYLOG, interactive=False, log_level=log_level)
+
+ # loading initial config
+ msec_config = config.MsecConfig(log, config=config.SECURITYCONF)
+ if not msec_config.load() and not force_level:
+ log.error(_("Level configuration not found, please run '%s -f <level>' to initialize.") % sys.argv[0])
+
+ # forcing new level
+ if force_level:
+ # first load the default configuration for level
+ levelconf = config.load_defaults(log, level)
+ params = levelconf.list_options()
+ if not params:
+ log.error(_("Default configuration for level '%s' not found, aborting.") % level)
+ sys.exit(1)
+ for opt in params:
+ msec_config.set(opt, levelconf.get(opt))
+
+ # load the msec library
+ msec = MSEC(log)
+
+ # apply the config to msec
+ msec.apply(msec_config)
+ # writing back changes
+ msec.commit(commit)
+ # saving updated config
+ if force_level and commit:
+ if not msec_config.save():
+ log.error(_("Unable to save config!"))
+ sys.exit(0)
diff --git a/src/msec/msecgui b/src/msec/msecgui
new file mode 100755
index 0000000..843eed0
--- /dev/null
+++ b/src/msec/msecgui
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Wrapper for msecgui
+#
+
+if [ "`whoami`" != "root" ]; then
+ echo 'msec: sorry, you must be root !'
+ exit 1
+fi
+
+LCK=/var/run/msec.pid
+
+function cleanup() {
+ rm -f $LCK
+}
+
+if [ -f $LCK ]; then
+ if [ -d /proc/`cat $LCK` ]; then
+ exit 0
+ else
+ rm -f $LCK
+ fi
+fi
+
+echo -n $$ > $LCK
+
+trap cleanup 0
+
+MSEC=/usr/share/msec/msecgui.py
+OPT="$@"
+
+$MSEC $OPT
+
+# msec ends here
diff --git a/src/msec/msecgui.py b/src/msec/msecgui.py
new file mode 100755
index 0000000..3e86506
--- /dev/null
+++ b/src/msec/msecgui.py
@@ -0,0 +1,725 @@
+#!/usr/bin/python -O
+"""
+This is graphical frontend to msec.
+"""
+
+import os
+import sys
+import string
+import getopt
+
+# PyGTK
+import gtk
+#import gtk.glade
+import pygtk
+import gobject
+import pango
+
+# config
+import config
+
+# version
+try:
+ from version import version
+except:
+ version = "development version"
+
+# libmsec
+from libmsec import MSEC, Log
+
+import logging
+
+# localization
+import gettext
+try:
+ cat = gettext.Catalog('msec')
+ _ = cat.gettext
+except IOError:
+ _ = str
+
+# localized help
+try:
+ from help import HELP
+except:
+ HELP = {}
+
+# text strings
+BASIC_SECURITY_TEXT=_("""Basic security options.
+
+These options control the basic aspects of system security. You may select
+a pre-defined profile, or customize the options.
+
+The following security profiles are defined in this version:
+
+ - <b>None</b>: this profile disables additional system security, and it should
+ be used when you want to fine-tune the system on your own.
+
+ - <b>Default</b>: this is the default profile, which configures a reasonably
+ safe set of security features. It activates several periodic system checks,
+ and mails their results daily to the selected email (by default, the local
+ 'root' account is used to receive such emails).
+
+ - <b>Secure</b>: this profile is configured to provide maximum security, even
+ at the cost of limiting the remote access to the system. It also runs a wider
+ set of periodic checks, enforces the local password settings, and periodically
+ checks if the system security settings, configured here, were modified.
+""")
+
+SYSTEM_SECURITY_TEXT=_("""System security options.
+
+These options control the local security configuration, such as the login restrictions,
+password configurations, integration with other security tools, and default file creation
+permissions.
+""")
+
+NETWORK_SECURITY_TEXT=_("""Network security options.
+
+These options define the network security agains remote treats, unauthorized accesses,
+and breakin attempts.
+""")
+
+PERIODIC_SECURITY_TEXT=_("""Periodic security checks.
+
+These options configure the security checks that should be executed periodically.
+""")
+
+NOTIFICATIONS_TEXT=_("""Security notifications.
+
+This page allows to configure the different ways the security notifications can be
+delivered.
+
+It is possible to receive notifications by e-mail, using syslog, using an exclusive
+log file, or using desktop environment notification system.
+""")
+
+PERMISSIONS_SECURITY_TEXT=_("""File permissions.
+
+These options allow to fine-tune system permissions for important files and directores.
+
+The following permissions are checked periodically, and any change to the owner, group,
+or current permission is reported. The permissions can be enforced, automatically
+changing them to the specified values when a change is detected.
+""")
+
+class MsecGui:
+ """Msec GUI"""
+ # common columns
+ (COLUMN_OPTION, COLUMN_DESCR, COLUMN_VALUE) = range(3)
+ (COLUMN_PATH, COLUMN_USER, COLUMN_GROUP, COLUMN_PERM, COLUMN_FORCE) = range(5)
+
+ def __init__(self, log, msec, config, perms):
+ """Initializes gui"""
+ self.log = log
+ self.msec = msec
+ self.config = config
+ # save original configuration
+ self.oldconfig = {}
+ for opt in config.list_options():
+ self.oldconfig[opt] = config.get(opt)
+ self.perms = perms
+ self.window = gtk.Window()
+ self.window.set_default_size(640, 480)
+ self.window.connect('destroy', self.quit)
+
+ # are we enforcing a level
+ self.enforced_level = None
+ self.enforcing_level = False
+
+ main_vbox = gtk.VBox(homogeneous=False, spacing=5)
+ self.window.add(main_vbox)
+
+ # main frame
+ frame = gtk.Frame()
+ main_vbox.pack_start(frame)
+
+ # notebook
+ self.notebook = gtk.Notebook()
+ frame.add(self.notebook)
+
+ self.notebook.append_page(self.basic_security_page(), gtk.Label(_("Basic security")))
+ self.notebook.append_page(self.system_security_page(), gtk.Label(_("System security")))
+ self.notebook.append_page(self.network_security_page(), gtk.Label(_("Network security")))
+ self.notebook.append_page(self.periodic_security_page(), gtk.Label(_("Periodic checks")))
+ self.notebook.append_page(self.notifications_page(), gtk.Label(_("Security notifications")))
+ self.notebook.append_page(self.permissions_security_page(), gtk.Label(_("Permissions")))
+
+ # control hbox
+ hbox = gtk.HBox(homogeneous=False, spacing=10)
+ main_vbox.pack_start(hbox, False, False)
+
+ # control buttons
+ # TODO: improve spacing
+ cancel = gtk.Button(gtk.STOCK_CANCEL)
+ cancel.set_use_stock(True)
+ cancel.connect('clicked', self.cancel)
+ hbox.pack_start(cancel, expand=True, fill=True)
+ help = gtk.Button(gtk.STOCK_HELP)
+ help.set_use_stock(True)
+ help.connect('clicked', self.help)
+ hbox.pack_start(help, expand=True, fill=True)
+ ok = gtk.Button(gtk.STOCK_OK)
+ ok.set_use_stock(True)
+ ok.connect('clicked', self.ok)
+ hbox.pack_start(ok, expand=True, fill=True)
+
+ self.window.show_all()
+
+ def cancel(self, widget):
+ """Cancel button"""
+ print "Cancel clicked."
+ self.quit(widget)
+
+ def help(self, widget):
+ """Help button"""
+ print "Help clicked."
+
+ def ok(self, widget):
+ """Ok button"""
+ print "Ok clicked."
+ # first, let's reset previous msec data
+ self.msec.reset()
+ # start buffered logging
+ self.log.start_buffer()
+ # are we enforcing a level?
+ if self.enforcing_level:
+ print ">> Enforcing level %s" % self.enforced_level
+ curconfig = config.load_defaults(self.log, self.enforced_level)
+ else:
+ curconfig = self.config
+ # apply config and preview changes
+ self.msec.apply(curconfig)
+ msec.commit(False)
+ messages = self.log.get_buffer()
+
+ # creating preview window
+ dialog = gtk.Dialog(_("Preview changes"),
+ self.window, 0,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OK, gtk.RESPONSE_OK)
+ )
+ sw = gtk.ScrolledWindow()
+ sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+ sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ dialog.vbox.add(sw)
+
+ vbox = gtk.VBox()
+ dialog.vbox.set_size_request(640, 300)
+ sw.add_with_viewport(vbox)
+ label = gtk.Label(_("Click OK to commit changes, or CANCEL to leave current configuration unmodified."))
+ vbox.pack_start(label, False, False)
+
+ # informative label
+ label = gtk.Label(_('<b>New msec configuration:</b>'))
+ label.set_use_markup(True)
+ vbox.pack_start(label, False, False)
+
+ # check for changed options
+ opt_changes = []
+ for opt in self.oldconfig:
+ if curconfig.get(opt) != self.oldconfig[opt]:
+ opt_changes.append(opt)
+
+ if len(opt_changes) > 0:
+ # some configuration parameters were changed
+ label = gtk.Label(_('<b>MSEC option changed:</b> <i>%s</i>\n') % ", ".join(opt_changes))
+ label.set_use_markup(True)
+ label.set_line_wrap(True)
+ vbox.pack_start(label, False, False)
+ else:
+ label = gtk.Label(_('<b>No changes in MSEC options.</b>'))
+ label.set_use_markup(True)
+ vbox.pack_start(label, False, False)
+
+ # see if there were any changes to system files
+ for msg in messages['info']:
+ if msg.find(config.MODIFICATIONS_FOUND) != -1 or msg.find(config.MODIFICATIONS_NOT_FOUND) != -1:
+ label = gtk.Label('<i>%s</i>' % msg)
+ label.set_line_wrap(True)
+ label.set_use_markup(True)
+ vbox.pack_start(label, False, False)
+ break
+ # a separator
+ vbox.pack_start(gtk.HSeparator(), False, False)
+ # adding specific messages
+ for cat in ['info', 'critical', 'error', 'warn', 'debug']:
+ msgs = messages[cat]
+ expander = gtk.Expander(_('Verbose information (%s): %d') % (cat, len(msgs)))
+ textview = gtk.TextView()
+ textview.set_wrap_mode(gtk.WRAP_WORD_CHAR)
+ textview.set_editable(False)
+ expander.add(textview)
+ count = 1
+ for msg in msgs:
+ buffer = textview.get_buffer()
+ end = buffer.get_end_iter()
+ buffer.insert(end, "%d: %s\n" % (count, msg))
+ count += 1
+ vbox.pack_start(expander, False, False)
+
+ dialog.show_all()
+ response = dialog.run()
+ if response != gtk.RESPONSE_OK:
+ dialog.destroy()
+ return
+ dialog.destroy()
+
+ # well, let's commit it!
+ if self.enforcing_level:
+ # rewriting configuration
+ for opt in curconfig.list_options():
+ self.config.set(opt, curconfig.get(opt))
+ # saving the configuration
+ self.config.save()
+ msec.apply(self.config)
+ msec.commit(True)
+ self.quit(widget)
+
+ def create_treeview(self, options):
+ """Creates a treeview from given list of options"""
+ sw = gtk.ScrolledWindow()
+ sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+ sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+
+ # list of options
+ lstore = gtk.ListStore(
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING)
+
+ # treeview
+ treeview = gtk.TreeView(lstore)
+ treeview.set_rules_hint(True)
+ treeview.set_search_column(self.COLUMN_DESCR)
+
+ treeview.connect('row-activated', self.option_changed, lstore)
+
+ # configuring columns
+
+ # column for option names
+ column = gtk.TreeViewColumn(_('Security Option'), gtk.CellRendererText(), text=self.COLUMN_OPTION)
+ column.set_sort_column_id(self.COLUMN_OPTION)
+ treeview.append_column(column)
+
+ # column for descriptions
+ renderer = gtk.CellRendererText()
+ renderer.set_property('wrap-width', 400)
+ renderer.set_property('wrap-mode', pango.WRAP_WORD_CHAR)
+ column = gtk.TreeViewColumn(_('Description'), renderer, text=self.COLUMN_DESCR)
+ column.set_sort_column_id(self.COLUMN_DESCR)
+ treeview.append_column(column)
+
+ # column for values
+ column = gtk.TreeViewColumn(_('Value'), gtk.CellRendererText(), text=self.COLUMN_VALUE)
+ column.set_sort_column_id(self.COLUMN_VALUE)
+ treeview.append_column(column)
+
+ sw.add(treeview)
+
+ for option in options:
+ # retreiving option description
+ if not config.SETTINGS.has_key(option):
+ # invalid option
+ self.log.error(_("Invalid option '%s'!") % option)
+ continue
+ # getting level settings, callback and valid params
+ callback, params = config.SETTINGS[option]
+ # getting the function description
+ if option in HELP:
+ self.log.debug("found localized help for %s" % option)
+ doc = HELP[option]
+ else:
+ # get description from function comments
+ func = msec.get_action(callback)
+ if func:
+ doc = func.__doc__.strip()
+ else:
+ doc = callback
+
+ # now for the value
+ value = self.config.get(option)
+
+ # building the option
+ iter = lstore.append()
+ lstore.set(iter,
+ self.COLUMN_OPTION, option,
+ self.COLUMN_DESCR, doc,
+ self.COLUMN_VALUE, value,
+ )
+ return sw
+
+ def basic_security_page(self):
+ """Builds the basic security page"""
+ vbox = gtk.VBox(homogeneous=False)
+
+ # security levels
+
+ levels = config.SECURITY_LEVELS
+
+ entry = gtk.Label(BASIC_SECURITY_TEXT)
+ entry.set_use_markup(True)
+ vbox.pack_start(entry, False, False)
+
+ # Are we enforcing a new security level
+ entry = gtk.CheckButton(_("Enforce a new security level"))
+
+ # security levels
+ frame = gtk.Frame()
+ frame.set_sensitive(False)
+ levels_vbox = gtk.VBox()
+ frame.add(levels_vbox)
+ # none
+ button = gtk.RadioButton(group=None, label=_("Pre-defined security level: NONE"))
+ button.connect('clicked', self.force_level, 'none')
+ levels_vbox.pack_start(button)
+ # default
+ button = gtk.RadioButton(group=button, label=_("Pre-defined security level: DEFAULT"))
+ button.connect('clicked', self.force_level, 'default')
+ button.set_active(True)
+ levels_vbox.pack_start(button)
+ # secure
+ button = gtk.RadioButton(group=button, label=_("Pre-defined security level: SECURE"))
+ button.connect('clicked', self.force_level, 'secure')
+ levels_vbox.pack_start(button)
+
+ # adding callback for enable button
+ entry.connect('clicked', self.enforce_level, frame)
+ vbox.pack_start(entry, False, False)
+ # putting levels to vbox
+ vbox.pack_start(frame)
+
+ return vbox
+
+ def enforce_level(self, widget, options):
+ """Enforces a new security level"""
+ frame = options
+ if widget.get_active():
+ # we are enforcing a level
+ self.enforcing_level = True
+ frame.set_sensitive(True)
+ # disable notebook pages
+ npages = self.notebook.get_n_pages()
+ for page in range(1, npages):
+ curpage = self.notebook.get_nth_page(page)
+ curpage.set_sensitive(False)
+ label = self.notebook.get_tab_label(curpage)
+ label.set_sensitive(False)
+ else:
+ frame.set_sensitive(False)
+ # enable notebook pages
+ npages = self.notebook.get_n_pages()
+ for page in range(1, npages):
+ curpage = self.notebook.get_nth_page(page)
+ curpage.set_sensitive(True)
+ label = self.notebook.get_tab_label(curpage)
+ label.set_sensitive(True)
+ # disable level enforcing
+ self.enforcing_level = False
+
+ def force_level(self, widget, level):
+ """Defines a given security level"""
+ if widget.get_active():
+ self.enforced_level = level
+ print level
+
+ def notifications_page(self):
+ """Builds the notifications page"""
+ vbox = gtk.VBox(homogeneous=False)
+
+ # security levels
+
+ entry = gtk.Label(NOTIFICATIONS_TEXT)
+ entry.set_use_markup(True)
+ vbox.pack_start(entry, False, False)
+
+ # basic security options
+ options_view = self.create_treeview(["TTY_WARN", "SYSLOG_WARN", "NOTIFY_WARN", "MAIL_WARN", "MAIL_USER", "MAIL_EMPTY_CONTENT"])
+ vbox.pack_start(options_view)
+
+ return vbox
+
+ def system_security_page(self):
+ """Builds the network security page"""
+ vbox = gtk.VBox(homogeneous=False)
+
+ entry = gtk.Label(SYSTEM_SECURITY_TEXT)
+ entry.set_use_markup(True)
+ vbox.pack_start(entry, False, False)
+
+ # system security options
+ options_view = self.create_treeview(["ENABLE_APPARMOR", "ENABLE_POLICYKIT",
+ "ENABLE_SUDO", "ENABLE_MSEC_CRON", "ENABLE_PAM_WHEEL_FOR_SU",
+ "ENABLE_SULOGIN", "CREATE_SERVER_LINK", "ENABLE_AT_CRONTAB",
+ "ALLOW_ROOT_LOGIN", "ALLOW_USER_LIST", "ENABLE_PASSWORD",
+ "ALLOW_AUTOLOGIN", "ENABLE_CONSOLE_LOG",
+ "ENABLE_PAM_WHEEL_FOR_SU", "CREATE_SERVER_LINK", "ALLOW_XAUTH_FROM_ROOT",
+ "ALLOW_REBOOT", "SHELL_HISTORY_SIZE", "SHELL_TIMEOUT", "PASSWORD_LENGTH",
+ "PASSWORD_HISTORY", "USER_UMASK", "ROOT_UMASK",
+ ])
+ vbox.pack_start(options_view)
+
+ return vbox
+
+ def network_security_page(self):
+ """Builds the network security page"""
+ vbox = gtk.VBox(homogeneous=False)
+
+ entry = gtk.Label(NETWORK_SECURITY_TEXT)
+ entry.set_use_markup(True)
+ vbox.pack_start(entry, False, False)
+
+ # network security options
+ options_view = self.create_treeview(["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",
+ ])
+ vbox.pack_start(options_view)
+
+ return vbox
+
+ def periodic_security_page(self):
+ """Builds the network security page"""
+ vbox = gtk.VBox(homogeneous=False)
+
+ entry = gtk.Label(PERIODIC_SECURITY_TEXT)
+ vbox.pack_start(entry, False, False)
+
+ self.periodic_checks = gtk.CheckButton(_("Enable periodic security checks"))
+ if self.config.get("CHECK_SECURITY") == "yes":
+ self.periodic_checks.set_active(True)
+ vbox.pack_start(self.periodic_checks, False, False)
+
+ # network security options
+ options_view = self.create_treeview(["CHECK_PERMS", "CHECK_USER_FILES", "CHECK_SUID_ROOT", "CHECK_SUID_MD5",
+ "CHECK_SGID", "CHECK_WRITABLE", "CHECK_UNOWNED",
+ "CHECK_PROMISC", "CHECK_OPEN_PORT", "CHECK_PASSWD",
+ "CHECK_SHADOW", "CHECK_CHKROOTKIT", "CHECK_RPM",
+ "CHECK_SHOSTS"
+ ])
+ vbox.pack_start(options_view)
+
+ # see if these tests are enabled
+ self.periodic_checks.connect('clicked', self.periodic_tests, options_view)
+ periodic_checks = self.config.get("CHECK_SECURITY")
+ if periodic_checks == 'no':
+ # disable all periodic tests
+ options_view.set_sensitive(False)
+
+ return vbox
+
+ def periodic_tests(self, widget, options):
+ '''Enables/disables periodic security tests.'''
+ status = widget.get_active()
+ if status:
+ self.config.set("CHECK_SECURITY", "yes")
+ options.set_sensitive(True)
+ else:
+ self.config.set("CHECK_SECURITY", "no")
+ options.set_sensitive(False)
+
+ def permissions_security_page(self):
+ """Builds the network security page"""
+ vbox = gtk.VBox(homogeneous=False)
+
+ entry = gtk.Label(PERMISSIONS_SECURITY_TEXT)
+ vbox.pack_start(entry, False, False)
+
+ sw = gtk.ScrolledWindow()
+ sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+ sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+
+ # list of options
+ lstore = gtk.ListStore(
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_BOOLEAN)
+
+ # treeview
+ treeview = gtk.TreeView(lstore)
+ treeview.set_rules_hint(True)
+ treeview.set_search_column(self.COLUMN_DESCR)
+
+ # TODO: fix
+ treeview.connect('row-activated', self.option_changed, lstore)
+
+ # configuring columns
+
+ # column for path mask
+ column = gtk.TreeViewColumn(_('Path'), gtk.CellRendererText(), text=self.COLUMN_PATH)
+ column.set_sort_column_id(self.COLUMN_PATH)
+ treeview.append_column(column)
+
+ # column for user
+ column = gtk.TreeViewColumn(_('User'), gtk.CellRendererText(), text=self.COLUMN_USER)
+ column.set_sort_column_id(self.COLUMN_USER)
+ treeview.append_column(column)
+
+ # column for group
+ column = gtk.TreeViewColumn(_('Group'), gtk.CellRendererText(), text=self.COLUMN_GROUP)
+ column.set_sort_column_id(self.COLUMN_GROUP)
+ treeview.append_column(column)
+
+ # column for permissions
+ column = gtk.TreeViewColumn(_('Permissions'), gtk.CellRendererText(), text=self.COLUMN_PERM)
+ column.set_sort_column_id(self.COLUMN_VALUE)
+ treeview.append_column(column)
+
+ # column for force option
+ renderer = gtk.CellRendererToggle()
+ renderer.connect('toggled', self.toggle_enforced, lstore)
+ column = gtk.TreeViewColumn(_('Enforce'), renderer, active=self.COLUMN_FORCE)
+ column.set_sort_column_id(self.COLUMN_FORCE)
+ column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+ column.set_fixed_width(50)
+
+ treeview.append_column(column)
+
+ sw.add(treeview)
+
+ for file in self.perms.list_options():
+ user_s, group_s, perm_s, force = self.perms.get(file)
+
+ # convert to boolean
+ if force:
+ force = True
+ else:
+ force = False
+
+ # building the option
+ iter = lstore.append()
+ lstore.set(iter,
+ self.COLUMN_PATH, file,
+ self.COLUMN_USER, user_s,
+ self.COLUMN_GROUP, group_s,
+ self.COLUMN_PERM, perm_s,
+ self.COLUMN_FORCE, force,
+ )
+ vbox.pack_start(sw)
+ return vbox
+
+ def toggle_enforced(self, cell, path, model):
+ '''Toggles a forced permission on an item'''
+ iter = model.get_iter((int(path),))
+ fixed = model.get_value(iter, self.COLUMN_FORCE)
+
+ # do something with the value
+ fixed = not fixed
+
+ # set new value
+ model.set(iter, self.COLUMN_FORCE, fixed)
+
+ def option_changed(self, treeview, path, col, model):
+ """Processes an option change"""
+ print path
+ iter = model.get_iter(path)
+ param = model.get_value(iter, self.COLUMN_OPTION)
+ descr = model.get_value(iter, self.COLUMN_DESCR)
+ value = model.get_value(iter, self.COLUMN_VALUE)
+
+ callback, params = config.SETTINGS[param]
+
+ # asks for new parameter value
+ dialog = gtk.Dialog(_("Select new value for %s") % (param),
+ self.window, 0,
+ (gtk.STOCK_OK, gtk.RESPONSE_OK,
+ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
+ label = gtk.Label(_("Modifying <b>%s</b>.\n<i>%s</i>\nCurrent value: <b>%s</b>") % (param, descr, value))
+ label.set_line_wrap(True)
+ label.set_use_markup(True)
+ dialog.vbox.pack_start(label)
+ if '*' in params:
+ # string parameter
+ entry = gtk.Entry()
+ entry.set_text(value)
+ dialog.vbox.pack_start(entry)
+ else:
+ # combobox parameter
+ entry = gtk.combo_box_new_text()
+ for item in params:
+ entry.append_text(item)
+ if value not in params:
+ entry.append_text(value)
+ params.append(value)
+ active = params.index(value)
+ entry.set_active(active)
+ dialog.vbox.pack_start(entry)
+
+ dialog.show_all()
+ response = dialog.run()
+ if response != gtk.RESPONSE_OK:
+ dialog.destroy()
+ return
+
+ # process new parameter
+ if '*' in params:
+ newval = entry.get_text()
+ else:
+ newval = entry.get_active_text()
+ dialog.destroy()
+
+ # update options
+ self.config.set(param, newval)
+
+ model.set(iter, self.COLUMN_VALUE, newval)
+
+
+ def quit(self, param):
+ """Quits the application"""
+ print "Leaving.."
+ gtk.main_quit()
+
+
+# {{{ usage
+def usage():
+ """Prints help message"""
+ print """Msec: Mandriva Security Center (%s).
+
+Arguments to msecgui:
+ -h, --help displays this helpful message.
+ -d enable debugging messages.
+""" % version
+# }}}
+
+if __name__ == "__main__":
+ log_level = logging.INFO
+
+ # parse command line
+ try:
+ opt, args = getopt.getopt(sys.argv[1:], 'hd', ['help', 'debug'])
+ except getopt.error:
+ usage()
+ sys.exit(1)
+ for o in opt:
+ # help
+ if o[0] == '-h' or o[0] == '--help':
+ usage()
+ sys.exit(0)
+ # list
+ elif o[0] == '-d' or o[0] == '--debug':
+ log_level = logging.DEBUG
+
+ # configuring logging
+ log = Log(interactive=True, log_syslog=False, log_file=True, log_level=log_level, log_path=config.SECURITYLOG)
+
+ # loading initial config
+ msec_config = config.MsecConfig(log, config=config.SECURITYCONF)
+ if not msec_config.load():
+ log.info(_("Unable to load config, using default values"))
+
+ # loading permissions config
+ perm_conf = config.PermConfig(log, config=config.PERMCONF)
+ if not perm_conf.load():
+ log.info(_("Unable to load permissions, using default values"))
+
+ # creating an msec instance
+ msec = MSEC(log)
+
+ log.info("Starting gui..")
+
+ gui = MsecGui(log, msec, msec_config, perm_conf)
+ gtk.main()
+
diff --git a/src/msec/msecperms b/src/msec/msecperms
new file mode 100755
index 0000000..3ce0c50
--- /dev/null
+++ b/src/msec/msecperms
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Wrapper for msecperms.py
+#
+
+if [ "`whoami`" != "root" ]; then
+ echo 'msec: sorry, you must be root !'
+ exit 1
+fi
+
+LCK=/var/run/msec.pid
+
+function cleanup() {
+ rm -f $LCK
+}
+
+if [ -f $LCK ]; then
+ if [ -d /proc/`cat $LCK` ]; then
+ exit 0
+ else
+ rm -f $LCK
+ fi
+fi
+
+echo -n $$ > $LCK
+
+trap cleanup 0
+
+MSEC=/usr/share/msec/msecperms.py
+OPT="$@"
+
+$MSEC $OPT
+
+# msec ends here
diff --git a/src/msec/msecperms.py b/src/msec/msecperms.py
new file mode 100755
index 0000000..a0f1676
--- /dev/null
+++ b/src/msec/msecperms.py
@@ -0,0 +1,152 @@
+#!/usr/bin/python -O
+"""This file is responsible for permissions checking and
+(optionally) enforcing.
+"""
+
+import glob
+import re
+import string
+import os
+import stat
+import pwd
+import grp
+import sys
+import logging
+import getopt
+
+# localization
+import gettext
+
+try:
+ cat = gettext.Catalog('msec')
+ _ = cat.gettext
+except IOError:
+ _ = str
+
+# config
+import config
+
+# version
+try:
+ from version import version
+except:
+ version = "development version"
+
+# libmsec
+from libmsec import Log, PERMS
+
+# {{{ usage
+def usage():
+ """Prints help message"""
+ print """Msec: Mandriva Security Center (%s).
+
+This applications verifies and (when required) enforces permissions
+of certain files and directories.
+
+The list of permissions is stored in %s.
+
+Available parameters:
+ -h, --help displays this helpful message.
+ -l, --level <level> displays configuration for specified security
+ level.
+ -f, --force <level> force new level, overwriting user settings.
+ -e, --enforce <level> enforce permissions on all files.
+ -d enable debugging messages.
+ -p, --pretend only pretend to change the level, perform no real
+ actions. Use this to see what operations msec
+ will perform.
+""" % (version, config.PERMCONF)
+# }}}
+
+if __name__ == "__main__":
+ # default options
+ log_level = logging.INFO
+ force_level = False
+ level = config.DEFAULT_LEVEL
+ commit = True
+ enforce = False
+
+ # parse command line
+ try:
+ opt, args = getopt.getopt(sys.argv[1:], 'hel:f:dp', ['help', 'enforce', 'list', 'force', 'debug', 'pretend'])
+ except getopt.error:
+ usage()
+ sys.exit(1)
+ for o in opt:
+ # help
+ if o[0] == '-h' or o[0] == '--help':
+ usage()
+ sys.exit(0)
+ # list
+ elif o[0] == '-l' or o[0] == '--list':
+ level = o[1]
+ log = Log(interactive=True, log_syslog=False, log_file=False)
+ permconf = config.load_default_perms(log, level)
+ params = permconf.list_options()
+ if not params:
+ print >>sys.stderr, _("Invalid security level '%s'.") % level
+ sys.exit(1)
+ for file in params:
+ user, group, perm, force = permconf.get(file)
+ if force:
+ print "!! forcing permissions on %s" % file
+ print "%s: %s.%s perm %s" % (file, user, group, perm)
+ sys.exit(0)
+ # force new level
+ elif o[0] == '-f' or o[0] == '--force':
+ level = o[1]
+ force_level = True
+ # debugging
+ elif o[0] == '-d' or o[0] == '--debug':
+ log_level = logging.DEBUG
+ # permission enforcing
+ elif o[0] == '-e' or o[0] == '--enforce':
+ enforce = True
+ # check-only mode
+ elif o[0] == '-p' or o[0] == '--pretend':
+ commit = False
+
+ # verifying use id
+ if os.geteuid() != 0:
+ print >>sys.stderr, _("Msec: Mandriva Security Center (%s)\n") % version
+ print >>sys.stderr, _("Error: This application must be executed by root!")
+ print >>sys.stderr, _("Run with --help to get help.")
+ sys.exit(1)
+
+ # configuring logging
+ interactive = sys.stdin.isatty()
+ if interactive:
+ # logs to file and to terminal
+ log = Log(log_path=config.SECURITYLOG, interactive=True, log_syslog=False, log_level=log_level)
+ else:
+ log = Log(log_path=config.SECURITYLOG, interactive=False, log_level=log_level)
+
+ # loading permissions
+ permconf = config.PermConfig(log, config=config.PERMCONF)
+ if not permconf.load() and not force_level:
+ log.error(_("Permissions configuration not found, please run '%s -f <level>' to initialize.") % sys.argv[0])
+
+ # forcing new level
+ if force_level:
+ # first load the default configuration for level
+ default_permconf = config.load_default_perms(log, level)
+ params = default_permconf.list_options()
+ if not params:
+ log.error(_("Default configuration for level '%s' not found, aborting.") % level)
+ sys.exit(1)
+ for opt in params:
+ permconf.set(opt, default_permconf.get(opt))
+
+ # load the main permission class
+ perm = PERMS(log)
+
+ # check permissions
+ changed_files = perm.check_perms(permconf)
+
+ # writing back changes
+ perm.commit(really_commit=commit, enforce=force_level)
+ # saving updated config
+ if force_level and commit:
+ if not permconf.save():
+ log.error(_("Unable to save config!"))
+ sys.exit(0)
diff --git a/src/msec/version.py b/src/msec/version.py
new file mode 100644
index 0000000..3e3074d
--- /dev/null
+++ b/src/msec/version.py
@@ -0,0 +1 @@
+version='0.60.1'
diff --git a/src/msec_find/Makefile b/src/msec_find/Makefile
index 3aec03f..e85221f 100644
--- a/src/msec_find/Makefile
+++ b/src/msec_find/Makefile
@@ -2,7 +2,7 @@ CC=gcc
NAME=msec_find
CFLAGS = -ggdb -Wall -Wmissing-prototypes -Wmissing-declarations \
--Wpointer-arith -m486 -O2 -finline-functions -fkeep-inline-functions \
+-Wpointer-arith -O2 -finline-functions -fkeep-inline-functions \
-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
OBJ=find.o
diff --git a/src/promisc_check/Makefile b/src/promisc_check/Makefile
index 8c87e07..776dde2 100644
--- a/src/promisc_check/Makefile
+++ b/src/promisc_check/Makefile
@@ -2,7 +2,7 @@ CC=gcc
NAME=promisc_check
CFLAGS = -ggdb -Wall -Wmissing-prototypes -Wmissing-declarations \
--Wpointer-arith -m486 -O2 -finline-functions -fkeep-inline-functions
+-Wpointer-arith -O2 -finline-functions -fkeep-inline-functions
OBJ=promisc_check.o