#!/bin/bash

# debian-edu-router_refresh-ldap-filterlists: Load group of users and clients from
# LDAP into Proxy filterlists.
# See Debian Edu Router Plugin: Content Filter.
# See Debian Edu Router Plugin: LDAP/AD Connector.
# Documentation can be found under: /usr/share/doc/debian-edu-router*/

# Copyright (C) 2024 Pädagogisches Landesinstitut Rheinland-Pfalz
# Copyright (C) 2024 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
#
# 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 2 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, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

set -eo pipefail

common_file="/usr/share/debian-edu-router/debian-edu-router.common"

# Load common functions, variables and stuff.
if [ -s "$common_file" ]; then
	source "$common_file"
else
	echo "Could not load common file at "$common_file"."
	exit 0;
fi

# Use D-E-R wrapper for ldapsearch.
LDSEARCH="/usr/sbin/debian-edu-router_ldapsearch -LLL"

# LOAD FOLLOWING VARIABLES #
declare -A ProxyGroupsTypes
declare -A ProxyGroupLDGrpNames
declare -A ProxyGroupsBaseDNs
declare -A ProxyGroupsPaths
declare -a ALL_PROXY_GROUPS
LDAP_DNS_SERVERS=""

LDAP_GROUP_MATCHING_CFG_FILE="/etc/debian-edu-router/ldap.d/ldap-groups-to-squid-acl-matching.sh"
# Load predefined variables, generated from debconf questions.
if [ -s "$LDAP_GROUP_MATCHING_CFG_FILE" ]; then
	source "$LDAP_GROUP_MATCHING_CFG_FILE"
else
	error_log "Could not source from LDAP group matching config file at:"
	error_log "'$LDAP_GROUP_MATCHING_CFG_FILE'."
	error_log "Please reconfigure the LDAP/AD connector plugin using the loginmenu or via"
	error_log "'dpkg-reconfigure debian-edu-router-plugin.ldap-connector'."
	exit 1;
fi

FILTERLISTS_PATH="/var/lib/debian-edu-router/filterlists.d/"
function finish {
	# If we crash, do not keep file there, delete it.
	rm -f "${FILTERLISTS_PATH}/work-in-progress"
}
trap finish EXIT

# Function to check if LDAP server is a Microsoft AD
IS_MICROSOFT_AD=""
function is_microsoft_ad() {
	local is_m_ad=""

	# Cache result and abort if result is already known.
	if [[ -n "${IS_MICROSOFT_AD}" ]]; then
		return
	fi

	while read -r KEY VALUE; do # Important -r: Disable backslashes to escape characters.
		case "$KEY" in
			# dn:) DN="dn=$VALUE" ;; # DN will always be available.
			sAMAccountName:)
				test "$VALUE" || continue # Don't allow empty VALUE.
				is_m_ad="true"
				break
			;;
		esac
	done < <($LDSEARCH "(objectClass=person)" sAMAccountName 2>/dev/null | perl -p0e 's/\n //g')

	if [[ -n "${is_m_ad}" ]]; then
		notice_log "The LDAP server is probably a Microsoft Active Directory."
		IS_MICROSOFT_AD="true"
	else
		notice_log "The LDAP server is probably NOT a Microsoft Active Directory."
		IS_MICROSOFT_AD="false"
	fi
}

# Checks if groupName is valid. If not, it will return an empty string to prevent LDAP
# searchstring attacks (similar to SQL injection attacks).
# String $1: The groupName which should be sanitized.
function sanitize_groupName() {
	# Do not allow anything else than: a-Z 0-9 _ -
	if [[ "$1" =~ ^[a-zA-Z0-9_-]+$ ]]; then
		echo "$1"
	else
		echo "" # groupName contains illegal characters, disable searching with it.
	fi
}

# String $1: The triple coming from an LDAP search.
# Returns:
#   - $trip_client
#   - $trip_user
#   - $trip_domain
function split_nisNetgroupTriple() {
	# Clean up triple.
	local triple="$(echo $triple | tr '(' ' ' | tr ')' ' ' | xargs)"
	# Now split triplet into 3 variables.
	export trip_client="$(echo $triple | awk -F ',' '{ print $1 }')"
	export trip_user="$(  echo $triple | awk -F ',' '{ print $2 }')"
	export trip_domain="$(echo $triple | awk -F ',' '{ print $3 }')"
}

# $1: member DN (with objectClass=person).
# $2: Filter computer accounts (sAMAccountName's ending with '$') (true|false|'').
# Returns $ACCOUNTNAMES: sAMAccountName e.g. "testUser1 testUser2".
function ldapsearch_sAMAccountName() {
	ACCOUNTNAMES=()
	local member_dn="$1"
	local filter_computer_accounts="$2"

	# TODO: Sanitize member_dn. See https://ldap.com/ldap-dns-and-rdns/

	# Search for objectClass=person objects with (potentially multiple) sAMAccountName
	# attribute(s), extract it/them and add it/them to uids.
	while read -r KEY VALUE; do # Important -r: Disable backslashes to escape characters.
		case "$KEY" in
			# dn:) DN="dn=$VALUE" ;; # DN will always be available.
			sAMAccountName:)
				test "$VALUE" || continue # Don't allow empty VALUE.

				if [[ "$filter_computer_accounts" = "true" ]]; then
					if echo "${VALUE: -1}" | grep -q '\$'; then
						debug_log "  - Skipping computer account '$VALUE'..."
						continue
					fi
				fi

				ACCOUNTNAMES+=("$VALUE")
			;;
		esac
	done < <($LDSEARCH -b "$member_dn" "(objectClass=person)" sAMAccountName 2>/dev/null | perl -p0e 's/\n //g')
}

# $*: member DN (with objectClass=person).
# Returns $ACCOUNTNAMES: uid e.g. "testUser1".
function ldapsearch_uid() {
	ACCOUNTNAMES=()
	local member_dn="$*"

	# TODO: Sanitize member_dn. See https://ldap.com/ldap-dns-and-rdns/

	# Search for objectClass=person objects with (potentially multiple) uid
	# attribute(s), extract it/them and add it/them to uids.
	while read -r KEY VALUE; do # Important -r: Disable backslashes to escape characters.
		case "$KEY" in
			# dn:) DN="dn=$VALUE" ;; # DN will always be available.
			uid:)
				test "$VALUE" || continue # Don't allow empty VALUE.
				ACCOUNTNAMES+=("$VALUE")
			;;
		esac
	done < <($LDSEARCH -b "$member_dn" "(objectClass=person)" uid 2>/dev/null | perl -p0e 's/\n //g')
}

# $*: member DN (with objectClass=computer).
# Returns: dNSHostName e.g. "testClient.intern pc04.edu".
function ldapsearch_dNSHostName() {
	HOSTNAMES=()
	local member_dn="$*"

	# TODO: Sanitize member_dn. See https://ldap.com/ldap-dns-and-rdns/

	# Search for objectClass=computer objects with (potentially multiple) dNSHostName
	# attribute(s), extract it/them and add it/them to hostnames.
	while read -r KEY VALUE; do # Important -r: Disable backslashes to escape characters.
		case "$KEY" in
			# dn:) DN="dn=$VALUE" ;; # DN will always be available.
			dNSHostName:)
				test "$VALUE" || continue # Don't allow empty VALUE.
				HOSTNAMES+=("$VALUE")
			;;
		esac
	done < <($LDSEARCH -b "$member_dn" "(objectClass=computer)" dNSHostName 2>/dev/null | perl -p0e 's/\n //g')
}

# $*: member DN (with objectClass=dhcpHost).
# Returns: HOSTNAMES e.g. "testClient.intern pc04.edu".
function ldapsearch_dhcpHost() {
	HOSTNAMES=()
	local member_dn="$*"

	# TODO: Sanitize member_dn. See https://ldap.com/ldap-dns-and-rdns/
	# Search for objectClass=dhcpHost objects with (potentially multiple) dhcpOption, dhcpStatements
	# attribute(s), extract it/them and add it/them to hostnames.
	while read -r KEY VALUE; do # Important -r: Disable backslashes to escape characters.
		case "$KEY" in
			dn:)
				DN="$VALUE"

				;; # DN will always be available.
			dhcpOption:)
				test "$VALUE" || continue # Don't allow empty VALUE.

				if [ "$DN" != "$member_dn" ]; then
					continue
				fi

				if ! grep -q "host-name" <<< "$VALUE"; then
					continue
				else
					# Cut 'host-name ' part.
					VALUE=$(cut -f 2- -d ' ' <<< "$VALUE")
				fi

				# TODO: Get full FQDN somehow or check if static IP is set via (dhcpStatements: fixed-address 10.0.10.1).

				HOSTNAMES+=("$VALUE")
				;;

			dhcpStatements:)
				test "$VALUE" || continue # Don't allow empty VALUE.

				if [ "$DN" != "$member_dn" ]; then
					continue
				fi

				if ! grep -q "fixed-address" <<< "$VALUE"; then
					continue
				else
					# Cut 'fixed-address ' part.
					ip_address=$(cut -f 2- -d ' ' <<< "$VALUE")
				fi

				local success=""
				for dns_server in "${LDAP_DNS_SERVERS[@]}"; do
					local stdout_tmp="$(mktemp)"
					local stderr_tmp="$(mktemp)"
					set +e # Pause -e, because we do error handling manually for 'dig' (and NOT exit incase of error).

					dig @"${dns_server}" -x "${ip_address}" +short +timeout=3 1> "${stdout_tmp}" 2> "${stderr_tmp}"

					# Error handling
					if [ "$?" -ne 0 ] || [ -n "$(cat ${stderr_tmp})" ] || [ -z "$(cat ${stdout_tmp})" ]; then
						warning_log "  - Reverse IP lookup of address '${ip_address}' could not be resolved (stdout):"
						[ -n "$(cat ${stdout_tmp})" ] && debug_log "  -\n$(cat ${stdout_tmp})"
						warning_log "  - Reverse IP lookup of address '${ip_address}' could not be resolved (stderr):"
						[ -n "$(cat ${stderr_tmp})" ] && debug_log "  -\n$(cat ${stderr_tmp})"
						warning_log "  - Used DNS server ${dns_server}.\n"

						set -e
						rm -f "${stdout_tmp}" "${stderr_tmp}"
						continue
					fi

					local hostname="$(cat ${stdout_tmp})"
					local hostname="${hostname%.}"
					debug_log "  - Found hostname '${hostname}' from reverse looking up static IP address '${ip_address}' from server '${dns_server}'."

					HOSTNAMES+=("${hostname}")

					rm -f "${stdout_tmp}" "${stderr_tmp}"
					set -e

					success="true"
					break
				done

				if [ -z "${success}" ]; then
					warning_log "  - Reverse IP lookup of address '${ip_address}' could not be resolved using DNS servers '${LDAP_DNS_SERVERS[@]}'!"
				fi
				;;
		esac
	done < <($LDSEARCH "(objectClass=dhcpHost)" dhcpOption dhcpStatements 2>/dev/null | perl -p0e 's/\n //g')
}

# String $1: groupName (e.g. cn for posixGroup) of LDAP group.
# String $2: proxyGroupName (e.g. ProxyTrustedUser, ProxyDenyClient, etc...)
# String $3: proxyGroupType (e.g. posixGroup, nisNetgroup, group, etc...)
# String $4: is_recursive (true/"")
# Returns $ITEMS (e.g. "teidan", "gabmik", etc...)
export nested_counter=0
function ldapsearch_group() {
	local ldapGroupName="$1"
	local proxyGroupName="$2"
	local proxyGroupType="$3"
	local is_recursive="$4"
	export ITEMS=()

	# If we find nested groups, do traverse them, but limit ourselves.
	local MAX_NESTED_COUNTER=16

	_groupName="$(sanitize_groupName "$ldapGroupName")"
	if [[ -z "$_groupName" && -n "$ldapGroupName" ]]; then
		warning_log_stderr "Groupname '$ldapGroupName' is not valid and contains illegal characters! Skipping calling ldapsearch."
		return
	fi
	ldapGroupName="$_groupName"

	local DN="" # Not needed currently.
	local objectClass=""
	local targetAttributes=()
	if [[ "${proxyGroupType}" == "nisNetgroup" ]]; then
		objectClass="nisNetgroup"
		targetAttributes=("nisNetgroupTriple" "memberNisNetgroup")
	elif [[ "${proxyGroupType}" == "groupOfNames" ]]; then
		objectClass="groupOfNames"
		targetAttributes=("member")
	elif [[ "${proxyGroupType}" == "group" ]]; then
		objectClass="group"
		targetAttributes=("member")
	elif [[ "${proxyGroupType}" == "posixGroup" ]]; then
		objectClass="posixGroup"
		targetAttributes=("memberUid")
	else
		error_log "Proxy group type '${proxyGroupType}' is not known!"
		error_log "You or a script modified ${LDAP_GROUP_MATCHING_CFG_FILE} probably."
		error_log "Or a version mismatch could be happening, of this script and Debian Edu Router Plugin: LDAP connector."
		exit 1
	fi

	local nestedNisNetgroups=()
	while read -r KEY VALUE; do # Important -r: Disable backslashes to escape characters.
		targetAttributes+=("dn")
		for targetAttribute in "${targetAttributes[@]}"; do
			if [[ "$KEY" == "dn:" ]]; then
				DN="dn=$VALUE"
			# Special Case: nisNetgroups can be nested.
			elif [[ "$KEY" == "memberNisNetgroup:" ]]; then
				test "$VALUE" || continue # Don't allow empty VALUE.
				nestedNisNetgroups+=("$VALUE")
			elif [[ "$KEY" == "${targetAttribute}:" ]]; then
				test "$VALUE" || continue # Don't allow empty VALUE.
				ITEMS+=("$VALUE")
			fi
		done
	done < <($LDSEARCH "(&(objectClass="${objectClass}")(cn=${ldapGroupName}))" "${targetAttributes[@]}" 2>/dev/null | perl -p0e 's/\n //g')

	local _items_main=( "${ITEMS[@]}" ) # $ITEMS will be overriden by recursive calling ldapsearch_group.
	local _items_nested=()

	# Remove duplicates and sort.
	local nestedNisNetgroups=( $(echo "${nestedNisNetgroups[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ') )

	# If we have any nested groups, recursively call this function.
	for nestedNisNetgroup in "${nestedNisNetgroups[@]}"; do
		if [ "${nested_counter}" -lt "${MAX_NESTED_COUNTER}" ]; then
			nested_counter=$(($nested_counter+1))

			debug_log "Found nested nisNetgroup '${nestedNisNetgroup}' inside of '${ldapGroupName}'. Issuing LDAP search..."

			if ldapsearch_group "${nestedNisNetgroup}" "${proxyGroupName}" "${proxyGroupType}" "recursive-ftw"; then
				if [[ "${#ITEMS[@]}" -gt 0 ]]; then
					debug_log "Found #${#ITEMS[@]} entries in nested nisNetgroup '${nestedNisNetgroup}'."
					_items_nested+=( "${ITEMS[@]}" )
				fi
			else
				# last ldapsearch_group failed, let's not go deeper into nested nisNetgroups.
				break;
			fi
		else
			warning_log "Reached maximum of '${MAX_NESTED_COUNTER}' levels of nested nisNetgroups!"
			warning_log "Are the groups reffering to each other, creating an endless cycle?"
			if [[ -z "${D_E_R_DEBUG}" ]]; then
				warning_log "Please enable debugging mode in the loginmenu to see stack trace."
			fi

			return 1
		fi
	done

	# Merge the arrays using a for loop
	for element in "${_items_main[@]}" "${_items_nested[@]}"; do
			# Trim leading and trailing whitespace from each element before adding to merged_array
			element=$(echo "$element" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
			ITEMS+=("$element")
	done

	if [ "${#ITEMS[@]}" -lt 1 ]; then
			debug_log "No entries found in proxy group's '${proxyGroupName}' LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
	else
			# Sort the array and remove duplicates
			mapfile -t ITEMS < <(printf "%s\n" "${ITEMS[@]}" | sort -u)
	fi

	# $nested_counter is "public", do not forget to reset it!
	if [[ -z "$is_recursive" ]]; then
		nested_counter=0
	fi
}

# String $1: SEARCH_DN
# String $2: PROXY_GROUP_NAME
# RETURNS: $ITEMS, $LAST_OBJECT_CLASS, $LAST_TARGET_ATTRIBUTE
function ldapsearch_search_dn() {
	local SEARCH_DN="$1"
	local PROXY_GROUP_NAME="$2"
	export ITEMS=()

	local objectClass=""
	local targetAttribute=""

	is_microsoft_ad
	if [[ "$IS_MICROSOFT_AD" == "true" ]]; then
		# Microsoft AD mode
		if [[ "${PROXY_GROUP_NAME}" == "Proxy"*"User" ]]; then
			objectClass="person"
			targetAttribute="sAMAccount"
		elif [[ "${PROXY_GROUP_NAME}" == "Proxy"*"Client" ]]; then
			objectClass="computer"
			targetAttribute="dNSHostName"
		else
			exit 1
		fi
	elif [[ "$IS_MICROSOFT_AD" == "false" ]]; then
		# Posix mode
		if [[ "${PROXY_GROUP_NAME}" == "Proxy"*"User" ]]; then
			objectClass="person"
			targetAttribute="uid"
		elif [[ "${PROXY_GROUP_NAME}" == "Proxy"*"Client" ]]; then
			objectClass="computer"
			targetAttribute="dhcpHost"
		else
			exit 1
		fi
	fi

	export LAST_OBJECT_CLASS="${objectClass}"
	export LAST_TARGET_ATTRIBUTE="${targetAttribute}"

	# _groupName="$(sanitize_groupName "$groupName")"
	# if [[ -z "$_groupName" && -n "$groupName" ]]; then
	# 	warning_log_stderr "Groupname '$groupName' is not valid and contains illegal characters! Skipping calling ldapsearch."
	# 	return
	# fi
	# groupName="$_groupName"

	while read -r KEY VALUE; do # Important -r: Disable backslashes to escape characters.
		case "$KEY" in
			# dn:) DN="dn=$VALUE" ;; # DN will always be available.
			"${targetAttribute}":)
				test "$VALUE" || continue # Don't allow empty VALUE.
				ITEMS+=("$VALUE")
			;;
		esac
	done < <($LDSEARCH -b "${SEARCH_DN}" "(objectClass=${objectClass})" "${targetAttribute}" 2>/dev/null | perl -p0e 's/\n //g')

	if [ "${#ITEMS[@]}" -lt 1 ]; then
		debug_log "No entries found for proxy group '${PROXY_GROUP_NAME}' in LDAP subtree '${PROXY_GROUP_BASE_DN}'."
		debug_log "using 'objectClass=person' and 'searchAttribute=uid'."
	fi
}

function updateFilterList() {
	ERROR_HAPPENED="" # Return this to caller.
	local PROXY_GROUP_NAME="$1"
	local PROXY_GROUP_LD_GRP_TYPE="$2"
	local PROXY_GROUP_LD_GRP_NAME="$3"
	local PROXY_GROUP_FILTERLIST_PATH="$4"
	local PROXY_GROUP_BASE_DN="$5"
	local FILTERLIST_HEADER_TEMPLATE_FILE="/usr/share/debian-edu-router/templates/ProxyHeader.ldap"

	[[ -z "${PROXY_GROUP_NAME}" ]]            && error_log "ASSERTION: \${PROXY_GROUP_NAME} "         "('${PROXY_GROUP_NAME}') "         "-n failed." && exit 1
	[[ -z "${PROXY_GROUP_LD_GRP_TYPE}" ]]     && error_log "ASSERTION: \${PROXY_GROUP_LD_GRP_TYPE} "  "('${PROXY_GROUP_LD_GRP_TYPE}') "  "-n failed." && exit 1
	[[ -z "${PROXY_GROUP_LD_GRP_NAME}" ]]     && error_log "ASSERTION: \${PROXY_GROUP_LD_GRP_NAME} "  "('${PROXY_GROUP_LD_GRP_NAME}') "  "-n failed." && exit 1
	[[ -z "${PROXY_GROUP_FILTERLIST_PATH}" ]] && error_log "ASSERTION: \${PROXY_GROUP_FILTERLIST_PATH} ('${PROXY_GROUP_FILTERLIST_PATH}') -n failed." && exit 1
	# [[ -z "${PROXY_GROUP_BASE_DN}" ]]         && error_log "ASSERTION: \${PROXY_GROUP_BASE_DN} "      "('${PROXY_GROUP_BASE_DN}') "      "-n failed." && exit 1

	notice_log "Refreshing filterlist '$PROXY_GROUP_FILTERLIST_PATH'..."

	cp --backup="numbered"    "$FILTERLIST_HEADER_TEMPLATE_FILE"                                        "$PROXY_GROUP_FILTERLIST_PATH"
	echo "# This file will be filled automatically via the '${PROXY_GROUP_LD_GRP_NAME}' LDAP group." >> "$PROXY_GROUP_FILTERLIST_PATH"
	if [[ -n "${PROXY_GROUP_BASE_DN}" ]]; then
		echo "# and via searching the '${PROXY_GROUP_BASE_DN}' LDAP subtree."                        >> "$PROXY_GROUP_FILTERLIST_PATH"
	fi
	echo                                                                                             >> "$PROXY_GROUP_FILTERLIST_PATH"

	# FIXME: TODO: Refactor if-else block here. Put them into individual files, which define functions for syncing users/groups.
	if [ "${PROXY_GROUP_LD_GRP_TYPE}" == "nisNetgroup" ]; then
		if [[ -n "${PROXY_GROUP_BASE_DN}" ]]; then
			ldapsearch_search_dn "${PROXY_GROUP_BASE_DN}" "${PROXY_GROUP_NAME}"
			for item in "${ITEMS[@]}"; do
					notice_log "  - Found obj. '${LAST_OBJECT_CLASS}(${LAST_TARGET_ATTRIBUTE})': $(printf "%-21s" "\"${item}\"") in LDAP subtree '$PROXY_GROUP_BASE_DN'."
					echo "$item"                                                                     >> "$PROXY_GROUP_FILTERLIST_PATH"
			done
		fi

		ldapsearch_group "${PROXY_GROUP_LD_GRP_NAME}" "${PROXY_GROUP_NAME}" "nisNetgroup"
		for triple in "${ITEMS[@]}"; do
			debug_log "Found nisNetgroupTriple [${PROXY_GROUP_LD_GRP_NAME}]: $triple"

			split_nisNetgroupTriple "$triple"

			if [[ "${PROXY_GROUP_NAME}" == "Proxy"*"User" ]]; then
				# We are only interested in 'user' part of the triple.
				if [ -n "$trip_user" ]; then
					notice_log "  - Found user '$trip_user' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
					echo "$trip_user"                                                                >> "$PROXY_GROUP_FILTERLIST_PATH"
				else
					debug_log "  - Skipping triple with empty user..."
				fi
			elif [[ "${PROXY_GROUP_NAME}" == "Proxy"*"Client" ]]; then
				# We are only interested in 'client' part of the triple.
				if [ -n "$trip_client" ]; then
					notice_log "  - Found client '$trip_client' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
					echo "$trip_client"                                                              >> "$PROXY_GROUP_FILTERLIST_PATH"
				else
					debug_log "  - Skipping triple with empty client..."
				fi
			fi
		done
	elif [ "${PROXY_GROUP_LD_GRP_TYPE}" == "group" ]; then
		if [[ -n "${PROXY_GROUP_BASE_DN}" ]]; then
			ldapsearch_search_dn "${PROXY_GROUP_BASE_DN}" "${PROXY_GROUP_NAME}"
			for item in "${ITEMS[@]}"; do
					notice_log "  - Found obj. '${LAST_OBJECT_CLASS}(${LAST_TARGET_ATTRIBUTE})': $(printf "%-21s" "\"${item}\"") in LDAP subtree '$PROXY_GROUP_BASE_DN'."
					echo "$item"                                                                     >> "$PROXY_GROUP_FILTERLIST_PATH"
			done
		fi

		ldapsearch_group "${PROXY_GROUP_LD_GRP_NAME}" "${PROXY_GROUP_NAME}" "group"
		for member_dn in "${ITEMS[@]}"; do
			debug_log "Found member DN in user group '${PROXY_GROUP_LD_GRP_NAME}': '$member_dn'."

			if [[ "${PROXY_GROUP_NAME}" == "Proxy"*"User" ]]; then
				ldapsearch_sAMAccountName "$member_dn"
				for accountname in "${ACCOUNTNAMES[@]}"; do
						notice_log "  - Found sAMAccountName '$accountname' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
						echo "$accountname"                                                          >> "$PROXY_GROUP_FILTERLIST_PATH"
				done
			elif [[ "${PROXY_GROUP_NAME}" == "Proxy"*"Client" ]]; then
				ldapsearch_dNSHostName $member_dn
				for hostname in "${HOSTNAMES[@]}"; do
						notice_log "  - Found dNSHostName '$hostname' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
						echo "$hostname"                                                             >> "$PROXY_GROUP_FILTERLIST_PATH"
				done
			fi
		done
	elif [ "${PROXY_GROUP_LD_GRP_TYPE}" == "groupOfNames" ]; then
		if [[ -n "${PROXY_GROUP_BASE_DN}" ]]; then
			ldapsearch_search_dn "${PROXY_GROUP_BASE_DN}" "${PROXY_GROUP_NAME}"
			for item in "${ITEMS[@]}"; do
					notice_log "  - Found obj. '${LAST_OBJECT_CLASS}(${LAST_TARGET_ATTRIBUTE})': $(printf "%-21s" "\"${item}\"") in LDAP subtree '$PROXY_GROUP_BASE_DN'."
					echo "$item"                                                                     >> "$PROXY_GROUP_FILTERLIST_PATH"
			done
		fi

		ldapsearch_group "${PROXY_GROUP_LD_GRP_NAME}" "${PROXY_GROUP_NAME}" "groupOfNames"
		for member_dn in "${ITEMS[@]}"; do
			debug_log "Found member DN in user group '${PROXY_GROUP_LD_GRP_NAME}': '$member_dn'."

			if [[ "${PROXY_GROUP_NAME}" == "Proxy"*"User" ]]; then
				ldapsearch_sAMAccountName "$member_dn" true
				for accountname in "${ACCOUNTNAMES[@]}"; do
						notice_log "  - Found sAMAccountName '$accountname' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
						echo "$accountname"                                                          >> "$PROXY_GROUP_FILTERLIST_PATH"
				done

				ldapsearch_uid "$member_dn"
				for accountname in "${ACCOUNTNAMES[@]}"; do
						notice_log "  - Found uid '$accountname' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
						echo "$accountname"                                                          >> "$PROXY_GROUP_FILTERLIST_PATH"
				done
			elif [[ "${PROXY_GROUP_NAME}" == "Proxy"*"Client" ]]; then
				ldapsearch_dhcpHost "$member_dn"
				for hostname in "${HOSTNAMES[@]}"; do
						notice_log "  - Found dhcpHost '$hostname' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
						echo "$hostname"                                                             >> "$PROXY_GROUP_FILTERLIST_PATH"
				done

				ldapsearch_dNSHostName "$member_dn"
				for hostname in "${HOSTNAMES[@]}"; do
						notice_log "  - Found dNSHostName '$hostname' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
						echo "$hostname"                                                             >> "$PROXY_GROUP_FILTERLIST_PATH"
				done
			fi
		done
	elif [ "${PROXY_GROUP_LD_GRP_TYPE}" == "posixGroup" ]; then
		if [[ -n "${PROXY_GROUP_BASE_DN}" ]]; then
			ldapsearch_search_dn "${PROXY_GROUP_BASE_DN}" "${PROXY_GROUP_NAME}"
			for item in "${ITEMS[@]}"; do
					notice_log "  - Found obj. '${LAST_OBJECT_CLASS}(${LAST_TARGET_ATTRIBUTE})': $(printf "%-21s" "\"${item}\"") in LDAP subtree '$PROXY_GROUP_BASE_DN'."
					echo "$item"                                                                     >> "$PROXY_GROUP_FILTERLIST_PATH"
			done
		fi

		# Special case: Do not allow anything other than Proxy*User here.
		if [[ "${PROXY_GROUP_NAME}" != "Proxy"*"User" ]]; then
			error_log "Proxy group with name '${PROXY_GROUP_LD_GRP_NAME}' can not be of type ${PROXY_GROUP_LD_GRP_TYPE}."
			error_log "This is probably because LDAP computer acccounts can not be added to LDAP posixGroups."
			error_log "You or a script modified ${LDAP_GROUP_MATCHING_CFG_FILE} probably."
			error_log "Or a version mismatch could be happening, of this script and debian-edu-router-plugin.ldap-connector."
			mv -v "$PROXY_GROUP_FILTERLIST_PATH.~1~"                                                    "$PROXY_GROUP_FILTERLIST_PATH"
			rm -f "$PROXY_GROUP_FILTERLIST_PATH".~*~
			ERROR_HAPPENED="posixGroup_non_user_error"
			return
		fi

		ldapsearch_group "${PROXY_GROUP_LD_GRP_NAME}" "${PROXY_GROUP_NAME}" "posixGroup"
		for member_uid in "${ITEMS[@]}"; do
			debug_log "Found member UID in user group '${PROXY_GROUP_LD_GRP_NAME}': '$member_uid'."

			notice_log "  - Found user '$member_uid' in LDAP group '${PROXY_GROUP_LD_GRP_NAME}'."
			echo "$member_uid"                                                                       >> "$PROXY_GROUP_FILTERLIST_PATH"
		done
	else
		error_log "Proxy group type '${PROXY_GROUP_LD_GRP_TYPE}' is currently not supported."
		error_log "You or a script modified ${LDAP_GROUP_MATCHING_CFG_FILE} probably."
		error_log "Or a version mismatch could be happening, of this script and debian-edu-router-plugin.ldap-connector."
		mv -v "$PROXY_GROUP_FILTERLIST_PATH.~1~"                                                        "$PROXY_GROUP_FILTERLIST_PATH"
		rm -f "$PROXY_GROUP_FILTERLIST_PATH".~*~
		ERROR_HAPPENED="unsupported_ldap_group_type"
		return
	fi

	rm -f "$PROXY_GROUP_FILTERLIST_PATH".~*~
}

function main() {
	if ! [[ -e "/var/lib/debian-edu-router/d-e-r-p.l-c/enabled" ]]; then
		error_log "LDAP/AD connector plugin is not enabled!"
		exit 1;
	fi

	if ! $LDSEARCH > /dev/null; then
		error_log "Could not connect to LDAP!"
		exit 1;
	fi

	# Do not let Squid-ACL-watcher reload all instances of Squid again and again.
	echo "$(LANG=C.UTF-8 date)" > "${FILTERLISTS_PATH}/work-in-progress"

	is_microsoft_ad

	#
	# UPDATE PROXY GROUP FILTERLISTS
	#
	if [[ -z "${ALL_PROXY_GROUPS[@]}" ]]; then
		warning_log "Nothing to do, since the LDAP group types of the proxy groups are not selected yet."
		warning_log ""
		warning_log "Please reconfigure the LDAP connector plugin using the loginmenu (p -> <d-e-r-p.l-c number> -> a)."
		warning_log "Please also note that you can disable the LDAP connector plugin completely,"
		warning_log "without deselecting every proxy group type, using the loginmenu (p -> <d-e-r-p.l-c number> -> t)."
	fi

	for proxy_group in "${ALL_PROXY_GROUPS[@]}"; do
		local group_name="${proxy_group}"
		local group_ld_grp_type="${ProxyGroupsTypes["${proxy_group}"]}"
		local group_ld_grp_name="${ProxyGroupLDGrpNames["${proxy_group}"]}"
		local group_filterlists_path="${ProxyGroupsPaths["${proxy_group}"]}"
		local group_ld_base_dn="${ProxyGroupsBaseDNs["${proxy_group}"]}"

		local errored_attribute=""
		[[ -z "${group_name}" ]]             && errored_attribute="Proxy group name"
		[[ -z "${group_ld_grp_type}" ]]      && errored_attribute="LDAP group type"
		[[ -z "${group_ld_grp_name}" ]]      && errored_attribute="LDAP group name"
		[[ -z "${group_filterlists_path}" ]] && errored_attribute="Filterlists path"
		# [[ -z "${group_ld_base_dn}" ]]       && errored_attribute="LDAP base/search DN"
		if [[ -n "${errored_attribute}" ]]; then
			warning_log "$(printf "While trying to proccess '${proxy_group}': Essential attribute '%s' was left empty, skipping..." "${errored_attribute}")"
			continue
		fi

		updateFilterList "$group_name" "$group_ld_grp_type" "$group_ld_grp_name" "$group_filterlists_path" "${group_ld_base_dn}"
		if [[ -n "$ERROR_HAPPENED" ]]; then
			error_message_reminder="${ERROR_HAPPENED}"
		fi
	done

	# Squid may be reloaded now too :)
	rm -f "${FILTERLISTS_PATH}/work-in-progress"
	systemctl start squid_d-e-r_acl_watcher.service 1>/dev/null || true
}


# Remind at the end of program, that an error occured.
error_message_reminder=""

main

if [[ -n "${error_message_reminder}" ]]; then
	error_log "At least one error occured! Last error code: '${error_message_reminder}'."
	error_log "Please scroll up to see the full error message."
	error_log "Enable debugging information using the loginmenu ('d') to further evaluate what did go wrong."
	error_log "Please contact your administrator, if you believe this is a bug (please attach debug logging information)."
	exit 1
fi
