#!/bin/bash

samba_account_lockout_threshold=5
samba_account_lockout_duration=5
samba_reset_account_lockout_after=5

echo "Detect UCS version"
lsb_release -r

eval "$(ucr shell)"

setup_all_password_lockout_policies() {
  echo "Configure Samba/AD password policy for test:"
  samba-tool domain passwordsettings set  \
	--account-lockout-threshold="${samba_account_lockout_threshold}" \
	--account-lockout-duration="${samba_account_lockout_duration}" \
  	--reset-account-lockout-after="${samba_reset_account_lockout_after}"

  echo "Configure local UCS faillog policy for test:"
  ucr set auth/faillog=yes auth/faillog/unlock_time=$((samba_account_lockout_duration * 60))

  echo "Configure UCS ppolicy config:"
  ucr set ldap/ppolicy=yes ldap/ppolicy/enabled=yes
  systemctl restart slapd

  ldapmodify -D "cn=admin,$ldap_base" -y /etc/ldap.secret <<%EOR
dn: cn=default,cn=ppolicy,cn=univention,$ldap_base
changetype: modify
replace: pwdMaxFailure
pwdMaxFailure: ${samba_account_lockout_threshold}
-
replace: pwdLockoutDuration
pwdLockoutDuration: $((samba_account_lockout_duration * 60))
-
replace: pwdFailureCountInterval
pwdFailureCountInterval: $((samba_reset_account_lockout_after * 60))
%EOR
}

## In a ucs-test setup we would want to configure the policies:
# setup_all_password_lockout_policies
## In a customer environment we must not do this.

echo "Samba/AD Domain Password policy:"
samba-tool domain passwordsettings show
## Expected:
# Password informations for domain '$ldap_base'
# Password complexity: on
# Store plaintext passwords: off
# Password history length: 0
# Minimum password length: 8
# Minimum password age (days): 0
# Maximum password age (days): 0
# Account lockout duration (mins): 5
# Account lockout threshold (attempts): 5
# Reset account lockout after (mins): 5

echo "UCS faillog/pam_tally policy:"
ucr search --brief faillog
## Expected:
# auth/faillog/limit: <empty>
# auth/faillog/lock_global: yes
# auth/faillog/root: <empty>
# auth/faillog/unlock_time: 300
# auth/faillog: yes

echo "UCS ppolicy config:"
ucr search --brief ppolicy
## Expected:
# ldap/ppolicy/default: <empty>
# ldap/ppolicy/enabled: yes
# ldap/ppolicy: yes

univention-ldapsearch -LLL -b "cn=default,cn=ppolicy,cn=univention,$ldap_base" -s base pwdLockout pwdMaxFailure pwdLockoutDuration pwdFailureCountInterval
## Expected:
# dn: cn=default,cn=ppolicy,cn=univention,dc=samltest,dc=intranet
# pwdLockout: TRUE
# pwdMaxFailure: 5
# pwdLockoutDuration: 300
# pwdFailureCountInterval: 300


### Test 4) Trigger via ppolicy:
test_account=user4

# 4.1 Check account state is ok
echo -e "\n\n## Check account state is ok"
echo "Check state of LDAP attributes:"
univention-ldapsearch -LLL uid="${test_account}" \
  userPassword sambaBadPasswordCount krb5KDCFlags sambaAcctFlags sambaBadPasswordTime pwdFailureTime pwdAccountLockedTime | ldapsearch-decode64
## Expected:
# sambaAcctFlags: [U          ]
# sambaBadPasswordCount: 0
# sambaBadPasswordTime: 0
# userPassword: {crypt}$6$...
# krb5KDCFlags: 126

echo "Check state of Samba/AD attributes:"
univention-s4search samaccountname="${test_account}" | egrep '^(badPwdCount|userAccountControl|lockoutTime): '
## Expected:
# userAccountControl: 512
# badPwdCount: 0

echo "Check Kerberos auth works:"
echo -n "univention" | kinit --password-file=STDIN "${test_account}" && echo ok
klist
kdestroy

echo "Check SMB Logon works:"
smbclient "//$(hostname -f)/sysvol" -U"${test_account}"%univention -c showconnect && echo ok
# Expected: //$hostname.$domainname/sysvol  ## SMB Logon works

echo "Check state of local faillog/pam_tally:"
pam_tally --user "${test_account}"
# Expected: User user4      (2017)  has 0

## 4.2 Trigger lockout via ppolicy
echo -e "\n\n## Trigger lockout via ppolicy"
for i in $(seq 5); do
	ldapsearch -LLL -D "uid=${test_account},cn=users,$ldap_base" -w wrong uid="${test_account}" 1.1
	## TODO: Depending on timing the S4-Connector ping-pong seems to mess up the sambaAcctFlags, leaving the account unlocked!
	# sleep 1
done

echo "Confirm LDAP lockout"
ldapsearch -LLL -D "uid=${test_account},cn=users,$ldap_base" -w univention uid="${test_account}" 1.1 || echo "OK, LDAP account locked out"

echo "Wait for S4-Connector replication"
sleep 10

## 4.3 Check LDAP attribute state, UDM state, Samba/AD attributes, local faillog, SMB Logon, Kerberos auth
echo -e "\n\n## Check account state after lock"
echo "Check state of LDAP attributes:"
univention-ldapsearch -LLL uid="${test_account}" \
  userPassword sambaBadPasswordCount krb5KDCFlags sambaAcctFlags sambaBadPasswordTime pwdFailureTime pwdAccountLockedTime | ldapsearch-decode64
## Expected:
# sambaBadPasswordCount: 0
# userPassword: {crypt}$6$...
# krb5KDCFlags: 126
# pwdFailureTime: 20210313072255.075943Z
# pwdFailureTime: 20210313072255.999354Z
# pwdFailureTime: 20210313072256.745364Z
# pwdFailureTime: 20210313072257.443448Z
# pwdFailureTime: 20210313072258.172143Z
# sambaBadPasswordTime: 132600937780000000
# sambaAcctFlags: [UL         ]
# pwdAccountLockedTime: 20210313072258Z

echo "Check UDM account state:"
udm users/user list --filter username="${test_account}" | egrep "^ +locked:"
# Expected:  locked: 1

echo "Check state of Samba/AD attributes:"
## Note: badPwdCount is 0, maybe we could sync that to a count of pwdFailureTime?
univention-s4search samaccountname="${test_account}" | egrep '^(badPwdCount|userAccountControl|lockoutTime): '
## Expected:
# userAccountControl: 512
# badPwdCount: 0
# lockoutTime: <!=0>

## Check account is locked in Samba/AD:
echo "Check SMB Logon is locked:"
smbclient "//$(hostname -f)/sysvol" -U"${test_account}"%univention -c showconnect || echo "Ok, SMB Logon is locked"
# Expected: session setup failed: NT_STATUS_ACCOUNT_LOCKED_OUT

echo "Check Kerberos auth is locked:"
echo -n "univention" | kinit --password-file=STDIN "${test_account}" || echo "Ok, Kerberos auth is locked"
# Expected: kinit: krb5_get_init_creds: No PKINIT PA found

echo "Check state of local faillog/pam_tally:"
pam_tally --user "${test_account}"

## 4.4 Wait for unlock and check LDAP attribute state, UDM state, Samba/AD attributes, local faillog, SMB Logon, Kerberos auth
echo -e "\n\n## Wait for unlock and check account state"
sleep 300

echo "Check state of LDAP attributes (should still show the locked state):"
univention-ldapsearch -LLL uid="${test_account}" \
  userPassword sambaBadPasswordCount krb5KDCFlags sambaAcctFlags sambaBadPasswordTime pwdFailureTime pwdAccountLockedTime | ldapsearch-decode64
## Expected:
# sambaBadPasswordCount: 0
# userPassword: {crypt}$6$...
# krb5KDCFlags: 126
# pwdFailureTime: 20210313072255.075943Z
# pwdFailureTime: 20210313072255.999354Z
# pwdFailureTime: 20210313072256.745364Z
# pwdFailureTime: 20210313072257.443448Z
# pwdFailureTime: 20210313072258.172143Z
# sambaBadPasswordTime: 132600937780000000
# sambaAcctFlags: [UL         ]
# pwdAccountLockedTime: 20210313072258Z

echo "Check UDM account state (should still show the locked state):"
udm users/user list --filter username="${test_account}" | egrep "^ +locked:"
# Expected:  locked: 1

echo "Check state of Samba/AD attributes (should still show the locked state):"
univention-s4search samaccountname="${test_account}" | egrep '^(badPwdCount|userAccountControl|lockoutTime): '
## Expected:
# userAccountControl: 512
# badPwdCount: 0
# lockoutTime: 0

## Ok, now the flow dependends on how the unlock is driggered:
# a) by good user login against Samba/AD (SMB or Kerberos)
# b) by good user login against OpenLDAP/UDM (ldapbind or UDM)
# c) by good user login against PAM (e.g. UMC or ssh)
# d) by good user login against SAML
# e) by admin (via UMC/UDM)
# In a complete test coverage all of these would need to be tested separately

## Unlock via Samba/AD SMB logon:
echo "Check SMB Logon works again:"
smbclient "//$(hostname -f)/sysvol" -U"${test_account}"%univention -c showconnect && echo ok
# Expected: //$hostname.$domainname/sysvol  ## SMB Logon works

echo "Check Kerberos auth works again"
echo -n "univention" | kinit --password-file=STDIN "${test_account}" && echo ok
# Expected: ok

echo "Check state of Samba/AD attributes (should show unlocked state now):"
univention-s4search samaccountname="${test_account}" | egrep '^(badPwdCount|userAccountControl|lockoutTime): '
## Expected:
# userAccountControl: 512
# badPwdCount: 0
# lockoutTime: 0

echo "Wait for S4-Connector replication"
sleep 10

echo "Check state of LDAP attributes (should show sambaAcctFlags unlocked but still show the ppolicy locked state):"
univention-ldapsearch -LLL uid="${test_account}" \
  userPassword sambaBadPasswordCount krb5KDCFlags sambaAcctFlags sambaBadPasswordTime pwdFailureTime pwdAccountLockedTime | ldapsearch-decode64
## Expected:
# sambaBadPasswordCount: 0
# userPassword: {crypt}$6$...
# krb5KDCFlags: 126
# pwdFailureTime: 20210313072255.075943Z
# pwdFailureTime: 20210313072255.999354Z
# pwdFailureTime: 20210313072256.745364Z
# pwdFailureTime: 20210313072257.443448Z
# pwdFailureTime: 20210313072258.172143Z
# sambaBadPasswordTime: 132600937780000000
# sambaAcctFlags: [U          ]
# pwdAccountLockedTime: 20210313072258Z

echo "Check UDM account state (should show the unlocked state):"
udm users/user list --filter username="${test_account}" | egrep "^ +locked:"
# Expected:  locked: 0

echo "Check LDAP bind (should work now):"
ldapsearch -LLL -D "uid=${test_account},cn=users,$ldap_base" -w univention uid="${test_account}" 1.1 && echo "OK, LDAP bind works again"


echo "Check state of LDAP attributes (should show full unlocked state now):"
univention-ldapsearch -LLL uid="${test_account}" \
  userPassword sambaBadPasswordCount krb5KDCFlags sambaAcctFlags sambaBadPasswordTime pwdFailureTime pwdAccountLockedTime | ldapsearch-decode64
## Expected:
# sambaAcctFlags: [U          ]
# sambaBadPasswordCount: 0
# sambaBadPasswordTime: 0
# userPassword: {crypt}$6$...
# krb5KDCFlags: 126

echo "Check UDM account state (should show unlocked state now):"
udm users/user list --filter username="${test_account}" | egrep "^ +locked:"
# Expected:  locked: 0

echo "Check state of Samba/AD attributes (should show unlocked state):"
univention-s4search samaccountname="${test_account}" | egrep '^(badPwdCount|userAccountControl|lockoutTime): '
## Expected:
# userAccountControl: 512
# badPwdCount: 0
# lockoutTime: 0

echo "Check state of local faillog/pam_tally:"
pam_tally --user "${test_account}"
# Expected: User user4      (2017)  has 0
