#!/bin/sh
#
# pfSense-repo-setup
#
# part of pfSense (https://www.pfsense.org)
# Copyright (c) 2015-2026 Rubicon Communications, LLC (Netgate)
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

usage() {
        /bin/cat << EOD >&2
Usage: $(basename "${0}") [-hU] [<destdir> <conf>]
	-h		- this help text
	-U		- do not update the repository settings

	<destdir> overrides the destination for the pkg.conf file and repos directory
	<conf> overrides system/pkg_repo_conf_path in the config
EOD
}

_pkg() {
	/usr/local/sbin/pkg-static "${@}" 2>/dev/null
	return "${?}"
}

get_default_repo() {
	local _file _repo_path _ver
	local _pkg_repo_files="${PLUS_CERT_BASE}/${PRODUCT}-repo-*.name"

	for _file in ${_pkg_repo_files}; do
		_repo_path="${_file%.name}"
		if [ -f "${_repo_path}.default" ]; then
			echo "${_repo_path}.conf"
			return
		fi
	done
	_ver="$(echo "${PFSENSE_VERSION}" | /usr/bin/cut -d'-' -f1)"
	for _file in ${_pkg_repo_files}; do
		_repo_path="${_file%.name}"
		if [ -f "${_repo_path}.repo-version" ]; then
			if [ "$(/bin/cat "${_repo_path}.repo-version")" = "${_ver}" ]; then
				echo "${_repo_path}.conf"
				return
			fi
		fi
	done
	echo ""
}

get_repo_path() {
	local _repo_name="${1}"

	# If the argument is a repo name, look for the respective repo conf path
	for file in "${PLUS_CERT_BASE}/${PRODUCT}-repo-"*".name"; do
		if [ ! -f "${file}" ]; then
			continue
		fi
		if [ "$(/bin/cat "${file}")" = "${_repo_name}" ]; then
			echo -n "${file%.name}.conf"
			return
		fi
	done

	# No path found, return the given value
	echo -n "${_repo_name}"
	return
}

get_repo_id() {
	local _id _repo_conf

	# If the argument is a repo name, look for its path
	_repo_conf="$(get_repo_path "${1}")"
	_id="$(echo -n "${_repo_conf}" | \
	    /usr/bin/sed 's/.*pfSense-repo-\(.*\).conf/\1/g')"

	echo -n "${_id}"
}

get_repo_name() {
	local _repo_conf _repo_name _repo_path

	# If the argument is a repo name, verify its path
	_repo_conf="$(get_repo_path "${1}")"

	_repo_path="${_repo_conf%.conf}"
	_repo_name="${_repo_path}.name"
	if [ ! -f "${_repo_name}" ]; then
		echo -n ""
		return
	fi
	/bin/cat "${_repo_name}"
}

get_repoc_feat() {
	local _err _feat

	_feat="$("/usr/local/sbin/${PRODUCT}-repoc-static" -f 2>&1)"
	_err="${?}"
	if [ "${_err}" -ne 0 ] || [ -z "${_feat}" ]; then
		echo -n ""
		return "${_err}"
	fi
	echo -n "${_feat}"
	return 0
}

is_ce() {
	[ "${PRODUCT_LABEL}" == "pfSense" ]
}

license_fetch_ca() {
	local _server _url

	_server="https://services.netgate.com"
	_url="${_server}/api/license/cacert"

	/bin/rm -f "${LICENSE_CA_FILE}" 2> /dev/null
	/bin/mkdir -p "${LICENSE_CA_PATH}" 2> /dev/null
	[ ! -x /usr/local/bin/curl ] && return 1
	/usr/local/bin/curl -s "${_url}" > "${LICENSE_CA_FILE}"
	return "${?}"
}

licensed_system() {
	local _diff _fetch_ca _lic_feat _modified _now
	unset _diff _fetch_ca _lic_feat _modified _now

	if is_ce; then
		return 1
	fi
	_lic_feat="$(get_repoc_feat | /usr/bin/grep -c "license")"
	if [ "${?}" -ne 0 ] && [ "${_lic_feat}" != "1" ]; then
		return 1
	fi
	if [ ! -f "${LICENSE_FILE}" ] || [ ! -r "${LICENSE_FILE}" ] ||
	    [ ! -f "${LICENSE_KEY}" ] || [ ! -r "${LICENSE_KEY}" ]; then
		return 1
	fi
	# If license files exists, check the presence of the CA file.
	if [ ! -f "${LICENSE_CA_FILE}" ] ||
	    [ ! -r "${LICENSE_CA_FILE}" ] ||
	    [ ! -s "${LICENSE_CA_FILE}" ]; then
		_fetch_ca="1"
	fi
	if [ -z "${_fetch_ca}" ] &&
	    [ -f "${LICENSE_CA_FILE}" ] &&
	    [ -r "${LICENSE_CA_FILE}" ]; then
		_modified="$(/usr/bin/stat -f "%m" "${LICENSE_CA_FILE}")"
		_now="$(/bin/date "+%s")"
		if [ "${_now}" -gt "${_modified}" ]; then
			_diff="$(( ("${_now}" - "${_modified}") / 3600 ))"
			if [ "${_diff}" -gt "${LICENSE_CA_FILE_EXPIRE}" ]; then
				_fetch_ca="1"
			fi
		fi
	fi
	if [ -n "${_fetch_ca}" ]; then
		if ! license_fetch_ca ; then
			echo "Warning: Cannot fetch the License CA."
			return 1
		fi
	fi
	if [ -f "${LICENSE_FILE}" ] && [ -r "${LICENSE_FILE}" ] &&
	    [ -f "${LICENSE_KEY}" ] && [ -r "${LICENSE_KEY}" ] &&
	    [ -f "${LICENSE_CA_FILE}" ] &&
	    [ -r "${LICENSE_CA_FILE}" ] &&
	    [ -s "${LICENSE_CA_FILE}" ]; then
		return 0
	fi
	return 1
}

validate_repo_conf() {
	local _default _default_file _id _pkg_repo_conf _repo_id _repo_name
	unset _default _default_file _id _pkg_repo_conf _repo_id _repo_name

	_default="$(get_default_repo)"
	_default_file="$(ls -1 "${PLUS_CERT_BASE}"/*.default 2>/dev/null | tail -n 1)"
	_repo_name="$(get_repo_name "${PKG_REPO_CONF_PATH}")"

	_pkg_repo_conf="${PLUS_CERT_BASE}/${PRODUCT}-repo-${_repo_name}.conf"
	if [ ! -f "${_pkg_repo_conf}" ]; then
		_repo_id="$(get_repo_id "${PKG_REPO_CONF_PATH}")"
		_pkg_repo_conf="${PLUS_CERT_BASE}/${PRODUCT}-repo-${_repo_id}.conf"
	fi

	if [ -n "${_default_file}" ] && [ -f "${_default_file}" ]; then
		_default="${_default_file%%.default}.conf"
	fi

	# Use the default config if pkg_repo_conf points to an invalid file, unless
	# invoked with destdir and conf arguments as indicated by a non-null string
	# in $DBDIR. In this case, abort with an error rather than do something
	# unexpected for this mode of operation.
	if [ -z "${_pkg_repo_conf}" ] || [ ! -f "${_pkg_repo_conf}" ]; then
		if [ -n "${DBDIR}" ]; then
			echo "No such file ${_pkg_repo_conf}"
			exit 1
		fi
		_pkg_repo_conf="${_default}"
	fi

	if [ -f "${_pkg_repo_conf}" ] && [ -r "${_pkg_repo_conf}" ]; then
		if [ -e "${PFSENSE_REPO_CONF}" ] && [ ! -L "${PFSENSE_REPO_CONF}" ]; then
			/bin/rm -f "${PFSENSE_REPO_CONF}"
		fi
		if [ ! -L "${PFSENSE_REPO_CONF}" ] || \
		    [ "$(readlink ${PFSENSE_REPO_CONF})" != "${_pkg_repo_conf}" ]; then
			/bin/mkdir -p "$(dirname "${PFSENSE_REPO_CONF}")"
			/bin/ln -sf "${_pkg_repo_conf}" "${PFSENSE_REPO_CONF}"
		fi
		export PKG_REPO_CONF_PATH="${_pkg_repo_conf}"
	fi
}

abi_setup() {
	local _arch _cur_abi _cur_altabi _err _freebsd_version _new_pkg
	local _pkg_abi _pkg_ver _repo_conf_file
	unset _arch _cur_abi _cur_altabi _err _freebsd_version _new_pkg
	unset _pkg_abi _pkg_ver _repo_conf_file

	_arch="$(uname -p)"
	_freebsd_version="$(uname -r)"
	_repo_conf_file="$(readlink "${PFSENSE_REPO_CONF}")"

	_cur_abi="FreeBSD:${_freebsd_version%%.*}:${_arch}"
	_cur_altabi="freebsd:${_freebsd_version%%.*}"

	case "${_arch}" in
	"aarch64")
		_cur_altabi="${_cur_altabi}:${_arch}:64"
		;;
	"amd64")
		_cur_altabi="${_cur_altabi}:x86:64"
		;;
	"armv6")
		_cur_altabi="${_cur_altabi}:${_arch}:32:el:eabi:hardfp"
		;;
	"armv7")
		_cur_altabi="${_cur_altabi}:${_arch}:32:el:eabi:softfp"
		;;
	*)
		return 1
		;;
	esac

	if [ -f ${_repo_conf_file%%.conf}.abi ]; then
		ABI="$(/bin/cat "${_repo_conf_file%%.conf}.abi")"
	else
		ABI="${_cur_abi}"
	fi

	if [ -f ${_repo_conf_file%%.conf}.altabi ]; then
		ALTABI="$(/bin/cat "${_repo_conf_file%%.conf}.altabi")"
	else
		ALTABI="${_cur_altabi}"
	fi

	OSVERSION="$(echo "${ABI}" | cut -f2 -d:)00000"
	OSVERSION_FEAT="$(get_repoc_feat | /usr/bin/grep -c "osversion")"
	[ "${?}" -eq 0 ] && [ "${OSVERSION_FEAT}" = "1" ] && \
	    [ -r "${_repo_conf_file%%.conf}.osversion" ] && \
	    OSVERSION="$(/bin/cat "${_repo_conf_file%%.conf}.osversion")"

	/bin/rm -f "${PKG_CONF}" 2> /dev/null
	/usr/bin/touch "${PKG_CONF}" 2> /dev/null

	#
	# Make sure pkg.conf is set properly so GUI can work.
	# Do not set the ALTABI for the new pkg (>= 2.0.6).
        # Use ABI and OSVERSION instead.
	#
	_pkg_ver="$(_pkg query %v pkg)"
	_new_pkg="$(_pkg version -t 2.0.6 "${_pkg_ver}")"
	if [ -n "${_new_pkg}" ] && [ "${_new_pkg}" = ">" ]; then
		/bin/cat << EOF > "${PKG_CONF}"
ABI=${ABI}
ALTABI=${ALTABI}
EOF
	else
		/bin/cat << EOF > "${PKG_CONF}"
ABI=${ABI}
OSVERSION=${OSVERSION}
EOF
	fi

	if [ -n "${DBDIR}" ] && [ -n "${REPOSDIR}" ]; then
		/bin/cat << EOF >> ${PKG_CONF}""
PKG_DBDIR=${DBDIR}
REPOS_DIR=[${REPOSDIR}]
EOF
	fi

	AUTH_CA="/etc/ssl/netgate-ca.pem"
	REPO_NAME=$(get_repo_name "${PKG_REPO_CONF_PATH}")
	PLUS_REPO_CONF="${PLUS_CERT_BASE}/${PRODUCT}-repo-${REPO_NAME}.conf"
	PLUS_CERT="${PLUS_CERT_BASE}/${PRODUCT}-repo-${REPO_NAME}-cert.pem"
	PLUS_KEY="${PLUS_CERT_BASE}/${PRODUCT}-repo-${REPO_NAME}-key.pem"
	if [ ! -f "${PLUS_REPO_CONF}" ]; then
		REPO_ID="$(get_repo_id "${PKG_REPO_CONF_PATH}")"
		PLUS_REPO_CONF="${PLUS_CERT_BASE}/${PRODUCT}-repo-${REPO_ID}.conf"
		PLUS_CERT="${PLUS_CERT_BASE}/${PRODUCT}-repo-${REPO_ID}-cert.pem"
		PLUS_KEY="${PLUS_CERT_BASE}/${PRODUCT}-repo-${REPO_ID}-key.pem"
	fi
	if licensed_system ; then
		AUTH_CA="${LICENSE_CA_FILE}"
		PLUS_KEY="${LICENSE_KEY}"
	fi
	if [ -f "${AUTH_CA}" ] && \
	    [ -f "${PLUS_CERT}" ] && \
	    [ -f "${PLUS_KEY}" ]; then
		/bin/cat << EOF >> "${PKG_CONF}"
PKG_ENV {
	SSL_CA_CERT_FILE=${AUTH_CA}
	SSL_CLIENT_CERT_FILE=${PLUS_CERT}
	SSL_CLIENT_KEY_FILE=${PLUS_KEY}
}
EOF
	fi

	_err="0"
	_pkg_abi="$(_pkg query %q pkg)"
	if [ "${_cur_abi}" != "${_pkg_abi}" ] && [ "${_pkg_abi}" != "${ABI}" ]; then
		# Upgrade pkg
		_err="11"
	fi

	if [ "${_cur_abi}" != "${ABI}" ] && [ "${_cur_abi}" != "${ALTABI}" ]; then
		# Set NEW_MAJOR
		if [ "${_err}" -eq 11 ]; then
			_err="13"
		else
			_err="12"
		fi
	fi

	return "${_err}"
}

pfSense_repo_setup() {
	local _err _fstyle _repoc_args

	/bin/cp "${PFSENSE_REPO_CONF}" "/tmp/${PRODUCT}.conf.copy"

	if [ -z "${NOUPDATE}" ]; then
		_repoc_args=""
		if licensed_system; then
			_repoc_args="${_repoc_args} -Z"
		fi
		_fstyle="$(get_repoc_feat | /usr/bin/grep -c "repodir-new-fstyle")"
		[ "${?}" -eq 0 ] && [ "${_fstyle}" = "1" ] && \
		    _repoc_args="${_repoc_args} -N"
		# Fetch the repository settings.
		if ! "/usr/local/sbin/${PRODUCT}-repoc-static" ${_repoc_args}; then
			echo "failed to update the repository settings!!!"
			/bin/rm -f "/tmp/${PRODUCT}.conf.copy"
			exit 1
		fi
	fi

	# Validate the new settings
	validate_repo_conf

	# Setup the repository.
	abi_setup
	_err="${?}"

	# If conf differs, may need force a pkg update
	if ! cmp -s "${PFSENSE_REPO_CONF}" "/tmp/${PRODUCT}.conf.copy" && \
	    [ "${_err}" -eq 0 ]; then
		_err="14"
	fi
	/bin/rm -f "/tmp/${PRODUCT}.conf.copy"

	return "${_err}"
}


#
# main()
#

PHP="/usr/local/bin/php"
READ_XML_TAG="/usr/local/sbin/read_xml_tag.sh"
LICENSE_PATH="/cf/conf/license"
LICENSE_FILE="${LICENSE_PATH}/license.pem"
LICENSE_KEY="${LICENSE_PATH}/license-key.pem"
LICENSE_PHRASE="${LICENSE_PATH}/license.phrase"
LICENSE_CA_PATH="/usr/local/share/pfSense/ssl"
LICENSE_CA_FILE="${LICENSE_CA_PATH}/netgate-zuul-ca.pem"
LICENSE_CA_FILE_EXPIRE="72"	# Hours
PFSENSE_VERSION="$("${PHP}" -n /usr/local/sbin/read_global_var product_version_string 00.00)"
export PLUS_CERT_BASE="$("${PHP}" -n /usr/local/sbin/read_global_var pkg_repos_path pfSense)"
export PRODUCT="$("${PHP}" -n /usr/local/sbin/read_global_var product_name pfSense)"
export PRODUCT_LABEL="$("${PHP}" -n /usr/local/sbin/read_global_var product_label pfSense)"
export PFSENSE_REPO_CONF="/usr/local/etc/pkg/repos/${PRODUCT}.conf"
export PKG_CONF="/usr/local/etc/pkg.conf"
PKG_REPO_CONF_PATH="$("${READ_XML_TAG}" string system/pkg_repo_conf_path)"
# If the value is a repo name, get its path
export PKG_REPO_CONF_PATH="$(get_repo_path "${PKG_REPO_CONF_PATH}")"

# If no branch is saved on the XML configuration, use the default repo.
if [ -z "${PKG_REPO_CONF_PATH}" ]; then
	export PKG_REPO_CONF_PATH="$(get_default_repo)"
fi

unset NOUPDATE
while getopts "hU" OPT; do
	case "${OPT}" in
	"h")
		usage
		exit 0
		;;
	"U")
		NOUPDATE="1"
		;;
	*)
		usage
		exit 1
		;;
	esac
done

shift "$(("${OPTIND}" - 1))"
if [ "${#}" != 0 ]; then
	if [ "${#}" != 2 ]; then
		usage
		exit 1
	fi
	DBDIR="${1}/db"
	REPOSDIR="${1}/repos"
	export PKG_CONF="${1}/pkg.conf"
	export PFSENSE_REPO_CONF="${REPOSDIR}/${PRODUCT}.conf"
	export PKG_REPO_CONF_PATH="${2}"
fi

#
# Validate the actual repo setup.
# Make sure it points to a valid file or fallback to the default.
#
validate_repo_conf

if [ -z "${NOUPDATE}" ] && ! is_ce && ! licensed_system ; then
	# Try to register the system.
	LICENSE_FEAT="$(get_repoc_feat | /usr/bin/grep -c "license")"
	if [ "${?}" -eq 0 ] && [ "${LICENSE_FEAT}" = "1" ]; then
		if [ ! -d "$(dirname "${LICENSE_PHRASE}")" ]; then
			/bin/rm -rf "$(dirname "${LICENSE_PHRASE}")" 2> /dev/null
			/bin/mkdir -p "$(dirname "${LICENSE_PHRASE}")" 2> /dev/null
		fi
		"/usr/local/sbin/${PRODUCT}-repoc-static" "-L" > "${LICENSE_PHRASE}"
		if [ "${?}" != "98" ]; then
			echo "failed to register the system."
			# fallback to the legacy access.
		fi
	fi
fi

# Fetch the repository data and setup the repository access.
pfSense_repo_setup
err="${?}"

# Exit codes:
# 0 = okay
# 1 = error
# 11 = update pkg
# 12 = new major
# 13 = update pkg + new major
# 14 = conf changed, check pkg version
exit "${err}"
