#!/bin/sh

OU=""
win2003=""
use_winbind=""
use_gpo=""
ad_dnshostname=""
ad_netbioshostname=""
ad_long_hostname=""

TEMP=$(getopt -o 'd' --long 'windows2003,winbind,gpo,version,createcomputer:,netbiosname:' -- "$@")
if [ $? -ne 0 ]; then
    exit 1
fi
eval set -- "$TEMP"
unset TEMP

while [[ $# -gt 0 ]]; do
  case "$1" in
    '-d') set -x; shift;;
    '--createcomputer') shift; OU="$1"; shift;;
    '--windows2003')  win2003="true"; shift;;
    '--winbind')  use_winbind="true"; shift;;
    '--gpo')  use_gpo="true"; shift;;
    '--version') rpm -q alterator-auth --qf '%{version}\n'; exit 0;;
    '--netbiosname') shift; ad_netbioshostname="$1"; shift;;
    --) shift;;
    *) break;;
  esac
done

# Check for krb5 version for join to Windows 2003 (DES support is removed in release 1.18)
krb5_version="$(rpm -q --qf '%{version}' krb5-kinit)"
krb5_version_without_des="1.18"
krb5_version_check="$(rpmvercmp "$krb5_version" "$krb5_version_without_des")"
if [ "$krb5_version_check" != "-1" -a "$win2003" = "true" ]; then
    echo "Join to Windows 2003 is not supported by krb5 since 1.18 (current version is $krb5_version)"
    exit 1
fi

rdelim='[[:space:]]\+'
wdelim=' '

#pam configuration
pamldapfile="/etc/pam_ldap.conf"

#nss configuration
nssldapfile=
nssldapfile1="/etc/nss_ldap.conf"
nssldapfile2="/etc/nslcd.conf"
nsswitchfile="/etc/nsswitch.conf"

# openldap conf
ldap_conf="/etc/openldap/ldap.conf"

# krb conf
krb5_conf="/etc/krb5.conf"

# samba conf
smb_conf="/etc/samba/smb.conf"

# time sync command
net_cmd="/usr/bin/net"

# FreeIPA client command
ipa_cmd="/usr/sbin/ipa-client-install"

# sssd config
sssd_conf="/etc/sssd/sssd.conf"

# group mapping
user_groups="/etc/alterator/auth/user-groups"
admin_groups="/etc/alterator/auth/admin-groups"

#select between nss_ldap and nss-ldapd
[ -f "$nssldapfile1" ] && nssldapfile="$nssldapfile1"
[ -f "$nssldapfile2" ] && nssldapfile="$nssldapfile2"

# Use old LDAP auth scheme (nss_ldap or nss-ldapd) or new one (sssd)
ldap_auth_sssd="no"
rpm -q sssd &>/dev/null && ldap_auth_sssd="yes"

. alterator-datetime-functions
. shell-config
. shell-ini-config
. shell-quote
. shell-error

#turn off auto expansion
set -f

gpupdate_setup()
{
    local gpupdate_cmd="/usr/sbin/gpupdate-setup"
    rpm -q gpupdate &>/dev/null || return 0

    $gpupdate_cmd enable
    if [ "$1" = "local" -a -f /etc/gpupdate/gpupdate.ini ]; then
        $gpupdate_cmd set-backend local
    fi
}

# getting current auth
get_status()
{
    local status="$(/usr/sbin/control system-auth)"

    case "$status" in
        local)
            echo "local"
	    ;;
        ldap)
            echo -n "ldap " && pam_ldap_info
            ;;
        krb5*)
            echo -n "$status " && pam_ldap_info
            ;;
        winbind|sss)
            # test FreeIPA join
            if grep -q '^ipa_domain' "$sssd_conf" &>/dev/null; then
                echo -n "freeipa " && pam_ipa_info
            elif grep -q '^ldap_uri' "$sssd_conf" &>/dev/null; then
                echo -n "krb5 " && pam_ldap_info
            else
                echo -n "ad " && pam_ad_info
            fi
            ;;
        *)
            echo "unknown status"
            ;;
    esac
}

list()
{
    # always local
    echo "local"

    # checking pam_ldap and libnss_ldap libs
    [ -n "$(find /$(getconf SLIB)/security -maxdepth 1 -name 'pam_ldap.*')" ] &&
	[ -n "$(find /$(getconf SLIB) -maxdepth 1 -name 'libnss_ldap.so.*')" ] &&
    echo "ldap"

    # checking ldap and krb5 libs
    if [ "$ldap_auth_sssd" = "yes" ]; then
        rpm -q sssd-ldap sssd-krb5 &>/dev/null &&
        echo "krb5"
    else
        [ -n "$(find /$(getconf SLIB)/security -maxdepth 1 -name 'pam_ldap.*')" ] &&
        [ -n "$(find /$(getconf SLIB) -maxdepth 1 -name 'libnss_ldap.so.*')" ] &&
        [ -n "$(find /$(getconf SLIB) -maxdepth 1 -name 'libkrb5.so.*')" ] &&
        echo "krb5"
    fi

    # checking winbind or sss libs
    [ -n "$(find /$(getconf SLIB)/security -maxdepth 1 -regextype egrep -regex '.*/pam_(winbind|sss).so')" ] &&
	echo "ad"

    # checking freeipa-client executable
    [ -x "$ipa_cmd" ] &&
	echo "freeipa"
}

pam_ldap_info()
{
    local uri basedn

    if grep -q '^ldap_uri' "$sssd_conf" &>/dev/null; then
        uri="$(sed -n 's/^ldap_uri *= *//p' "$sssd_conf")"
        basedn="$(sed -n 's/^ldap_search_base *= *//p' "$sssd_conf")"
    elif [ -f "$pamldapfile" ];then
        uri="$(read_pam_ldap uri)"
        basedn="$(read_pam_ldap base)"
    else
        uri="$(read_nss_ldap uri)"
        basedn="$(read_nss_ldap base)"
    fi

    check_uri "$uri" && check_basedn "$basedn" && echo "$basedn $uri"
}

pam_ad_info()
{
    local domain="$(ini_config_get "$smb_conf" "global" "realm")"
    local hostname="$(ini_config_get "$smb_conf" "global" "netbios name")"
    local workgroup="$(ini_config_get "$smb_conf" "global" "workgroup")"
    echo "$domain $hostname $workgroup"
}

pam_ipa_info()
{
    local domain="$(sed -n 's/^ipa_domain *= *//p' "$sssd_conf")"
    local hostname="$(sed -n 's/^ipa_hostname *= *//p' "$sssd_conf")"
    hostname="${hostname//.$domain}"
    echo "$domain $hostname"
}

check_uri()
{
    local uri="$1"

    [ -z "$uri" ] && message "$0 (check_uri) uri not set" && return 1

    [ -z "$(echo "$uri"| egrep "^ldap[s|i]?:\/\/[^\/]+/?$")" ] && message "$0 (check_uri) invalid uri format" && return 1

    :
}

check_basedn()
{
    local basedn="$1"

    [ -z "$basedn" ] && message "$0 (check_basedn) basedn not set" && return 1

    [ -z "$(echo "$basedn"| egrep "^dc=[^,]+(,dc=[^,]+)*$")" ] && message "$0 (check_basedn) invalid basedn format" && return 1

    :
}

read_pam_ldap()
{
    read_config "$pamldapfile" "$1"
}


read_nss_ldap()
{
    read_config "$nssldapfile" "$1"
}

read_config()
{
    shell_config_get "$1" "$2" "$rdelim"
}


write_profile()
{
    local scheme="$1"

    case "$scheme" in
	local)
	    /usr/sbin/control system-auth local
	    write_nsswitch "passwd" "files"
	    write_nsswitch "shadow" "tcb files"
	    write_nsswitch "group" "files"
	    ;;
	ldap|krb5*)
        if [ "$ldap_auth_sssd" = "yes"  ]; then
            /usr/sbin/control system-auth "sss"
            write_nsswitch "passwd" "files sss"
            write_nsswitch "shadow" "tcb files sss"
            write_nsswitch "group" "files [SUCCESS=merge] sss"
            if [ -x /etc/control.d/facilities/libnss-role ]; then
                control libnss-role enabled
            fi
        else
            /usr/sbin/control system-auth "$scheme"
            write_nsswitch "passwd" "files ldap"
            write_nsswitch "shadow" "tcb files ldap"
            write_nsswitch "group" "files [SUCCESS=merge] ldap"
        fi
	    ;;
	ad)
	    if [ -e "$sssd_conf" -a -z "$use_winbind" ]; then
		scheme="sss"
	    else
		scheme="winbind"
	    fi
            /usr/sbin/control system-auth "$scheme"
	    write_nsswitch "passwd" "files $scheme"
	    write_nsswitch "shadow" "tcb files $scheme"
	    write_nsswitch "group" "files [SUCCESS=merge] $scheme"
	    if [ -x /etc/control.d/facilities/libnss-role ]; then
                control libnss-role enabled
	    fi
	    ;;
    esac
    # [SUCCESS=merge] is supported only in glibc-core >= 2.23. Remove this option for earlier version
    if [ $(rpmvercmp `rpm -q --qf '%{version}' glibc-core` '2.23') == -1 ]; then
	subst 's/\[SUCCESS=merge\] //g' "$nsswitchfile"
    fi
}

write_nsswitch()
{
    write_config "$nsswitchfile" "$1:" "$2"
}

write_2_ldap()
{
    write_pam_ldap "$1" "$2"
    write_nss_ldap "$1" "$2"
    write_ldap_conf "$1" "$2"
}

write_pam_ldap()
{
    [ -f "$pamldapfile" ] && write_config "$pamldapfile" "$1" "$2"
}

write_nss_ldap()
{
    write_config "$nssldapfile" "$1" "$2"
}

write_ldap_conf()
{
    # ugly, but effective
    sed -r -i -e "/^[^#]*$1.*$/Id" "$ldap_conf"
    echo "$1 $2" >> "$ldap_conf"
}

write_config()
{
    shell_config_set "$1" "$2" "$3" "$rdelim" "$wdelim"
}

dn_2_host()
{
    local dn="$1"

    echo "$dn"|sed -e 's/^dc=//i'|sed -e 's/,dc=/\./g'
}


remove_host_from_confs()
{
    del_from_conf_var "$pamldapfile" "host" && del_from_conf_var "$nssldapfile" "host"
}

del_from_conf_var()
{
    shell_config_del "$1" "$2" "$rdelim"
}

upper()
{
    echo -n "$1" | tr '[[:lower:]]' '[[:upper:]]'
}

lower()
{
    echo -n "$1" | tr '[[:upper:]]' '[[:lower:]]'
}

set_domain_group_mapping()
{
	# Check if libnss-role is installed
	if [ ! -x /usr/bin/rolelst ]; then
		return
	fi
	groupadd -r localadmins &>/dev/null
	if [ -e /etc/role ]; then
		/bin/mv -f /etc/role /etc/role.old
		touch /etc/role
	fi
	# Add domain groups by its name
	echo "Domain Users:users" >> /etc/role
	echo "Domain Admins:localadmins" >> /etc/role
}

set_domain_group_mapping_for_ad()
{
    local domain owndomainsid domainusers domainadmins retval=0

    domain="$(echo $1 | tr '[:lower:]' '[:upper:]')"
    # Get domain SID
    owndomainsid="$(net getdomainsid | grep "SID for domain $domain is:" |  sed 's/.*: \(S-1-5-21-.*\).*/\1/')"
    [ -n "$owndomainsid" ] || return 1

    # Add domain groups by its name
    domainusers="$(wbinfo --sid-to-fullname="$owndomainsid-513" | sed 's/\(.*\)\ [0-9]\+/\1/')"
    domainadmins="$(wbinfo --sid-to-fullname="$owndomainsid-512" | sed 's/\(.*\)\ [0-9]\+/\1/')"
    [ -z "$domainadmins" ] || roleadd "$domainadmins" localadmins || retval=1
    [ -z "$domainusers" ] || roleadd "$domainusers" users || retval=1
    return $retval
}

adapt_dm()
{
	export shell_ini_config_prefix=''
	if [ -e /etc/lightdm/lightdm.conf ]; then
		ini_config_set /etc/lightdm/lightdm.conf "Seat:*" "greeter-hide-users" "true"
		chmod a+r /etc/lightdm/lightdm.conf
	fi
	if [ -e /etc/lightdm/lightdm-gtk-greeter.conf ]; then
		ini_config_set /etc/lightdm/lightdm-gtk-greeter.conf "greeter" "show-language-selector" "false"
		chmod a+r /etc/lightdm/lightdm-gtk-greeter.conf
	fi
	export shell_ini_config_prefix='	'
}

check_hostname()
{
    # Check hostname length
    [ "$(echo -n "$1" | wc -m)" -le 15 ]
}

write_ad_conf()
{
	local domain="$1"
	local hostname="$2"
	local workgroup="$3"

	# Prepare values for configuration
	[ -z "$domain" ] && echo "Please set domain name to continue joining" >&2 && exit 1

	# hostname condition by RFC952
	if ! echo "$hostname" | grep '^[0-9a-zA-Z-]\+$' | grep  -q -v '\(^-\)\|\(-$\)\|\(--\)'; then
	    echo -e "Invalid characters in the name. Use letters, numbers and possibly separate them with a sign '-'" >&2
	    exit 1
	fi

	[ -z "$workgroup" ] && workgroup="${domain/.*/}"

	# Convert to upper case
	domain="$(upper $domain)"
	hostname="$(upper $hostname)"
	workgroup="$(upper $workgroup)"

	# Prepare file for write parameters
	test -e "$smb_conf.orig" || cp "$smb_conf" "$smb_conf.orig"

	# Mapping paraments for Samba < 4
	if [ -n "$(rpm -qf /etc/samba/smb.conf --qf="%{version}"|grep '^3\.')" ] ; then
	    MAPPING_PARAMS="$(cat << MAPPING_PARAMS_SAMBA3
        idmap uid = 10000-20000000
        idmap gid = 10000-20000000
        idmap backend = tdb
MAPPING_PARAMS_SAMBA3
)"
	else
		if [ -e "$sssd_conf" -a -z "$use_winbind" ]; then
		    MAPPING_PARAMS="$(cat << MAPPING_PARAMS_SSSD
        idmap config * : range = 200000-2000200000
        idmap config * : backend = sss
MAPPING_PARAMS_SSSD
)"
		else
		    MAPPING_PARAMS="$(cat << MAPPING_PARAMS_SAMBA4
        idmap config * : range = 10000-20000000
        idmap config * : backend = tdb
MAPPING_PARAMS_SAMBA4
)"
		fi
	fi

	if [ -e "$sssd_conf" -a -z "$use_winbind" ]; then
	    WINBIND_PARAMS="$(cat << MAPPING_PARAMS_WINBIND_SSSD
	winbind use default domain = yes
	winbind enum users = no
	winbind enum groups = no
	template homedir = /home/$domain/%U
MAPPING_PARAMS_WINBIND_SSSD
)"
	else
	    WINBIND_PARAMS="$(cat << MAPPING_PARAMS_WINBIND
	winbind use default domain = yes
	winbind enum users = no
	winbind enum groups = no
	template homedir = /home/$domain/%U
	winbind refresh tickets = yes
	winbind offline logon = yes
MAPPING_PARAMS_WINBIND
)"
	fi
	if [ "$win2003" = "true" ]; then
            WIN2003_PARAMS="$(cat << WINDOWS2003_PARAMS
	client min protocol = NT1
WINDOWS2003_PARAMS
)"
	fi

	# Write main parameters
	CONFIG="$(cat << AD_PARAMS
	security = ads
	realm = $domain
	workgroup = $workgroup
	netbios name = $hostname
	template shell = /bin/bash
	kerberos method = system keytab
	wins support = no
$WINBIND_PARAMS
$MAPPING_PARAMS
$WIN2003_PARAMS
	machine password timeout = 0
;	encrypt passwords = true
;	dns proxy = no
;	socket options = TCP_NODELAY
;	domain master = no
;	local master = no
;	preferred master = no
;	os level = 0
;	domain logons = no
;	load printers = no
;	show add printer wizard = no
;	printcap name = /dev/null
;	disable spoolss = yes
AD_PARAMS
)"

	# Replace entire section [global] in /etc/samba/smb.conf by new config
	sed -i -e "/^\[global\]/,/^\[/ {/^\([^[]\|$\)/d};/^\[global\]/a\\`echo "$CONFIG"|sed ':a;{N;s/\n/\\\\n/};ba'`" "$smb_conf"

	# Set parameters for PAM modules
	. shell-ini-config
	shell_ini_config_prefix=''
	if [ -e "$sssd_conf" -a -z "$use_winbind" ]; then
	    ini_config_set "$sssd_conf" 'sssd' 'services' 'nss, pam'
	    ini_config_set "$sssd_conf" 'sssd' 'domains' "$domain"
	    ini_config_set "$sssd_conf" "domain/$domain" 'id_provider' 'ad'
	    ini_config_set "$sssd_conf" "domain/$domain" 'auth_provider' 'ad'
	    ini_config_set "$sssd_conf" "domain/$domain" 'chpass_provider' 'ad'
	    ini_config_set "$sssd_conf" "domain/$domain" 'access_provider' 'ad'
	    ini_config_set "$sssd_conf" "domain/$domain" 'default_shell' '/bin/bash'
	    ini_config_set "$sssd_conf" "domain/$domain" 'fallback_homedir' '/home/%d/%u'
	    ini_config_set "$sssd_conf" "domain/$domain" 'debug_level' '0'
	    ini_config_set "$sssd_conf" "domain/$domain" '; cache_credentials' 'false'
	    ini_config_set "$sssd_conf" "domain/$domain" 'ad_gpo_ignore_unreadable' 'true'
	    ini_config_set "$sssd_conf" "domain/$domain" 'ad_gpo_access_control' 'permissive'
	    ini_config_set "$sssd_conf" "domain/$domain" 'ad_update_samba_machine_account_password' 'true'
	    [ -n "$ad_long_hostname" ] && \
	        ini_config_set "$sssd_conf" "domain/$domain" 'ad_hostname' "$hostname" || \
	        ini_config_del "$sssd_conf" "domain/$domain" 'ad_hostname'
	    chmod 0600 "$sssd_conf"
	else
	    pam_winbind_cfg='/etc/security/pam_winbind.conf'
	    ini_config_set "$pam_winbind_cfg" 'global' 'cached_login' 'yes'
	    ini_config_set "$pam_winbind_cfg" 'global' 'krb5_auth' 'yes'
	    ini_config_set "$pam_winbind_cfg" 'global' 'krb5_ccache_type' 'KEYRING'
	    ini_config_set "$pam_winbind_cfg" 'global' 'silent' 'yes'
            chmod a+r "$pam_winbind_cfg"

	    # Remove krb5_ccache_type=FILE from /etc/pam.d/system-auth-winbind
	    sed -i 's/ krb5_ccache_type=FILE//g' /etc/pam.d/system-auth-winbind
	fi

	# Set time sync from dc for client
	write_pool "$domain"
	write_ntp_status "#t"

	# Adapt DM for too many domain users
	adapt_dm
}

write_sssd_ldap_conf()
{
    local domain="$1"
    local basedn="$2"
    local ldapuri="$3"

    . shell-ini-config
    shell_ini_config_prefix=''
    # Remove previous [domain/...] section
    subst '/^\[domain\//,$d' "$sssd_conf"
    if [ -e "$sssd_conf" ]; then
        ini_config_set "$sssd_conf" 'sssd' 'services' 'nss, pam'
        ini_config_set "$sssd_conf" 'sssd' 'domains' "$domain"
        ini_config_set "$sssd_conf" "domain/$domain" 'id_provider' 'ldap'
        ini_config_set "$sssd_conf" "domain/$domain" 'auth_provider' 'krb5'
        ini_config_set "$sssd_conf" "domain/$domain" 'chpass_provider' 'krb5'
        ini_config_set "$sssd_conf" "domain/$domain" 'ldap_uri' "$ldapuri"
        ini_config_set "$sssd_conf" "domain/$domain" 'ldap_tls_reqcert' 'never'
        ini_config_set "$sssd_conf" "domain/$domain" 'ldap_search_base' "$basedn"
        ini_config_set "$sssd_conf" "domain/$domain" 'ldap_user_object_class' 'posixAccount'
        ini_config_set "$sssd_conf" "domain/$domain" 'ldap_group_object_class' 'posixGroup'
        ini_config_set "$sssd_conf" "domain/$domain" 'ldap_user_home_directory' 'homeDirectory'
        ini_config_set "$sssd_conf" "domain/$domain" 'ldap_force_upper_case_realm' 'true'
        ini_config_set "$sssd_conf" "domain/$domain" 'krb5_server' "$domain"
        ini_config_set "$sssd_conf" "domain/$domain" 'krb5_realm' "$(upper $domain)"
        ini_config_set "$sssd_conf" "domain/$domain" 'debug_level' '0'
        chmod 0600 "$sssd_conf"
    fi

    # Prepare Kerberos environment
    set_kerberos_realm "$domain"

    # Map domain groups to local Unix groups
    set_domain_group_mapping

    # Set time sync from dc for client
    write_pool "$domain"
    write_ntp_status "#t"

    # Adapt DM for too many domain users
    adapt_dm
}

#initial settings
init()
{
    # removing host parameter from pam_ldap_conf
    remove_host_from_confs
    if [ -f "$nssldapfile1" ]; then
        write_config "$nssldapfile1" bind_policy soft
        write_config "$nssldapfile1" bind_timelimit 30
    fi
}

# Enable service and (re)start it
enable_service() {
    service="$1"
    if [ -e "/etc/init.d/$service" -o -e "/lib/systemd/system/$service.service" ]; then
        chkconfig $service on &>/dev/null
        [ -n "$(service $service status| grep '^active\|running$')" ] && service $service stop &>/dev/null
        service $service start >/dev/null
    fi
    return 0
}

enable_timer() {
    timer_name=$1
    if [ -e "/lib/systemd/system/$timer_name.timer" ]; then
        systemctl enable --now "$timer_name.timer" >/dev/null 2>&1
    fi
    return 0
}

# Disable service and stop it
disable_service() {
    service="$1"
    if [ -e "/etc/init.d/$service" -o -e "/lib/systemd/system/$service.service" ]; then
        chkconfig $service off &>/dev/null
        [ -n "$(service $service status| grep '^active\|running$')" ] && service $service stop &>/dev/null
    fi
    if [ -e "/lib/systemd/system/$service.socket" ]; then
    	SYSTEMCTL="systemctl"
    	"$SYSTEMCTL" disable --now $service.socket
    fi
    if [ -e "/lib/systemd/system/$service.timer" ]; then
        SYSTEMCTL="systemctl"
        "$SYSTEMCTL" disable --now "$service.timer"
    fi
    return 0
}

# Set correct FQDN
set_hostname()
{
    local FQDN="$(lower "$1")"
    if [ -n "$FQDN" ]; then
        shell_config_set "/etc/sysconfig/network" "HOSTNAME" "$FQDN"
        [ -f "/etc/hostname" ] && \
          echo "$FQDN" > "/etc/hostname"
    fi
}

# Check domain name in DNS
check_domain_in_dns()
{
    host -t srv "_kerberos._tcp.$1" | grep -q 'has SRV record' 2>/dev/null
    if [ $? -ne 0 ]; then
        echo "Unable to find specified domain" >&2
        return 1
    fi
    return 0
}

# Set parameter to /etc/krb5.conf
krb5_config_set()
{
    local conf_file="$1"; shift
    local section="$1"; shift
    local param="$1"; shift
    local value="$1"; shift
    if grep -q "^[[:space:]]*$param[[:space:]]*=" "$conf_file" ; then
        subst "s/^\([[:space:]]*$param[[:space:]]*=[[:space:]]*\).*$/\1$value/" "$conf_file"
    else
        subst "/^\[$section\]$/a $param = $value" "$conf_file"
    fi
}

# Set Kerberos realm
set_kerberos_realm()
{
    test -e "$krb5_conf" || touch "$krb5_conf"
    krb5_config_set "$krb5_conf" "libdefaults" "default_realm" "$(upper $1)"
    krb5_config_set "$krb5_conf" "libdefaults" "dns_lookup_kdc" "true"
    krb5_config_set "$krb5_conf" "libdefaults" "dns_lookup_realm" "false"
    krb5_config_set "$krb5_conf" "libdefaults" "default_ccache_name" "KEYRING:persistent:%{uid}"
    # Prevent GSS failure in sssd (Server not found in Kerberos database)
    krb5_config_set "$krb5_conf" "libdefaults" "rdns" "false"
    if [ "$win2003" = "true" ]; then
        krb5_config_set "$krb5_conf" "libdefaults" "default_tgs_enctypes" "aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 rc4-hmac des-cbc-crc des3-cbc-sha1 des-cbc-md5"
	krb5_config_set "$krb5_conf" "libdefaults" "default_tkt_enctypes" "aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 rc4-hmac des-cbc-crc des3-cbc-sha1 des-cbc-md5"
	krb5_config_set "$krb5_conf" "libdefaults" "preferred_enctypes" "aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 rc4-hmac des-cbc-crc des3-cbc-sha1 des-cbc-md5"
    fi
    chmod a+r "$krb5_conf"
}

# Register host in domain DNS
dns_register()
{
    local domain=
    local host_name=
    local upper_host_name=
    local dns_host_name=
    local host_tmp_krb5_cache=

    #set vars
    domain="$(upper "$1")"
    host_name="$2"
    if [ -n "$3" ]; then
        dns_host_name="$3"
    fi
    upper_host_name="$(upper $host_name)"
    host_tmp_krb5_cache="$(mktemp | sed "s/^/FILE:/")"

    # Export tmp cache for host
    export KRB5CCNAME="$host_tmp_krb5_cache"

    # Kinit with host cred
    out_host_kinit="$(kinit -k "$upper_host_name"\$@"$domain" 2>&1)"

    # echo "ERROR: $output" and destroy tmp cache
    if [ "$?" -ne 0 ]; then
        echo "$out_host_kinit"
        kdestroy -A &>/dev/null
        unset KRB5CCNAME
        return 1
    else
        # Register machine
        $net_cmd ads dns register --use-kerberos=required --use-krb5-ccache="$host_tmp_krb5_cache" "$dns_host_name.$domain"
    fi

    # destroy ticket and unset host tmp cache var
    kdestroy -A &>/dev/null
    unset KRB5CCNAME
}

# Join to Active Directory domin
join_ad_domain()
{
    local ldomain="$1"
    local domain="$(upper $ldomain)"
    local user=
    local use_krb_ccache=
    local password=
    local host_name=
    local krb_ccache_name=
    local dns_host_name="$2"

    if [ $# -gt 3 ]; then
        user="$3"
        password="$4"
        host_name="$5"
        krb_ccache_name="$(mktemp | sed "s/^/FILE:/")"
        export KRB5CCNAME="$krb_ccache_name"
    else
        use_krb_ccache=1
        host_name="$3"
    fi
    [ -x /usr/bin/kinit ] || fatal "krb5-kinit is required for join to Active Directory domain"
    [ -x "$net_cmd" ] || fatal "$net_cmd from samba-common or samba-common-tools package is required for join to Active Directory domain"
    [ -n "$(ls /etc/init.d/{winbind,sssd} 2>/dev/null)" ] || fatal "samba-winbind or sssd-ad is required for join to Active Directory domain"

    # Prepare Kerberos environment
    set_kerberos_realm "$ldomain"

    # Get or check Kerberos ticket for administrator
    if [ -n "$use_krb_ccache" ]; then
        output="$(klist 2>&1 >/dev/null)"
    else
        output="$(echo "$password" | kinit "$user@$domain" 2>&1 >/dev/null)"
    fi
    if [ "$?" -ne 0 ]; then
	#echo "ERROR: $output"
	error_ccache_kcm_server="$(echo "$output"|grep '^klist: No KCM server found while resolving ccache')"
	error_ccache_not_found="$(echo "$output"|grep '^klist: No credentials cache found')"
	error_ccache_keyring_not_found="$(echo "$output"|grep '^klist: Credentials cache keyring .* not found')"
	error_unknown_kdc="$(echo "$output"|grep '^kinit: Cannot contact any KDC for realm')"
	error_bad_username="$(echo "$output"|grep '^kinit: Client not found in Kerberos database while getting initial credentials$')"
	error_bad_preauth="$(echo "$output"|grep '^kinit: Preauthentication failed while getting initial credentials$')"
	error_bad_credentials="$(echo "$output"|grep '^kinit: Password incorrect while getting initial credentials$')"
	error_pass_expired="$(echo "$output"|grep '^kinit: Cannot read password while getting initial credentials$')"
	[ -n "$error_unknown_kdc" ] && echo "Cannot contact KDC for realm" >&2
	[ -n "$error_bad_username" ] && echo "Unknown administrator name" >&2
	[ -n "$error_bad_preauth" ] && echo "Preauthentication failed" >&2
	[ -n "$error_bad_credentials" ] && echo "Wrong password" >&2
	[ -n "$error_pass_expired" ] && echo "Perhaps the password has expired" >&2
	[ -n "$error_ccache_not_found" ] && echo "No credentials cache found" >&2
	[ -n "$error_ccache_keyring_not_found" ] && echo "No credentials cache found" >&2
	[ -n "$error_ccache_kcm_server" ] && echo "No KCM server found while resolving ccache" >&2
	return 1
    else
        [ -z "$use_krb_ccache" ] ||
            krb_ccache_name="$(klist 2>/dev/null | grep "Ticket cache:" | sed "s/Ticket cache: //")"

	# Set correct FQDN
	set_hostname "$dns_host_name.$ldomain"

    OS_NAME=
    OS_VER=

    if hostnamectl -j 2>/dev/null 1>&2; then
        OS_NAME="$(hostnamectl -j 2>/dev/null |
            jq -r '.OperatingSystemReleaseData[] | select(startswith("NAME=")) |
            sub("NAME="; "")' )"
        OS_VER="$(hostnamectl -j 2>/dev/null |
            jq -r '.OperatingSystemReleaseData[] | select(startswith("VERSION_ID=")) |
            sub("VERSION_ID="; "")')"
    fi

    if [ -z "$OS_NAME" ] || [ -z "$OS_VER" ]; then
        # Set correct OS name and version
        IFS=: read -r OS_NAME OS_VER <<<"$(hostnamectl | sed -E -n 's/^.*Operating System: (.*) ([0-9.]+).*$/\1:\2/p')"

        # Exception for Sisyphus regular builds
        if [ -z "$OS_VER" ]; then
            IFS=: read -r OS_NAME OS_VER <<<"$(hostnamectl | sed -E -n 's/^.*Operating System: ([^0-9.]*) ([^\(\) ]*).*$/\1:\2/p')"
        fi
    fi

    samba_dir_path="/var/lib/samba"
    secrets_tdb="$samba_dir_path/private/secrets.tdb"
    secrets_tdb_backup="$samba_dir_path/private/secrets.tdb.backup"

	# Backup and remove secrets.tdb file before domain join operation
    if [ -f "$secrets_tdb" ]; then
        mv -f "$secrets_tdb" "$secrets_tdb_backup"
    fi

    # Join to domain
    if [ "$dns_host_name" = "$host_name" ]; then
        if [ -z "$OU" ]; then
            $net_cmd ads join --use-kerberos=required --no-dns-updates \
                --use-krb5-ccache="$krb_ccache_name" \
                osName="$OS_NAME" osVer="$OS_VER"
        else
            $net_cmd ads join --use-kerberos=required --no-dns-updates \
                --use-krb5-ccache="$krb_ccache_name" \
                createcomputer="${OU}" osName="$OS_NAME" osVer="$OS_VER"
        fi
    else
        if [ -z "$OU" ]; then
            $net_cmd ads join --use-kerberos=required --no-dns-updates \
                --use-krb5-ccache="$krb_ccache_name" \
                osName="$OS_NAME" osVer="$OS_VER" \
                dnshostname="$dns_host_name.$ldomain"
        else
            $net_cmd ads join --use-kerberos=required --no-dns-updates \
                --use-krb5-ccache="$krb_ccache_name" \
                createcomputer="${OU}" osName="$OS_NAME" osVer="$OS_VER" \
                dnshostname="$dns_host_name.$ldomain"
        fi
    fi

    # Check if domain join operation was successful,
    # restore/remove backup accordingly
    if [ $? -ne 0 ]; then
        mv -f "$secrets_tdb_backup" "$secrets_tdb"
        return 1
    else
        rm -f "$secrets_tdb_backup"
    fi

    # Register additional SPN's for long hostnames
    if [ "$dns_host_name" != "$host_name" ]; then
        # Register SPN's in AD database
        $net_cmd ads setspn add host/"$dns_host_name"
        $net_cmd ads setspn add restrictedkrbhost/"$dns_host_name"

        # Regenerate local keytab with additional SPN's
        $net_cmd ads keytab create
    fi

	[ "$?" -ne 0 ] && return 1

	# Destroy ticket if credential cache not used
	if [ -z "$use_krb_ccache" ]; then
	    kdestroy -A &>/dev/null
	    unset KRB5CCNAME
	fi
    fi
}

# Join FreeIPA domain
join_ipa_domain()
{
    local domain_name="$1"
    local host_name="$2"
    local admin_name="$3"
    local admin_password="$4"
    local log="/tmp/freeipa-join-$(date +%d.%m.%Y-%H:%M:%S)-$domain_name.log"

    # Begin log
    date > "$log"
    echo "Domain: $domain_name" >> "$log"
    echo >> "$log"

    # Set hostname
    set_hostname "$host_name.$domain_name"

    # Uninstall old join
    "$ipa_cmd" -U --uninstall &>/dev/null

    # Join to domain
    $ipa_cmd -U --domain "$domain_name" -p "$admin_name" -w "$admin_password" 2>&1 | tee -a "$log"
    ret=$?

    # Adapt DM for too many domain users
    adapt_dm

    echo "Finished at $(date)" >> "$log"
    return $ret
}

usage() {
    cat << USAGE.
Usage: system-auth [options] action [object...]
Show or change system authentication scheme.

Options:
-d         Show debug output

Actions:
status     show current authentication information
list       list available authentication schemes
write      set authentication parameters

--version  show current program version

Examples:
Show current authentication information
  system-auth status

Use local authentication
  system-auth write local [--gpo]

Use LDAP authentication
  system-auth write ldap dc=domain,dc=name ldap://127.0.0.1

Use Active Directory authentication (using default kerberos ccache without Administrator password).
<host> and <short hostname> must be unique
  system-auth write ad domain.name host workgroup [Administrator password]
        [--windows2003]                         Allow to add machine to Windows AD 2003 and older domains
        [--createcomputer=COMPUTEROU/Linux]     Add machine to specified OU instead of default OU (Computers)
        [--winbind]                             Use Winbind instead of SSSD as client
        [--gpo]                                 Enable group policies (gpupdate)
        [--netbiosname=<short hostname>]        Short NetBIOS name (no more than 15 characters).
                                                Required if <host> is longer than 15 characters

Use FreeIPA authentication
  system-auth write freeipa domain.name host admin password
USAGE.
}

# Place dns source immediately after files if .local domain is requested
check_avahi() {
	domain="$1"
	if [ "${domain##*.}" = 'local' ]; then
		subst '/^hosts:/ s/ dns//;s/files/files dns/' "$nsswitchfile"
	fi
}

action="$1" ; shift
[ "$action" = "-d" ] && action="$1" && shift
[ $# -ge 1 ] && object="$1" && shift

case "$action" in
    status)
        get_status
    ;;
    list)
        list
    ;;
    write)
	# Disable service of old scheme
	current="$(/usr/sbin/control system-auth)"
	[ "$current" = "ldap" -o "$current" = "krb5" ] && disable_service nslcd
	if [ "$current" = "winbind" ]; then
        disable_service winbind
        disable_service winbind-dnsupdate
    fi
	if [ "$current" = "sss" ]; then
	    disable_service sssd
	    disable_service winbind
	fi

    case "$object" in
        local)
            write_profile "$object"
            if [ "$use_gpo" == "true" ]; then
                gpupdate_setup local
            fi
            ;;
        ldap|krb5*)
            check_avahi "$2"
            if  check_basedn "$1" && check_uri "$2"  ;then
                if [ "$ldap_auth_sssd" = "yes" ]; then
                    rpm -q sssd-ldap sssd-krb5 &>/dev/null || fatal "LDAP authentication with SSSD requires both sssd-ldap and sssd-krb5 packages installed"
                    write_profile "$object"

                    # Extract domain name from ldap_uri
                    domain="${2##*/}"
                    domain="${domain##ldap.}"

                    # Store configuration parameters: domain baseDN ldap_uri
                    write_sssd_ldap_conf "$domain" "$1" "$2"

                    # Make topdir for home directories for Astra Linux Directory
                    domain_type="$(ldapsearch -x -LLL -b "$1" -h "$domain" -s base o 2>/dev/null | sed -n 's/o: //p')"
                    if [ "$domain_type" = "Astra Linux Directory" ]; then
                        mkdir /ald_home 2>/dev/null
                    fi

                    enable_service sssd
                    disable_service nscd
                else
                    init
                    write_profile "$object"
                    write_2_ldap base "$1"
                    write_2_ldap uri "$2"
                    # if nss-ldapd is used, restart daemon
                    enable_service nslcd
                fi
            else
                exit 1
            fi
            ;;
        ad)
            if  test -n "$1" ;then
                ad_dnshostname="$2"
                if ! check_hostname "$ad_dnshostname" && [ -z "$ad_netbioshostname" ]; then
                    echo "To use a machine name longer than 15 characters, you must additionally specify" >&2
                    echo "a short NetBIOS name using the --netbiosname option" >&2
                    exit 1
                fi
                if [ -n "$ad_netbioshostname" ] && ! check_hostname "$ad_netbioshostname"; then
                    echo "NetBIOS name must not be longer than 15 characters."
                    exit 1
                fi
                [ -n "$ad_netbioshostname" ] && [ "$ad_dnshostname" != "$ad_netbioshostname" ] && \
                    ad_long_hostname=1
                [ -z "$ad_netbioshostname" ] && ad_netbioshostname="$ad_dnshostname"

                check_avahi "$1"
                test -x "$net_cmd" || fatal "Cannot find $net_cmd executable required to join to Active Directory domain"
                # Check domain name in DNS
                check_domain_in_dns "$1" || exit 1
                write_profile "$object"
                # Sync time with DC before join
                $net_cmd time set -S "$1" &>/dev/null

                # Store configuration parameters: domain [hostname] [workgroup]
                write_ad_conf "$1" "$ad_netbioshostname" "$3"
                # Join computer to domain
                if [ $# -ge 5 ]; then
                    join_ad_domain "$1" "$ad_dnshostname" "$4" "$5" "$ad_netbioshostname"
                else
                    join_ad_domain "$1" "$ad_dnshostname" "$ad_netbioshostname"
                fi
                [ "$?" -ne 0 ] && exit 1
                # prepare auth service
                if [ -e "$sssd_conf" -a -z "$use_winbind" ]; then
                    disable_service nscd
                    enable_service winbind
                    enable_service sssd
                else
                    disable_service nscd
                    disable_service sssd
                    enable_service winbind
                    enable_timer winbind-dnsupdate
                fi

                # Register in domain dns
                dns_register "$1" "$ad_netbioshostname" "$ad_dnshostname"

                # Adding roles for domain users
                set_domain_group_mapping_for_ad "$3" || set_domain_group_mapping
                # Enable GPO
                if [ "$use_gpo" == "true" ]; then
                    gpupdate_setup
                fi
            else
                exit 1
            fi
            ;;
        freeipa)
            if  test -n "$1" ;then
                check_avahi "$1"
                test -x "$ipa_cmd" || fatal "Cannot find $ipa_cmd executable required to join to FreeIPA domain"
                # Check domain name in DNS
                check_domain_in_dns "$1" || exit 1
                join_ipa_domain "$1" "$2" "$3" "$4"
                [ "$?" -ne 0 ] && exit 1
                # prepare auth service
                enable_service sssd
            else
                exit 1
            fi
            ;;

        *)
            fatal "unknown auth type $object"
        ;;
      esac
    ;;
    *)
      usage
	;;
esac

