#!/bin/sh

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"

# 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"

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

#turn off auto expansion
set -f

# 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)
            echo -n "ad " && pam_ad_info
		;;
        *)
            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
    [ -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" 

    # checking winbind libs
    [ -n "$(find /$(getconf SLIB)/security -maxdepth 1 -name 'pam_winbind.*')" ] &&
	echo "ad"
}

pam_ldap_info()
{
    local uri basedn

    if [ -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"
}

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"
	[ "$scheme" = "ad" ] && scheme="winbind"
    /usr/sbin/control system-auth "$scheme" &&
    case "$1" in
	local)
	    write_nsswitch "passwd" "files"
	    write_nsswitch "shadow" "tcb files"
	    write_nsswitch "group" "files"
	    ;;
	ldap|krb5*)
	    write_nsswitch "passwd" "files ldap"
	    write_nsswitch "shadow" "tcb files ldap"
	    write_nsswitch "group" "files [SUCCESS=merge] ldap"
	    ;;
	ad)
	    write_nsswitch "passwd" "files winbind"
	    write_nsswitch "shadow" "tcb files winbind"
	    if [ -x /usr/bin/rolelst ]; then
	        write_nsswitch "group" "files [SUCCESS=merge] winbind role"
	    else
	        write_nsswitch "group" "files [SUCCESS=merge] winbind"
	    fi
	    ;;
    esac
}

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
	roleadd users `cat $user_groups`
	roleadd localadmins `cat $admin_groups`
	# Add domain groups by its name
	echo "Domain Users:users" >> /etc/role
	echo "Domain Admins:localadmins" >> /etc/role
}

adapt_dm()
{
	if [ -e /etc/lightdm/lightdm.conf ]; then
		ini_config_set /etc/lightdm/lightdm.conf "SeatDefaults" "greeter-hide-users" "true"
	fi
	if [ -e /etc/lightdm/lightdm-gtk-greeter.conf ]; then
		ini_config_set /etc/lightdm/lightdm-gtk-greeter.conf "greeter" "show-language-selector" "false"
	fi
}

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

	# Prepare values for configuration
	[ -z "$hostname" ] && hostname="$(hostname -s)"
	[ -z "$workgroup" ] && workgroup="${domain/.*/}"

	# Check hostname length
	if [ "$(echo -n "$hostname" | wc -m)" -gt 15 ]; then
		echo "Netbios name should not be more 15 chars" >&2
		exit 1
	fi

	# 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 "$(/usr/sbin/winbindd --version|grep '^Version 3\.')" ] ; then
	    MAPPING_PARAMS="$(cat << MAPPING_PARAMS_SAMBA3
        idmap uid = 10000-20000000
        idmap gid = 10000-20000000
        idmap backend = tdb
MAPPING_PARAMS_SAMBA3)"
	else
	    MAPPING_PARAMS="$(cat << MAPPING_PARAMS_SAMBA4
        idmap config * : range = 10000-20000000
        idmap config * : backend = tdb
MAPPING_PARAMS_SAMBA4)"
	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
	dedicated keytab file = /etc/krb5.keytab
	winbind use default domain = yes
	winbind enum users = no
	winbind enum groups = no
	winbind refresh tickets = yes
	winbind offline logon = yes
	wins support = yes
$MAPPING_PARAMS
;	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"

	# Support offline login and set more usable parameters for pam_winbind
	. shell-ini-config
	shell_ini_config_prefix=''
	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'

	# 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

	# 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.servce" ]; then
        chkconfig $service on &>/dev/null
        [ -n "$(service $service status| grep '^active\|running$')" ] && service $service stop &>/dev/null
        service $service start >/dev/null
    fi
}

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

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

    [ -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"
    [ -e /etc/init.d/winbind ] || fatal "samba-winbind is required for join to Active Directory domain"

    # Prepare Kerberos environment
    local i="$shell_ini_config_prefix"
    export shell_ini_config_prefix=''
    ini_config_set "$krb5_conf" "libdefaults" "default_realm" "$(upper $1)"
    export shell_ini_config_prefix="$i"

    # Remove realms section
    sed -i '/^\[realms\]/,/^\[/{//!d};/^\[realms\]/d' "$krb5_conf"
    # Add realm
    cat >> "$krb5_conf" << EOF.
[realms]
$domain = {
kdc = $domain
}
EOF.
    chmod 0644 "$krb5_conf"

    # Get Kerberos ticket for administrator
    output="$(echo "$password" | kinit "$user@$domain" 2>&1 >/dev/null)"
    if [ "$?" -ne 0 ]; then
	#echo "ERROR: $output"
	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_credentials="$(echo "$output"|grep '^kinit: Preauthentication failed 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_credentials" ] && echo "Wrong password" >&2
	return 1
    else
	# Set correct FQDN
	FQDN="$(lower "$host_name.$ldomain")"
	shell_config_set "/etc/sysconfig/network" "HOSTNAME" "$FQDN"
	hostname "$FQDN"
	[ -x "/usr/bin/hostnamectl" ] && hostnamectl set-hostname "$FQDN"

	# Join to domain
	$net_cmd ads join -U"$user%$password" | grep -v '^Using short domain name'

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

	# Register machine in domain DNS
	if [ -n "$host_name" ]; then
		$net_cmd ads dns register -U"$user%$password" "$FQDN"
	fi

	# Destroy ticket
	kdestroy &>/dev/null
    fi
}

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

Actions:
status     show current authentication information
list       list available authentication schemes
write      set authentication parameters
--version  Show current program verision

Examples:
Show current authentication information
  system-auth status

Use local authentication
  system-auth write local

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

Use Active Directory authentication
  system-auth write ad domain.name host workgroup Administrator password
USAGE.
}

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
	  [ "$current" = "ldap" -o "$current" = "krb5" ] && disable_service settime-rfc867
	  [ "$current" = "winbind" ] && disable_service winbind

	  case "$object" in
        local)
            write_profile "$object"
        ;;
        ldap|krb5*)
            if  check_basedn "$1" && check_uri "$2"  ;then
                init
                write_profile "$object"
                write_2_ldap base "$1"
                write_2_ldap uri "$2"
                # if nss-ldapd is used, restart daemon
		enable_service nslcd
		enable_service settime-rfc867
            else
                exit 1
            fi
        ;;
        ad)
            if  test -n "$1" ;then
		test -x "$net_cmd" || fatal "Cannot find $net_cmd executable required to join to Active Directory domain"
                init
                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" "$2" "$3"
                # Join computer to domain
                join_ad_domain "$1" "$4" "$5" "$2"
                [ "$?" -ne 0 ] && exit 1
                # prepare winbind service
                enable_service winbind
            else
                exit 1
            fi
		;;
        *)
            fatal "unknown auth type $object"
        ;;
      esac
    ;;
    --version)
        rpm -q alterator-auth --qf '%{version}\n'
	;;
    *)
	usage
	;;
esac

