diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | src/msec/config.py | 16 | ||||
-rwxr-xr-x | src/msec/libmsec.py | 278 | ||||
-rw-r--r-- | src/msec/plugins/pam.py | 215 |
4 files changed, 274 insertions, 237 deletions
@@ -40,7 +40,7 @@ install: all install -m755 src/msec/$${i}o $(RPM_BUILD_ROOT)/usr/share/msec ; \ done # install plugins - for i in apparmor.py policykit.py; do \ + for i in pam.py apparmor.py policykit.py; do \ install -m755 src/msec/plugins/$$i $(RPM_BUILD_ROOT)/usr/share/msec/plugins ; \ install -m755 src/msec/plugins/$${i}o $(RPM_BUILD_ROOT)/usr/share/msec/plugins ; \ done diff --git a/src/msec/config.py b/src/msec/config.py index 7d7fd12..c79910e 100644 --- a/src/msec/config.py +++ b/src/msec/config.py @@ -98,15 +98,8 @@ SETTINGS = {'BASE_LEVEL': ("libmsec.base_level", 'ENABLE_IP_SPOOFING_PROTECTION': ("libmsec.enable_dns_spoofing_protection", ['yes', 'no']), 'ENABLE_LOG_STRANGE_PACKETS': ("libmsec.enable_log_strange_packets", ['yes', 'no']), 'ENABLE_MSEC_CRON': ("libmsec.enable_msec_cron", ['yes', 'no']), - 'ENABLE_PAM_ROOT_FROM_WHEEL': ("libmsec.enable_pam_root_from_wheel", ['yes', 'no']), 'ENABLE_SUDO': ("libmsec.enable_sudo", ['yes', 'no', 'wheel']), - 'ENABLE_PAM_WHEEL_FOR_SU': ("libmsec.enable_pam_wheel_for_su", ['yes', 'no']), 'ENABLE_SULOGIN': ("libmsec.enable_sulogin", ['yes', 'no']), - # password stuff - 'ENABLE_PASSWORD': ("libmsec.enable_password", ['yes', 'no']), - 'PASSWORD_HISTORY': ("libmsec.password_history", ['*']), - # format: min length, num upper, num digits - 'PASSWORD_LENGTH': ("libmsec.password_length", ['*']), 'SHELL_HISTORY_SIZE': ("libmsec.set_shell_history_size", ['*']), 'SHELL_TIMEOUT': ("libmsec.set_shell_timeout", ['*']), 'ENABLE_STARTUP_MSEC': ("libmsec.enable_startup_msec", ['yes', 'no']), @@ -118,11 +111,10 @@ OPTION_DISABLED=_("System default") # settings organizes by category # system security settings SETTINGS_SYSTEM = ["ENABLE_STARTUP_MSEC", "ENABLE_STARTUP_PERMS", "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", + "ENABLE_SULOGIN", "CREATE_SERVER_LINK", "ENABLE_AT_CRONTAB", + "ALLOW_ROOT_LOGIN", "ALLOW_USER_LIST", "ALLOW_AUTOLOGIN", + "ENABLE_CONSOLE_LOG", "CREATE_SERVER_LINK", "ALLOW_XAUTH_FROM_ROOT", + "ALLOW_REBOOT", "SHELL_HISTORY_SIZE", "SHELL_TIMEOUT", "USER_UMASK", "ROOT_UMASK", ] # network security settings SETTINGS_NETWORK = ["ACCEPT_BOGUS_ERROR_RESPONSES", "ACCEPT_BROADCASTED_ICMP_ECHO", "ACCEPT_ICMP_ECHO", diff --git a/src/msec/libmsec.py b/src/msec/libmsec.py index 551ba12..6c1d8b9 100755 --- a/src/msec/libmsec.py +++ b/src/msec/libmsec.py @@ -93,20 +93,16 @@ 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' @@ -121,6 +117,7 @@ AFTER=2 # regexps space = re.compile('\s') # X server +SECURETTY = '/etc/securetty' STARTX_REGEXP = '(\s*serverargs=".*) -nolisten tcp(.*")' XSERVERS_REGEXP = '(\s*[^#]+/usr/bin/X .*) -nolisten tcp(.*)' GDMCONF_REGEXP = '(\s*command=.*/X.*?) -nolisten tcp(.*)$' @@ -131,22 +128,12 @@ CTRALTDEL_REGEXP = '^ca::ctrlaltdel:/sbin/shutdown.*' 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' @@ -1088,59 +1075,6 @@ class MSEC: self.log.info(_("Forbidding list of users in GDM")) gdmconf.set_shell_variable('Browser', 'false') - def allow_root_login(self, arg): - ''' Allow direct root login on terminal.''' - securetty = self.configfiles.get_config_file(SECURETTY) - kde = self.configfiles.get_config_file(KDE) - gdm = self.configfiles.get_config_file(GDM) - gdmconf = self.configfiles.get_config_file(GDMCONF) - xdm = self.configfiles.get_config_file(XDM) - - val = {} - val_kde = kde.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') - val_gdm = gdm.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') - val_xdm = xdm.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') - num = 0 - for n in range(1, 7): - s = 'tty' + str(n) - if securetty.get_match(s): - num = num + 1 - s = 'vc/' + str(n) - if securetty.get_match(s): - num = num + 1 - - if arg == "yes": - if val_kde or val_gdm or val_xdm or num != 12: - self.log.info(_('Allowing direct root login')) - if gdmconf.exists(): - gdmconf.set_shell_variable('ConfigAvailable', 'true', '\[greeter\]', '^\s*$') - - for cnf in [kde, gdm, xdm]: - if cnf.exists(): - cnf.remove_line_matching('^auth\s*required\s*(?:/lib/security/)?pam_listfile.so.*bastille-no-login', 1) - - for n in range(1, 7): - s = 'tty' + str(n) - securetty.replace_line_matching(s, s, 1) - s = 'vc/' + str(n) - securetty.replace_line_matching(s, s, 1) - else: - if gdmconf.exists(): - gdmconf.set_shell_variable('ConfigAvailable', 'false', '\[greeter\]', '^\s*$') - if (kde.exists() and not val_kde) or (gdm.exists() and not val_gdm) or (xdm.exists() and not val_xdm) or num > 0: - self.log.info(_('Forbidding direct root login')) - - bastillenologin = self.configfiles.get_config_file(BASTILLENOLOGIN) - bastillenologin.replace_line_matching('^\s*root', 'root', 1) - - # TODO: simplify this - for cnf in [kde, gdm, xdm]: - if cnf.exists(): - (cnf.replace_line_matching('^auth\s*required\s*(?:/lib/security/)?pam_listfile.so.*bastille-no-login', - 'auth required pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') or - cnf.insert_at(0, 'auth required pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login')) - securetty.remove_line_matching('.+', 1) - def allow_remote_root_login(self, arg): ''' Allow remote root login via sshd. If yes, login is allowed. If without-password, only public-key authentication logins are allowed. See sshd_config(5) man page for more information.''' sshd_config = self.configfiles.get_config_file(SSHDCONFIG) @@ -1164,62 +1098,6 @@ class MSEC: 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): - ''' Allow only users in wheel grup to su to root.''' - 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_before('^auth\s+include', '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: - 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 autologin.''' autologin = self.configfiles.get_config_file(AUTOLOGIN) @@ -1368,107 +1246,6 @@ class MSEC: ''' Enable logging of strange network packets.''' self.set_zero_one_variable(SYSCTLCONF, 'net.ipv4.conf.all.log_martians', arg, 'Enabling logging of strange packets', 'Disabling logging of strange packets') - def password_length(self, arg): - ''' Set the password minimum length and minimum number of digit and minimum number of capitalized letters, using length,ndigits,nupper format.''' - - 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 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): ''' Ask for root password when going to single user level (man sulogin(8)).''' inittab = self.configfiles.get_config_file(INITTAB) @@ -1550,6 +1327,59 @@ class MSEC: self.log.info(_('Disabling periodic promiscuity check')) cron.remove_line_matching('[^#]+/usr/share/msec/promisc_check.sh') + def allow_root_login(self, arg): + ''' Allow direct root login on terminal.''' + securetty = self.configfiles.get_config_file(SECURETTY) + kde = self.configfiles.get_config_file(KDE) + gdm = self.configfiles.get_config_file(GDM) + gdmconf = self.configfiles.get_config_file(GDMCONF) + xdm = self.configfiles.get_config_file(XDM) + + val = {} + val_kde = kde.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') + val_gdm = gdm.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') + val_xdm = xdm.get_match('auth required (?:/lib/security/)?pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') + num = 0 + for n in range(1, 7): + s = 'tty' + str(n) + if securetty.get_match(s): + num = num + 1 + s = 'vc/' + str(n) + if securetty.get_match(s): + num = num + 1 + + if arg == "yes": + if val_kde or val_gdm or val_xdm or num != 12: + self.log.info(_('Allowing direct root login')) + if gdmconf.exists(): + gdmconf.set_shell_variable('ConfigAvailable', 'true', '\[greeter\]', '^\s*$') + + for cnf in [kde, gdm, xdm]: + if cnf.exists(): + cnf.remove_line_matching('^auth\s*required\s*(?:/lib/security/)?pam_listfile.so.*bastille-no-login', 1) + + for n in range(1, 7): + s = 'tty' + str(n) + securetty.replace_line_matching(s, s, 1) + s = 'vc/' + str(n) + securetty.replace_line_matching(s, s, 1) + else: + if gdmconf.exists(): + gdmconf.set_shell_variable('ConfigAvailable', 'false', '\[greeter\]', '^\s*$') + if (kde.exists() and not val_kde) or (gdm.exists() and not val_gdm) or (xdm.exists() and not val_xdm) or num > 0: + self.log.info(_('Forbidding direct root login')) + + bastillenologin = self.configfiles.get_config_file(BASTILLENOLOGIN) + bastillenologin.replace_line_matching('^\s*root', 'root', 1) + + # TODO: simplify this + for cnf in [kde, gdm, xdm]: + if cnf.exists(): + (cnf.replace_line_matching('^auth\s*required\s*(?:/lib/security/)?pam_listfile.so.*bastille-no-login', + 'auth required pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login') or + cnf.insert_at(0, 'auth required pam_listfile.so onerr=succeed item=user sense=deny file=/etc/bastille-no-login')) + securetty.remove_line_matching('.+', 1) + # The following checks are run from crontab. We only have these functions here # to get their descriptions. diff --git a/src/msec/plugins/pam.py b/src/msec/plugins/pam.py new file mode 100644 index 0000000..ab1cd27 --- /dev/null +++ b/src/msec/plugins/pam.py @@ -0,0 +1,215 @@ +#!/usr/bin/python +"""PAM plugin for msec """ + +# main plugin class name +PLUGIN = "pam" + +import os +import re +import gettext + +# configuration +import config + +# localization +try: + cat = gettext.Catalog('msec') + _ = cat.gettext +except IOError: + _ = str + +class pam: + # configuration variables + SIMPLE_ROOT_AUTHEN = '/etc/pam.d/simple_root_authen' + SU = '/etc/pam.d/su' + SYSTEM_AUTH = '/etc/pam.d/system-auth' + # 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' + # 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.*)') + + def __init__(self, log=None, configfiles=None, root=None): + # initializing plugin + self.log = log + self.configfiles = configfiles + self.root = root + + # configuring entry in global settings + config.SETTINGS['ENABLE_PAM_WHEEL_FOR_SU'] = ("pam.enable_pam_wheel_for_su", ['yes', 'no']) + config.SETTINGS['ENABLE_PAM_ROOT_FROM_WHEEL'] = ("pam.enable_pam_root_from_wheel", ['yes', 'no']) + # password stuff + config.SETTINGS['ENABLE_PASSWORD'] = ("pam.enable_password", ['yes', 'no']) + config.SETTINGS['PASSWORD_HISTORY'] = ("pam.password_history", ['*']) + # format: min length, num upper, num digits + config.SETTINGS['PASSWORD_LENGTH'] = ("pam.password_length", ['*']) + + # insert entry into system security settings + config.SETTINGS_SYSTEM.append('ENABLE_PAM_WHEEL_FOR_SU') + config.SETTINGS_SYSTEM.append('ENABLE_PAM_ROOT_FROM_WHEEL') + config.SETTINGS_SYSTEM.append('ENABLE_PASSWORD') + config.SETTINGS_SYSTEM.append('PASSWORD_HISTORY') + config.SETTINGS_SYSTEM.append('PASSWORD_LENGTH') + + def enable_password(self, arg): + ''' Use password to authenticate users. Take EXTREMELY care when disabling passwords, as it will leave the machine vulnerable.''' + system_auth = self.configfiles.get_config_file(self.SYSTEM_AUTH) + + val = system_auth.get_match(self.PASSWORD_REGEXP) + + if arg == "yes": + if val: + self.log.info(_('Using password to authenticate users')) + system_auth.remove_line_matching(self.PASSWORD_REGEXP) + else: + if not val: + self.log.info(_('Don\'t use password to authenticate users')) + system_auth.replace_line_matching(self.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(self.SYSTEM_AUTH) + + pam_tcb = system_auth.get_match(self.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(self.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(self.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(self.OPASSWD) + opasswd.exists() or opasswd.touch() + else: + self.log.info(_('Disabling password history')) + system_auth.replace_line_matching(self.UNIX_REGEXP, '@1@3') + + def password_length(self, arg): + ''' Set the password minimum length and minimum number of digit and minimum number of capitalized letters, using length,ndigits,nupper format.''' + + 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(self.SYSTEM_AUTH) + + val_length = val_ndigits = val_ucredit = 999999 + + if passwd.exists(): + val_length = passwd.get_match(self.LENGTH_REGEXP, '@2') + if val_length: + val_length = int(val_length) + + val_ndigits = passwd.get_match(self.NDIGITS_REGEXP, '@2') + if val_ndigits: + val_ndigits = int(val_ndigits) + + val_ucredit = passwd.get_match(self.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(self.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(self.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(self.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_pam_wheel_for_su(self, arg): + ''' Allow only users in wheel grup to su to root.''' + su = self.configfiles.get_config_file(self.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_before('^auth\s+include', '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(self.SU) + simple = self.configfiles.get_config_file(self.SIMPLE_ROOT_AUTHEN) + + if not su.exists(): + return + + val = su.get_match(self.SUCCEED_MATCH) + + val_simple = simple.get_match(self.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: + su.insert_before('^auth\s+sufficient', self.SUCCEED_LINE) + if simple.exists() and not val_simple: + simple.insert_before('^auth\s+sufficient', self.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(self.SUCCEED_MATCH) + if simple.exists() and val_simple: + simple.remove_line_matching(self.SUCCEED_MATCH) + |