#!/bin/bash
#
# VERSION=5
# CHANGES="Harden provisioning polling: avoid */N cron, gate by minutes, disable cron when off, and remove cron after once."

export PATH=${PATH}:/sbin/:/bin:/usr/sbin:/usr/bin:/usr/fallback

BEROCONF=/usr/fallback/beroconf
PROV_LAST_RUN_FILE=/usr/conf/provisioning_last_run

function log () {
	echo "[init_provisioning] ${1}"
}

function cron_run () {
	# Polling is implemented as "run at most once every N minutes", where N comes
	# from beroconf. We intentionally do not put the user value into the cron
	# expression (e.g. */N), because cron step semantics are surprising for large
	# values and it is easy to misconfigure.
	provisioning_mode=$(${BEROCONF} get root provisioning_mode | grep -v failed)
	if [ "${provisioning_mode}" = "off" ]; then
		exit 0
	fi

	polling_interval=$(${BEROCONF} get root polling_interval | grep -v failed)

	# disabled (unset or "0")
	if [ -z "${polling_interval}" ] || [ "${polling_interval}" = "0" ]; then
		exit 0
	fi

	# harden: allow only digits
	if ! echo "${polling_interval}" | grep -Eq '^[0-9]+$'; then
		log "Invalid polling_interval='${polling_interval}'. Skipping."
		exit 0
	fi

	now_sec=$(date +%s)
	now_min=$((now_sec / 60))
	last=0
	if [ -f "${PROV_LAST_RUN_FILE}" ]; then
		last=$(cat "${PROV_LAST_RUN_FILE}" 2>/dev/null)
	fi
	if ! echo "${last}" | grep -Eq '^[0-9]+$'; then
		last=0
	fi

	# Backward compatibility: older versions may have stored seconds.
	if [ "${last}" -gt 1000000000 ]; then
		last=$((last / 60))
	fi

	interval_min=${polling_interval}
	if [ "${interval_min}" -le 0 ]; then
		exit 0
	fi
	# For "once" we want the next cron tick to run immediately (do not delay by interval).
	if [ "${provisioning_mode}" != "once" ] && [ "${last}" -gt 0 ] && [ $((now_min - last)) -lt "${interval_min}" ]; then
		exit 0
	fi

	# Mark attempt time before running to avoid log/traffic spam on repeated failures.
	# Store minutes (not seconds) to prevent drift (e.g. 3 -> looking like 4 minutes).
	echo "${now_min}" > "${PROV_LAST_RUN_FILE}"

	/usr/bin/env -i bash -c "/usr/php/provisioningTool.php on-run" > /dev/null
	ret=${?}

	# If provisioning was run in "once" mode, the tool switches it back to "off".
	# Remove the cron entry immediately to avoid keeping a pointless 1/min wakeup.
	if [ "${provisioning_mode}" = "once" ]; then
		setup_cron
	fi

	exit ${ret}
}

function setup_cron () {
	# if provisioning is disabled, do not keep a polling cron entry around
	provisioning_mode=$(${BEROCONF} get root provisioning_mode | grep -v failed)

	# get the polling interval
	polling_interval=$(${BEROCONF} get root polling_interval | grep -v failed)

	# always clean old provisioning cron entries (legacy and new wrapper)
	/usr/bin/crontab -l \
		| grep -v provisioningTool.php \
		| grep -v "S45provisioning cron_run" \
		> /tmp/prov_cron.tmp

	# if it is not set (or is 0), or provisioning is off, we do not add crontab-entry
	if [ -z "${polling_interval}" ] || [ "${polling_interval}" = "0" ] || [ "${provisioning_mode}" = "off" ]; then
		/usr/bin/crontab /tmp/prov_cron.tmp
		rm -f /tmp/prov_cron.tmp
		return
	fi

	log "Adding crontab-entry for provisioning polling (every minute; interval=${polling_interval} minutes)."

	# Fixed schedule, interval handled in cron_run()
	echo "* * * * * /usr/bin/env -i bash -c \"/etc/init.d/S45provisioning cron_run\"" >> /tmp/prov_cron.tmp
	/usr/bin/crontab /tmp/prov_cron.tmp
	rm -f /tmp/prov_cron.tmp
}

function start_prov () {
	log "Running BootUp-Provisioning."
	/usr/bin/env -i bash -c "/usr/php/provisioningTool.php on-boot" > /dev/null
}

# do not start if this card needs to be produced
if [ "$(${BEROCONF} get root need_prod | grep -v failed)" = "1" ]; then
	exit 0
fi

# do not start if this card is in recovery-mode
if [ "$(${BEROCONF} get root boot_recoverymode | grep -v failed)" = "1" ]; then
	# only if TLSv1.2 works
	if [ "$(${BEROCONF} get root TLSv1.2-disabled | grep -v failed)" = "" ]; then
		log "Recovery-mode. exit"
		exit 0
	fi
	# otherwise: try to recover device using provisioning
fi

# do not start of this card is factory-resetted
if [ -f /sys/class/beronet/gateway/resetjumper ] && [ "$(cat /sys/class/beronet/gateway/resetjumper)" = "1" ]; then
	exit 0
fi

# do not start if this card is in update-mode
if [ "$(${BEROCONF} get root boot_fwupdate | grep -v failed)" = "1" ]; then
	exit 0
fi

	case "${1}" in
		start)
			setup_cron
			start_prov
			# If provisioning_mode was "once", provisioningTool flips it back to "off".
			# Resync cron after the run.
			setup_cron
			if [ -f /tmp/reboot_after_update ]; then
				/sbin/reboot
			fi

			;;
		cron_run)
			cron_run
			;;
		cron_setup)
			setup_cron
			;;
		recover)
		sleep 3600
		touch /tmp/recover.device
		log "Running Recover-Provisioning."
		/usr/bin/env -i bash -c "/usr/php/provisioningTool.php firmware" > /dev/null
		;;
	*)
		echo "Usage: ${0} [start|cron_setup|recover]" >&2
		exit 1
		;;
esac
