diff options
Diffstat (limited to 'sss-auth-setup.py')
-rwxr-xr-x | sss-auth-setup.py | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/sss-auth-setup.py b/sss-auth-setup.py new file mode 100755 index 000000000000..efc6eadcd624 --- /dev/null +++ b/sss-auth-setup.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 + +# Written by: Xiao-Long Chen <chenxiaolong@cxl.epac.to> +# License: GPLv3 + +import base64 +import hashlib +import os +import re +import shutil + +nss_databases = ['passwd', 'group', 'services', 'netgroup', 'automount'] + +PAM_CONFIG_DIR = '/etc/pam.d/' + +def nss_enable_sss(): + if os.path.exists("/etc/nsswitch.conf.sss_tmp"): + os.remove("/etc/nsswitch.conf.sss_tmp") + + # Backup /etc/nsswitch.conf + shutil.copyfile("/etc/nsswitch.conf", "/etc/nsswitch.conf.sss_bak") + + nsswitch_orig = open("/etc/nsswitch.conf", 'r') + nsswitch_new = open("/etc/nsswitch.conf.sss_tmp", 'w') + + while True: + current_line = nsswitch_orig.readline() + if not current_line: + break + + if current_line != '\n' and current_line.split()[0][:-1] in nss_databases: + if "sss" in current_line: + print("sss is already enabled for the NSS " + + current_line.split()[0][:-1] + " database") + else: + print("Enabling sss support for the NSS " + + current_line.split()[0][:-1] + " database...") + if current_line[-1] == '\n': + current_line = current_line[:-1] + " sss\n" + else: + current_line += " sss" + + # Write new file + nsswitch_new.write(current_line) + + nsswitch_orig.close() + nsswitch_new.close() + + # Replace original /etc/nsswitch.conf + shutil.move("/etc/nsswitch.conf.sss_tmp", "/etc/nsswitch.conf") + +def nss_disable_sss(): + if os.path.exists("/etc/nsswitch.conf.sss_tmp"): + os.remove("/etc/nsswitch.conf.sss_tmp") + + nsswitch_orig = open("/etc/nsswitch.conf", 'r') + nsswitch_new = open("/etc/nsswitch.conf.sss_tmp", 'w') + + while True: + current_line = nsswitch_orig.readline() + if not current_line: + break + + if current_line != '\n' and current_line.split()[0][:-1] in nss_databases: + if "sss" in current_line: + print("Disabling sss for the NSS " + + current_line.split()[0][:-1] + " database...") + current_line = re.sub(r"[ \t]+sss[ \t]*", ' ', current_line) + # Remove extra spaces + current_line = re.sub(r"[ \t]+\n", '\n', current_line) + + # Write new file + nsswitch_new.write(current_line) + + nsswitch_orig.close() + nsswitch_new.close() + + # Replace original /etc/nsswitch.conf + shutil.move("/etc/nsswitch.conf.sss_tmp", "/etc/nsswitch.conf") + +def pam_check_header(pam_config): + pam_file = open(PAM_CONFIG_DIR + pam_config, 'r') + + inside_header = False + has_header = False + sha512sum = '' + base64enc = '' + returned = None + + while True: + current_line = pam_file.readline() + if not current_line: + break + + if current_line == '\n' or current_line == '# \n': + continue + + if current_line == '# -----BEGIN PAM BACKUP-----\n': + inside_header = True + + elif current_line == '# -----END PAM BACKUP-----\n': + if not inside_header: + # Invalid because the begin line is missing + returned = ('INVALID', None, None) + break + + has_header = True + break + + elif inside_header: + if current_line.startswith('# Hash: '): + sha512sum = current_line[8:-1] + elif current_line.startswith('# Data: '): + base64enc = current_line[8:-1] + else: + # Invalid because unknown data is in the header + returned = ('INVALID', None, None) + break + + pam_file.close() + + if has_header: + if sha512sum == hashlib.sha512(base64.b64decode(base64enc)).hexdigest(): + returned = ('VALID', sha512sum, base64enc) + else: + # Invalid because the checksum of the data does not match the hash + returned = ('INVALID', None, None) + + if not returned: + returned = ('NONE', None, None) + + return returned + +def pam_config_setup(pam_config): + pam_file_orig = open(PAM_CONFIG_DIR + pam_config, 'r') + pam_file_new = open(PAM_CONFIG_DIR + pam_config + '.sss_tmp', 'a') + + while True: + current_line = pam_file_orig.readline() + if not current_line: + break + + if current_line.startswith('#%PAM-1.0'): + continue + + if current_line != '\n' and current_line[0] != '#': + current_line_split = current_line.split() + + # Change 'required' to 'sufficient' for the pam_unix.so module + if current_line_split[2] == "pam_unix.so" and current_line_split[1] == "required": + #pam_file_new.write(current_line.replace("required", "sufficient")) + pam_file_new.write(current_line_split[0] + "\t\tinclude\t\tsss\n") + continue + + pam_file_new.write(current_line) + + pam_file_orig.close() + pam_file_new.close() + +def pam_enable_sss(): + print('Enabling sssd support in:') + + rows, columns = os.popen('stty size', 'r').read().split() + columns = int(columns) - 3 + + for fullpath, directories, files in os.walk(PAM_CONFIG_DIR): + files.sort() + for pam_config in files: + if pam_config == 'sss' or pam_config == 'sss.bak' or \ + pam_config.startswith('.') or pam_config.endswith('~'): + continue + + status = pam_check_header(pam_config)[0] + if status == 'NONE': + status_msg = 'done' + elif status == 'VALID': + status_msg = 'already enabled (skipping)' + elif status == 'INVALID': + status_msg = 'invalid backup header (skipping)' + + pam_config_path = PAM_CONFIG_DIR + pam_config + + if status == 'NONE': + pam_file = open(pam_config_path, 'rb') + + raw_content = pam_file.read() + sha512sum = hashlib.sha512(raw_content).hexdigest() + base64enc_raw = base64.b64encode(raw_content) + base64enc = base64enc_raw.decode('ascii') + + pam_file.close() + + tmp_file = open(pam_config_path + '.sss_tmp', 'w') + + tmp_file.write('#%PAM-1.0\n') + tmp_file.write('# -----BEGIN PAM BACKUP-----\n') + tmp_file.write('# Hash: ' + sha512sum + '\n') + tmp_file.write('# \n') + tmp_file.write('# Data: ' + base64enc + '\n') + tmp_file.write('# -----END PAM BACKUP-----\n') + tmp_file.write('\n') + + tmp_file.close() + + pam_config_setup(pam_config) + + shutil.move(pam_config_path + '.sss_tmp', pam_config_path) + + if len(pam_config_path + status_msg) > columns: + print(pam_config_path) + print(('{:>%is} ' % columns + 2).format(status_msg)) + else: + print((' {:<%is}{:>%is} ' % \ + (len(pam_config_path), columns - len(pam_config_path))). \ + format(pam_config_path, status_msg)) + + if os.path.exists(PAM_CONFIG_DIR + 'sss'): + print('%ssss already exists. Moving it to %ssss.bak' % \ + (PAM_CONFIG_DIR, PAM_CONFIG_DIR)) + shutil.move(PAM_CONFIG_DIR + 'sss', PAM_CONFIG_DIR + 'sss.bak') + + pam_sss = open(PAM_CONFIG_DIR + 'sss', 'w') + # Auth + pam_sss.write("auth sufficient pam_unix.so nullok try_first_pass\n") + pam_sss.write("auth sufficient pam_sss.so use_first_pass\n") + pam_sss.write("auth required pam_deny.so\n") + # Account + pam_sss.write("account required pam_unix.so\n") + pam_sss.write("#account [default=bad success=ok user_unknown=ignore] pam_sss.so\n") + pam_sss.write("account optional pam_sss.so\n") + # Password + pam_sss.write("password sufficient pam_unix.so try_first_pass nullok sha512 shadow\n") + pam_sss.write("password sufficient pam_sss.so use_authtok\n") + pam_sss.write("password required pam_deny.so\n") + # Session + pam_sss.write("session required pam_unix.so\n") + pam_sss.write("session optional pam_sss.so\n") + pam_sss.close() + +def pam_disable_sss(): + print('Disabling sssd support in:') + + rows, columns = os.popen('stty size', 'r').read().split() + columns = int(columns) - 3 + + for fullpath, directories, files in os.walk(PAM_CONFIG_DIR): + files.sort() + for pam_config in files: + if pam_config == 'sss' or pam_config == 'sss.bak' or \ + pam_config.startswith('.') or pam_config.endswith('~'): + continue + + status, sha512sum, base64enc = pam_check_header(pam_config) + if status == 'NONE': + status_msg = 'already disabled (skipping)' + elif status == 'VALID': + status_msg = 'done' + elif status == 'INVALID': + status_msg = 'invalid backup header (skipping)' + + pam_config_path = PAM_CONFIG_DIR + pam_config + + if status == 'VALID': + pam_file = open(pam_config_path + '.sss_tmp', 'wb') + pam_file.write(base64.b64decode(base64enc)) + pam_file.close() + shutil.move(pam_config_path + '.sss_tmp', pam_config_path) + + if len(pam_config_path + status_msg) > columns: + print(pam_config_path) + print(('{:>%is} ' % columns + 2).format(status_msg)) + else: + print((' {:<%is}{:>%is} ' % \ + (len(pam_config_path), columns - len(pam_config_path))). \ + format(pam_config_path, status_msg)) + + if os.path.exists(PAM_CONFIG_DIR + 'sss'): + os.remove(PAM_CONFIG_DIR + 'sss') + +def parse_arguments(): + import argparse + import textwrap + + arg_parser = argparse.ArgumentParser() + arg_parser.formatter_class = argparse.RawDescriptionHelpFormatter + arg_parser.description = textwrap.dedent(""" + Arch Linux sssd authentication setup helper for PAM and NSS + ----------------------------------------------------------- + """) + + nss_group = arg_parser.add_mutually_exclusive_group() + nss_group.add_argument("--enable-nss", + help="Enable support for SSSD in NSS", + action="store_true", + dest="nss_action", + default=None) + nss_group.add_argument("--disable-nss", + help="Disable support for SSSD in NSS", + action="store_false", + dest="nss_action", + default=None) + + pam_group = arg_parser.add_mutually_exclusive_group() + pam_group.add_argument("--enable-pam", + help="Enable support for SSSD in PAM", + action="store_true", + dest="pam_action", + default=None) + pam_group.add_argument("--disable-pam", + help="Disable support for SSSD in PAM", + action="store_false", + dest="pam_action", + default=None) + + args = arg_parser.parse_args() + + if args.nss_action == None and args.pam_action == None: + print("No action given!") + exit(1) + + if os.getuid() != 0: + print("sss-auth-setup must be run as root!") + exit(1) + + if args.nss_action != None: + if args.nss_action: + nss_enable_sss() + else: + nss_disable_sss() + + if args.pam_action != None: + if args.pam_action: + pam_enable_sss() + else: + pam_disable_sss() + +if __name__ == "__main__": + parse_arguments() |