#!/bin/bash
### Copyright 1999-2026. WebPros International GmbH. All rights reserved.

export LC_ALL="C" PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
unset GREP_OPTIONS
umask 022

prog="`basename $0`"
action="$1"

# Communication protocol with repaird is:
# * emit output to stdout as plaintext;
# * stderr is not shown to user, but may be logged by 'repaird -v 3';
# * last line of output is JSON with any remaining output (see result() below);
# * exit code is 0.

info()
{
	echo "INFO: $*"
}

warn()
{
	echo "WARNING: $*"
}

err()
{
	echo "ERROR: $*"
}

die()
{
	err "$@" | result "failed"
	exit 0
}

success()
{
	echo -n | result "ok"
	exit 0
}

usage()
{
	echo "Usage: $prog { --check | --repair }" >&2
	exit 1
}

result()
{
	local status="$1"
	python3 -c 'import sys, json
message = sys.stdin.read()
status = sys.argv[1] if message.strip() else "ok"
print(json.dumps({"status": status, "message": message}))
' "$status"
}

is_remote_db_feature_enabled()
{
	[ -s "/etc/psa/private/dsn.ini" ]
}

get_mysql_option_value()
{
	local section="$1"
	local name="$2"
	local default="$3"

	local value="`/usr/bin/my_print_defaults "$section" | sed -n "s/^--$name=//p" | tail -n 1`"
	echo "${value:-$default}"
}

get_mysql_datadir()
{
	get_mysql_option_value mysqld datadir "/var/lib/mysql"
}

find_psa_shadow()
{
	find /etc/psa/.psa.shadow /etc/psa/private/secret_key "$@"
}

find_mysql_datadir()
{
	find "$MYSQL_DATADIR" -maxdepth 0 "$@"
}

find_mysql_dirs()
{
	find "$MYSQL_DATADIR" -mindepth 1 -type d ! \( -name 'lost+found' -o -name 'lost@002bfound' \) "$@"
}

find_mysql_files()
{
	find "$MYSQL_DATADIR" -mindepth 1 -type f \
		! \( -name 'debian-*.flag' -o -name 'mysql_upgrade_info' -o -name 'mariadb_upgrade_info' \) "$@"
}

declare -A PERMS=(
	[find_psa_shadow]="     "   # Use permissions fsmng knows about

	# Ideally permission changes on MySQL files should be done with stopped MySQL, but we don't
	# want the procedure to require downtime, so sacrifice some degree of correctness by skipping this.
	[find_mysql_dirs]="     --perms 0700        --owner mysql --group mysql --filetype directory"
	[find_mysql_files]="    --perms 0660,0600   --owner mysql --group mysql --filetype file"
	[find_mysql_datadir]="  --perms 0755        --owner mysql --group mysql --filetype directory"
)

permissions()
{
	local operation="$1"
	[ "$operation" = "--check-ac" -o "$operation" = "--set-ac" ] || die "Unknown operation: $operation"

	local restorecon_opts=(-iv)
	[ "$operation" = "--set-ac" ] || restorecon_opts+=(-n)

	local rc=0

	for finder in "${!PERMS[@]}"; do
		local perms="${PERMS[$finder]}"
		$finder | "$PRODUCT_ROOT_D/admin/sbin/fsmng" "$operation" - $perms 2>&1 || rc="$?"
	done

	if command -v restorecon >/dev/null 2>&1 && should_check_selinux "$operation"; then
		for finder in "${!PERMS[@]}"; do
			$finder
		done | restorecon -f - "${restorecon_opts[@]}" || rc="$?"
	fi

	return "$rc"
}

should_check_selinux()
{
	local operation="$1"
	local mode="`get_selinux_mode`"

	if [ "$operation" = "--check-ac" -a "$mode" = "Enforcing" ]; then
		# Check only in Enforcing mode
		return 0
	elif [ "$operation" = "--set-ac" -a "$mode" != "Disabled" ]; then
		# Repair in Enforcing or Permissive mode
		return 0
	fi
	return 1
}

get_selinux_mode()
{
	if ! command -v selinuxenabled >/dev/null 2>&1 || ! selinuxenabled; then
		echo "Disabled"
	elif ! command -v getenforce >/dev/null 2>&1; then
		echo "Disabled"
	else
		getenforce || echo "Disabled"
	fi
}

check()
{
	! is_remote_db_feature_enabled || die "Cannot check permissions of MySQL-related files with a remote database."
	permissions --check-ac | result "corrupted"
	exit 0
}

repair()
{
	! is_remote_db_feature_enabled || die "Cannot repair permissions of MySQL-related files with a remote database."
	permissions --set-ac || die "Failed to repair some permissions of MySQL-related files."
	success
}

PRODUCT_ROOT_D="/opt/psa"
MYSQL_DATADIR="`get_mysql_datadir`"

case "$action" in
	--check)
		check
	;;
	--repair)
		repair
	;;
	*)
		usage
	;;
esac

# vim:ft=sh
