#!/bin/bash

declare -A BEHAVIOR_VERSION
BEHAVIOR_VERSION=(
	["2008_R2"]=4
)
# BEHAVIOR_VERSION["2012"]=5
# BEHAVIOR_VERSION["2012R2"]=6
# BEHAVIOR_VERSION["2016"]=7

LDAP_MATCHING_RULE_BIT_AND="1.2.840.113556.1.4.803"

eval "$(ucr shell domainname hostname samba4/ldap/base version/version)"

set_dc_level() {
	local dn
	local behavior_version
	dn="$1"
	behavior_version="$2"
	if [ -z "$dn" ]; then
		echo "ERROR: raise_dc_level requires dn as first argument" >&2
		exit 1
	fi
	if [ -z "$behavior_version" ]; then
		echo "ERROR: raise_dc_level requires behavior version as second argument" >&2
		exit 1
	fi
	echo "Setting Behavior Version of '$dn' to $behavior_version"
	ldbmodify -H /var/lib/samba/private/sam.ldb <<-%EOF
	dn: $dn
	changetype: modify
	replace: msDS-Behavior-Version
	msDS-Behavior-Version: $behavior_version
	%EOF
}

raise_dc_level() {
	local dc_level
	dc_level="$1"
	if [ -z "$dc_level" ]; then
		echo "ERROR: function raise_domain_level requires DC level as first argument" >&2
		exit 1
	fi
	ldif=$(ldbsearch -H /var/lib/samba/private/sam.ldb --controls=domain_scope:0 "(&(objectClass=computer)(userAccountControl:$LDAP_MATCHING_RULE_BIT_AND:=0x82000))" serverReferenceBL | ldapsearch-wrapper)
	while read serverReference; do
		ldif=$(ldbsearch -H /var/lib/samba/private/sam.ldb -b "$serverReference" "(objectClass=nTDSDSA)" msDS-Behavior-Version)
		dn=$(sed -n "s/^dn: //p" <<<"$ldif")
		behavior_version=$(sed -n "s/^msDS-Behavior-Version: //p" <<<"$ldif")
		if [ "$behavior_version" -lt "$dc_level" ]; then
			set_dc_level "$dn" "$dc_level"
		fi
	done < <(sed -n "s/^serverReferenceBL: //p" <<<"$ldif")
}

raise_domain_and_forest_level() {
	local domain_level
	domain_level="$1"
	if [ -z "$domain_level" ]; then
		echo "ERROR: function raise_domain_level requires domain level as first argument" >&2
		exit 1
	fi

	local rc
	rc=1

	ldif=$(ldbsearch -H /var/lib/samba/private/sam.ldb -b "$samba4_ldap_base" -s base msDS-Behavior-Version)
	domain_behavior_version=$(sed -n "s/^msDS-Behavior-Version: //p" <<<"$ldif")
	if [ "$domain_behavior_version" -lt "${BEHAVIOR_VERSION["$function_level"]}" ]; then
		echo "Setting domain function level to $function_level"
		samba-tool domain level raise --domain-level="$domain_level" || exit $?
		rc=0  # indicates we did something
	fi

	ldif=$(ldbsearch -H /var/lib/samba/private/sam.ldb -b "CN=Partitions,CN=Configuration,$samba4_ldap_base" -s base msDS-Behavior-Version)
	forest_behavior_version=$(sed -n "s/^msDS-Behavior-Version: //p" <<<"$ldif")
	if [ "$forest_behavior_version" -lt "${BEHAVIOR_VERSION["$function_level"]}" ]; then
		echo "Setting forest function level to $function_level"
		samba-tool domain level raise --forest-level="$domain_level" || exit $?
		rc=0  # indicates we did something
	fi
	return "$rc"
}

chgkrbtgtpass_samba47() {
	## The avoids the issue of https://forge.univention.org/bugzilla/show_bug.cgi?id=39207
	python <<%EOF
## Source: https://git.samba.org/?p=samba.git;a=blob_plain;hb=refs/heads/v4-7-stable;f=source4/scripting/devel/chgkrbtgtpass
##
## Alternative: samba-tool user setpassword --random-password --filter sAMAccountName=krbtgt
##
#!/usr/bin/env python
#
# Copyright (C) Matthieu Patou <mat@matws.net>  2010
# Copyright (C) Andrew Bartlett <abartlet@samba.org>  2015
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


__docformat__ = "restructuredText"


import optparse
import sys
# Allow to run from s4 source directory (without installing samba)
sys.path.insert(0, "bin/python")

import samba.getopt as options
from samba.credentials import DONT_USE_KERBEROS
from samba.auth import system_session
from samba import param
from samba.provision import find_provision_key_parameters
from samba.upgradehelpers import (get_paths,
                                  get_ldbs,
                                 update_krbtgt_account_password)

parser = optparse.OptionParser("chgkrbtgtpass [options]")
sambaopts = options.SambaOptions(parser)
parser.add_option_group(sambaopts)
parser.add_option_group(options.VersionOptions(parser))
credopts = options.CredentialsOptions(parser)
parser.add_option_group(credopts)

opts = parser.parse_args()[0]

lp = sambaopts.get_loadparm()
smbconf = lp.configfile
creds = credopts.get_credentials(lp)
creds.set_kerberos_state(DONT_USE_KERBEROS)


paths = get_paths(param, smbconf=smbconf)
session = system_session()

ldbs = get_ldbs(paths, creds, session, lp)
ldbs.startTransactions()

names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
                                      paths, smbconf, lp)

update_krbtgt_account_password(ldbs.sam, names)
ldbs.groupedCommit()
%EOF
}

chgkrbtgtpass_samba410() {
	## The avoids the issue of https://forge.univention.org/bugzilla/show_bug.cgi?id=39207
	python3 <<%EOF
## Source: https://git.samba.org/?p=samba.git;a=blob_plain;hb=refs/heads/v4-10-stable;f=source4/scripting/devel/chgkrbtgtpass
##
## Alternative: samba-tool user setpassword --random-password --filter sAMAccountName=krbtgt
##
#!/usr/bin/env python3
#
# Copyright (C) Matthieu Patou <mat@matws.net>  2010
# Copyright (C) Andrew Bartlett <abartlet@samba.org>  2015
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


__docformat__ = "restructuredText"


import optparse
import sys
# Allow to run from s4 source directory (without installing samba)
sys.path.insert(0, "bin/python")

import samba.getopt as options
from samba.credentials import DONT_USE_KERBEROS
from samba.auth import system_session
from samba import param
from samba.provision import find_provision_key_parameters
from samba.upgradehelpers import (get_paths,
                                  get_ldbs,
                                 update_krbtgt_account_password)

parser = optparse.OptionParser("chgkrbtgtpass [options]")
sambaopts = options.SambaOptions(parser)
parser.add_option_group(sambaopts)
parser.add_option_group(options.VersionOptions(parser))
credopts = options.CredentialsOptions(parser)
parser.add_option_group(credopts)

opts = parser.parse_args()[0]

lp = sambaopts.get_loadparm()
smbconf = lp.configfile
creds = credopts.get_credentials(lp)
creds.set_kerberos_state(DONT_USE_KERBEROS)


paths = get_paths(param, smbconf=smbconf)
session = system_session()

ldbs = get_ldbs(paths, creds, session, lp)
ldbs.startTransactions()

update_krbtgt_account_password(ldbs.sam)
ldbs.groupedCommit()
%EOF
}

raise_function_level() {
	local function_level
	function_level="$1"
	if [ -z "$function_level" ]; then
		echo "ERROR: function raise_domain_level requires function level as first argument" >&2
		exit 1
	elif [ -z "${BEHAVIOR_VERSION["$function_level"]}+isset" ]; then
		echo "ERROR: raise_function_level: unknown function level: $function_level" >&2
		exit 1
	fi

	local rc
	rc=1

	raise_dc_level "${BEHAVIOR_VERSION["$function_level"]}" || exit 1
	if raise_domain_and_forest_level "$function_level"; then
		output=$(ldbsearch -H /var/lib/samba/private/sam.ldb samaccountname=krbtgt supplementalcredentials | s4search-decode)
		if ! grep -q "keytype: aes" <<<"$output"; then
			## Alternative: ldbsearch -H /var/lib/samba/private/sam.ldb samaccountname=krbtgt supplementalcredentials --show-binary | grep 'Primary:Kerberos-Newer-Keys'
			echo "INFO: KRBTGT account doesn't have AES keys yet"
			echo "Changing password of KRBTGT"
			chgkrbtgtpass || exit $?
		fi
		rc=0  # indicates we did something
	fi
	return "$rc"
}

chgkrbtgtpass() {
	if [ "$version_version" == "4.4" ]; then
		chgkrbtgtpass_samba410
	else
		chgkrbtgtpass_samba47
	fi
}

list_locally_known_samba4_dcs() {
	local ldif
	local dc_list

	declare -a dc_list
	ldif=$(ldbsearch -H /var/lib/samba/private/sam.ldb --controls=domain_scope:0 "(&(objectClass=computer)(userAccountControl:$LDAP_MATCHING_RULE_BIT_AND:=0x82000))" sAMAccountName | ldapsearch-wrapper)
	while read sAMAccountName; do
		cn=$(univention-ldapsearch -LLL -oldif-wrap=no "(uid=$sAMAccountName)" cn | sed -n "s/^cn: //p")
		dc_list+=("$cn")
	done < <(sed -n "s/^sAMAccountName: //p" <<<"$ldif")
	echo "${dc_list[*]}"
}

list_school_samba4_dcs() {
	ldif=$(univention-ldapsearch "(univentionService=S4 SlavePDC)" cn)
	sed -n 's/^cn: //p' <<<"$ldif"
}

list_todo_service_accounts() {
	for dc in $(list_locally_known_samba4_dcs); do
		if [ "$dc" != "$hostname" ]; then
			prefix="ssh $dc '"
			suffix="'"
		else
			prefix=""
			suffix=""
		fi
		echo "      samba-tool user delete dns-$dc"
		echo "      ${prefix}mv /var/lib/samba/private/dns.keytab /var/lib/samba/private/dns.keytab.bak$suffix"
		echo "      ${prefix}/usr/share/univention-samba4/scripts/create_spn_account.sh --samaccountname dns-$dc --serviceprincipalname DNS/$dc.$domainname$suffix"
		echo "      "
	done
}

list_todo_server_password_change() {
	for dc in $(list_locally_known_samba4_dcs); do
		if [ "$dc" != "$hostname" ]; then
			prefix="ssh $dc '"
			suffix="'"
		else
			prefix=""
			suffix=""
		fi
		echo "      ${prefix}ucr get server/password/interval > /tmp/server_password_interval$suffix"
		echo "      ${prefix}ucr set server/password/interval=0$suffix"
		echo "      ${prefix}/usr/lib/univention-server/server_password_change$suffix"
		echo "      ${prefix}ucr set server/password/interval=\$(</tmp/server_password_interval)$suffix"
		echo "      "
	done
}

echo "         "
echo "WARNING: running this script may change the function level of this domain and possibly update the krbtgt password hash"
echo "WARNING: If the function level actually needed to be raised, all joined"
echo "         Microsoft Windows systems will have to rotate their machine passwords!"
echo "         "
echo "WARNING: Please first check this in a representative (i.e. cloned) test environment before proceeding."
echo "         "
read -p "Continue? [yN]: "
if [ "${REPLY^^}" != Y ]; then
	exit 1
fi
if raise_function_level "2008_R2"; then
	echo "      "
	echo "INFO: Now log on to each UCS Samba/AD DC and change the passwords of all Kerberos service accounts."
	echo "      like this:"
	echo "      "
	list_todo_service_accounts
	read -p "Done? [N]: "
	while [ "${REPLY^^}" != Y ]; do
		read -p "Done? [N]: "
	done

	echo "      "
	echo "INFO: Now log on to each UCS Samba/AD DC and change their machine passwords *once* and one at a time."
	echo "      like this:"
	echo "      "
	list_todo_server_password_change
	read -p "Done? [N]: "
	while [ "${REPLY^^}" != Y ]; do
		read -p "Done? [N]: "
	done

	dc_list=$(list_school_samba4_dcs)
	if [ -n "$dc_list" ]; then
		echo "      "
		echo "INFO: This seems to be a UCS@school domain, so you should additionally"
		echo "      copy and run this script on the following UCS@school Slave PDCs:"
		echo "      $dc_list"
	fi

	echo "      "
	echo "INFO: Now force all Microsoft Windows systems to change their passwords and reboot."
	echo "      This can be done by running the following two commands on all Microsoft Windows systems"
	echo "      as Domain Admin or local Admin:"
	echo "      "
	echo "      nltest /SC_CHANGE_PWD:$domainname"
	echo "      shutdown /r /t 0 /c PasswordChage"

else
	echo "      "
	echo "INFO: Nothing done"
fi
echo "      "
samba-tool domain level show
