#!/bin/sh -e
#
# (C) Copyright 2015-2018 Axis Communications AB, LUND, SWEDEN

# Error codes
# The error codes start at 20 because the add-on CGI wants to be able to return
# the error code and message directly from the scripts if an error occurs
# without having conflicting error codes.

######################## Install/uninstall 20 - 39 #############################
ERR_INSTALL_ERROR=20 # Installation Error
ERR_FILE_SYSTEM=21 # File system operation failed
ERR_INSTALL_OPKG=22 # Failed to install the add-on opk
ERR_UNINSTALL_ERROR=23 # Uninstall Error
ERR_INSTALL_ADDON_USER=24 # Failed create or modify addon user
ERR_PREINSTALL_SKIPPED=25 # Skipped to pre-install the add-on
ERR_CREATE_FCGI=26 # Failed to create fcgi handler or fcgi access file
ERR_CREATE_URL=27 # Failed to create url access file

######################## Package content 40 - 59 ###############################
ERR_FILE_FORMAT=40 # Invalid add-on package file format
ERR_INVALID_CONTENT=41 # Invalid content in the add-on opk
ERR_NO_SIGNATURE=42 # Add-on package does not contain a signature
ERR_BAD_SIGNATURE=43 # The signature in the add-on package is invalid
ERR_MANIFEST=44 # Invalid manifest
ERR_NO_LICENSE=45 # Add-on manifest does not contain a license
ERR_NO_PACKAGE_CONFIG=46 # Add-On package does not contain a package config
ERR_INVALID_PACKAGE_CONFIG=47 # Invalid package config
ERR_UNSUPPORTED_SIGNATURE_FORMAT=48 # Unsupported signature format
ERR_UNSUPPORTED_PACKAGE_FORMAT=49 # Unsupported package format
ERR_INVALID_PKG_NAME=50 # Invalid package name

########################### Requirements 60 - 69 ###############################
ERR_INVALID_REQUIREMENT=60 # Invalid requirement in the add-on manifest
ERR_API_GRANT_FAILED=61 # Failed to grant access to API
ERR_ARCHITECTURE=62 # Incompatible architecture specified in add-on manifest

########################### Status 70 - 79 #####################################
ERR_ADDON_NOT_FOUND=70 # Add-on not found
ERR_ADDON_SERVICE=71 # Add-on service error
# Value 72 is already in use by the addonmanager

############################ Downgrade 80 - 99 #################################
ERR_LOWER_VERSION=80 # Failed to install add-on with lower version than the
################################################################################


COMMON_LOG_TAG="addon-common"

# Developer mode config
DEVMODE_CONF_FILE="/etc/addonmanagerconf/devmode.conf"
DEVMODE_CONF_GROUP="[Developer mode]"
DEVMODE_CONF_ALLOWED_APIS_KEY="allowed_apis"
DEVMODE_ALLOW_ALL_VALUE='*'

fail_errorcode() {
	# Write message to stdout and exit with the specified exit status
	#
	# Arguments:
	#	$1 Exit status
	#	$.. Zero or more strings to write to stdout

	local errorcode=$1
	shift
	echo "$@"
	exit $errorcode
}

_syslog_msg() {
	# Writes the specified message and tag to syslog
	#
	# Arguments:
	# $1 Severity
	# $2 Log tag
	# $.. Zero or more strings to write to the Syslog

	local logtag
	local severity

	if [ $# -ge 3 ]; then
		severity="$1"
		logtag="$2"
		shift 2
	else
		severity="error"
		logtag="addon-util"
	fi

	logger -t "$logtag" -p "$severity" "$@"
}

syslog_notice() {
	# Writes the specified notice message and tag to the Syslog
	#
	# Arguments:
	#	$1 Log tag
	#	$.. Zero or more strings to write to the Syslog

	_syslog_msg notice "$@"
}

syslog_warn() {
	# Writes the specified warning message and tag to the Syslog
	#
	# Arguments:
	#	$1 Log tag
	#	$.. Zero or more strings to write to the Syslog

	_syslog_msg warning "$@"
}

syslog_err() {
	# Writes the specified error message and tag to the Syslog
	#
	# Arguments:
	#	$1 Log tag
	#	$.. Zero or more strings to write to the Syslog

	_syslog_msg error "$@"
}

# /lib/rcscripts/sh/files.sh and /lib/rcscripts/sh/string.sh both
# sources /lib/rcscripts/sh/error.sh, which defines an error() that
# logs and exits, unless there already is a function error() defined.
# Define error() to avoid to exit at fsynced_write_or_cleanup() failures.
# For consistancy, replace all of the logging functions in error.sh.
error() {
	syslog_err "$@"
}
warning() {
	syslog_warn "$@"
}
information() {
	syslog_notice "$@"
}

. /lib/rcscripts/sh/string.sh
. /lib/rcscripts/sh/files.sh

taint_pre_install_finalize_file() {
	# Create the finalize file if it not already exist
	# It will be checked by the addon-preinstaller-finalizer service

	local tmp_addon_dir="/tmp/addon"
	local finalize_file="$tmp_addon_dir/finalize"

	if [ ! -d $tmp_addon_dir ]; then
		mkdir -p $tmp_addon_dir
	fi
	if [ ! -f $finalize_file ]; then
		touch $finalize_file
	fi
}

OPKG_CONF=/etc/opkg/addon/addon_opkg.conf

[ -f $OPKG_CONF ] || {
	syslog_err $COMMON_LOG_TAG "addon-common: No opkg config file"
	exit 1
}

OPKG="opkg -f $OPKG_CONF"

# Get addon destination directory from the opkg config
OPKG_DEST=$(sed -n 's/^dest[[:blank:]]root[[:blank:]]\(.*\)/\1/p' $OPKG_CONF)

# Addon lock file directory
LOCK_PATH=$OPKG_DEST/.locks

SERVICE_PATH=/etc/systemd/system

WEB_ROOT=/etc/httpd/html/

MANIFEST_PATH=/opt/MANIFEST

UNSIGNED_ADDON_PATH=/etc/opkg/addon

ALIAS_CONF_DIR=/etc/apache2/conf.d/alias

REWRITES_CONF_DIR=/etc/apache2/conf.d/rewrites

ADDON_MAIN_REWRITE_FILE=40_addon_main.conf

WEB_DOCUMENT_ROOT=/usr/html

# Boolean values
TRUE=1
FALSE=0

# Flock lock files
LOCKFILE=/var/lock/addon.lock
DEVMODELOCKFILE=/var/lock/addonmanagerconf/devmode.lock
# Flock lock file descriptor. File descriptor 200 and 201 was chosen because its
# unlikely that any of the scripts will use a file descriptor with such a
# high number.
LOCKFD=200
DEVMODELOCKFD=201

# Blacklist file where addons that shall be prevented from being installed upon
# pre-installation will be stored.
PREINSTALL_BLACKLIST_FILE="/etc/addon/preinstall-blacklist"
# Flock lock file
PREINSTALL_BLACKLIST_LOCK_FILE=$PREINSTALL_BLACKLIST_FILE.lock
# Flock lock file file descriptor
PREINSTALL_BLACKLIST_LOCKFD=202

release_lock() {
	# Releases the acquired addon lock
	flock -u $LOCKFD
	# Clear trap actions as we do not need them any more
	trap - INT QUIT TERM EXIT
}

acquire_lock_or_wait() {
	# Acquires an exclusive addon write lock or waits until the lock can be
	# acquired

	# Open file descriptor to lock file
	eval "exec $LOCKFD>\"$LOCKFILE\""
	# Acquire exclusive addon lock or wait until we can acquire it
	flock -x $LOCKFD || {
		syslog_err $COMMON_LOG_TAG 			"acquire_lock_or_wait: Failed to acquire an exclusive lock $LOCKFD"
		return 1
	}
	# Clean up the lock if the script is interrupted
	trap release_lock INT QUIT TERM EXIT
}

release_devmode_lock() {
	# Releases the acquired addon lock
	flock -u $DEVMODELOCKFD
	# Clear trap actions as we do not need them any more
	trap - INT QUIT TERM EXIT
}

acquire_devmode_lock_or_wait() {
	# Acquires an exclusive devmode write lock or waits until the lock can be
	# acquired

	# Open file descriptor to lock file
	eval "exec $DEVMODELOCKFD>\"$DEVMODELOCKFILE\""
	# Acquire exclusive devmode lock or wait until we can acquire it
	flock -x $DEVMODELOCKFD || {
		syslog_err $COMMON_LOG_TAG 			"acquire_devmode_lock_or_wait: Failed to acquire an exclusive lock $DEVMODELOCKFD"
		return 1
	}
	# Clean up the lock if the script is interrupted
	trap release_devmode_lock INT QUIT TERM EXIT
}

save_running_state() {
	# Utility function to save the running state of the add-on.
	# Should be called whenever the add-on is deliberately started or stopped.
	#
	# Arguments:
	#	$1 Add-on name
	#
	[ $1 ] || {
		syslog_err $COMMON_LOG_TAG 			"Save running state: Empty add-on name given"
		return 1
	}
	[ -d /opt/$1 ] || revert_and_fail $ERR_INSTALL_ERROR 		"Package $1 is installed but the folder does not exist"
	systemctl is-active $1 > /opt/$1/saved_states.tmp || :
	fsynced_write_or_cleanup /opt/$1/saved_states.tmp /opt/$1/saved_states
}

restore_running_state() {
	# Utility function to restore the state of the add-on based on the saved running state.
	# Should be called after an add-on upgrade.
	# Should be called with 'autostart' after restore at firmware upgrade.
	#
	# Arguments:
	#	$1 Add-on name
	#	$2 File: Optional: Write name to given file to handle start later
	#
	local num_words

	[ $# -eq 1 ] || [ $# -eq 2 ] || {
		syslog_err $COMMON_LOG_TAG 			"Restore running state: Invalid number of arguments"
		return 1
	}
	[ $1 ] || {
		syslog_err $COMMON_LOG_TAG 			"Restore running state: Empty add-on name given"
		return 1
	}
	[ -d /opt/$1 ] || revert_and_fail $ERR_ADDON_NOT_FOUND 		"No installation folder for package $1"

	if [ -f /opt/$1/saved_states ]; then
		num_words=$(wc -w < /opt/$1/saved_states)

		if [ $num_words -ne 1 ]; then
			syslog_err $COMMON_LOG_TAG 				"Restore running state: " 				"Wrong format of saved_states file, " 				"skipping restore running states"
			return 1
		fi
		read old_state < /opt/$1/saved_states
		case $old_state in
			active)
				if [ $2 ]; then
					# This add the addon to the list of add-ons to be started
					# by the autostart service
					echo $1 > $2.tmp || syslog_warn $COMMON_LOG_TAG 									"Restore running state: Failed to write to $2.tmp"
					[ ! -f $2 ] || {
						cat $2 >> $2.tmp || syslog_warn $COMMON_LOG_TAG 										"Restore running state: Failed to append to $2.tmp"
					}
					fsynced_write_or_cleanup "$2.tmp" "$2"
					syslog_notice $COMMON_LOG_TAG 						      "Restore running state: Remember to start $1"
				else
					# This sets the state of the add-on to the last known state
					# as described in the saved_state file. Therefore it is
					# fine to call systemctl start directly here.
					syslog_notice $COMMON_LOG_TAG "Restore running state: Starting add-on $1"
					addon-systemctl start $1 > /dev/null 2>&1 || {
						syslog_err $COMMON_LOG_TAG 							"Restore running state: Start of $1 failed"
						return 1
					}
				fi
				;
			inactive)
				syslog_notice $COMMON_LOG_TAG 					"Restore running state: Keeping add-on $1 inactive"
				;
			*)
				syslog_warn $COMMON_LOG_TAG 					"Restore running state: $old_state unkown, skipping $1"
				;
		esac
	else
		syslog_notice $COMMON_LOG_TAG 			"Restore running state: No state to restore for $1"
	fi
}

is_free_from_prepost_scripts() {
	# Checks if the OPK file contains pre/post installation/remove scripts.
	#
	# Arguments:
	#	$1 Add-on OPK file
	#	$2 Variable to put the result (TRUE/FALSE)
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	# Prefixing variable with underscore to prevent variables with the same name
	# when doing the indirect assignment with eval.
	local _result

	[ $# -eq 2 ] || {
		syslog_err $COMMON_LOG_TAG 			"is_free_from_prepost_scripts: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_free_from_prepost_scripts: Invalid add-on OPK file"
		return 1
	}

	[ "$2" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_free_from_prepost_scripts: Invalid result variable"
		return 1
	}

	ar -p $1 control* | tar -zt | egrep "pre|post"

	# If egrep's exit status is zero it found one or both scripts and shall
	# return a false value.
	if [ $? -eq 0 ]; then
		_result=$FALSE
	else
		_result=$TRUE
	fi

	eval $2=\$_result
}

is_package_name_valid() {
	# Checks if the specified package name is valid. The package name may only
	# contain the characters [a-z0-9.+-].
	#
	# Arguments:
	#	$1 package name
	#	$2 Variable to put the result (TRUE/FALSE)
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	# Prefixing variable with underscore to prevent variables with the same name
	# when doing the indirect assignment with eval.
	local _result

	[ $# -eq 2 ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_name_valid: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_name_valid: Invalid package name"
		return 1
	}

	[ "$2" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_name_valid: Invalid result variable"
		return 1
	}

	# Check if the package name contains any unexpected characters
	if echo "$1" | grep -q -v '[^a-z0-9.+-]'; then
		_result=$TRUE
	else
		_result=$FALSE
	fi

	eval $2=\$_result
}

_lock_preinstall_blacklist() {
	# Acquires an exclusive write lock on the preinstall blacklist or waits
	# until the lock can be acquired
	trap _unlock_preinstall_blacklist INT QUIT TERM EXIT
	# Open file descriptor to lock file
	eval "exec $PREINSTALL_BLACKLIST_LOCKFD>\"$PREINSTALL_BLACKLIST_LOCK_FILE\""
	flock $PREINSTALL_BLACKLIST_LOCKFD
}

_unlock_preinstall_blacklist() {
	# Release the aquired preinstall blacklist lock
	flock -u $PREINSTALL_BLACKLIST_LOCKFD
	rm $PREINSTALL_BLACKLIST_LOCK_FILE
	trap - INT QUIT TERM EXIT
}

append_to_preinstall_blacklist() {
	# Insert the package to the blacklist if it not already present in it
	#
	# Arguments:
	#	$1 package name
	#
	# Exit status:
	# 0: Always

	local tmp_file=$PREINSTALL_BLACKLIST_FILE.tmp

	[ $# -eq 1 ] || {
		syslog_err $COMMON_LOG_TAG 			"append_to_preinstall_blacklist: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"append_to_preinstall_blacklist: Invalid package name"
		return 1
	}

	# Lock the blacklist, wait for lock if already locked
	_lock_preinstall_blacklist
	# Read the blacklist and search for $1
	grep -q "$1" $PREINSTALL_BLACKLIST_FILE 2>/dev/null || {
		rm -f $tmp_file
		[ -f $PREINSTALL_BLACKLIST_FILE ] &&
			cat $PREINSTALL_BLACKLIST_FILE >$tmp_file
		echo "$1" >>$tmp_file
		fsynced_write_or_cleanup $tmp_file $PREINSTALL_BLACKLIST_FILE
	}

	# Release blacklist lock
	_unlock_preinstall_blacklist
}

is_package_preinstall_blacklisted() {
	# Checks if the package with the specified name is blacklisted for being
	# pre-installed.
	#
	# Arguments:
	#	$1 package name
	#	$2 Variable to put the result (TRUE/FALSE)
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	# Prefixing variable with underscore to prevent variables with the same name
	# when doing the indirect assignment with eval.
	local _result

	[ $# -eq 2 ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_preinstall_blacklisted: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_preinstall_blacklisted: Invalid package name"
		return 1
	}

	[ "$2" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_preinstall_blacklisted: Invalid result variable"
		return 1
	}

	# Lock the blacklist, wait for lock if already locked
	_lock_preinstall_blacklist
	# Read the blacklist and search for $1
	grep -q "$1" $PREINSTALL_BLACKLIST_FILE 2>/dev/null &&
		_result=$TRUE ||
		_result=$FALSE

	eval $2=\$_result

	# Release blacklist lock
	_unlock_preinstall_blacklist
}

is_package_installed() {
	# Checks if the package with the specified name is installed
	#
	# Arguments:
	#	$1 package name
	#	$2 Variable to put the result (TRUE/FALSE)
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	# Prefixing variable with underscore to prevent variables with the same name
	# when doing the indirect assignment with eval.
	local _result

	[ $# -eq 2 ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_installed: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_installed: Invalid package name"
		return 1
	}

	[ "$2" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_package_installed: Invalid result variable"
		return 1
	}

	OPKGSTATUS=$($OPKG status $1) || {
		syslog_err $COMMON_LOG_TAG 			"is_package_installed: Cannot get opkg status for $1"
		return 1
	}

	# If the OPKGSTATUS is empty the package is not installed
	if [ -z "$OPKGSTATUS" ]; then
		_result=$FALSE
	else
		_result=$TRUE
	fi

	eval $2=\$_result
}

get_devmode_allowed_apis() {
	# Get developer mode allowed APIs
	#
	# Return: A list of devmode allowed APIs formatted on the
	#	  g_key_file_string_list value format "value1;value2;value3;" if
	#	  enabled. Special case is if all APIs are allowed then
	#	  DEVMODE_ALLOW_ALL_VALUE is returned.
	#	  None if devmode is disabled.

	local _allowed_apis

	acquire_devmode_lock_or_wait

	_allowed_apis=$(addon-gkeyfileparser "$DEVMODE_CONF_FILE" 					"$DEVMODE_CONF_GROUP" "$DEVMODE_CONF_ALLOWED_APIS_KEY") 					2>/dev/null || :

	echo $_allowed_apis

	release_devmode_lock
}

is_api_allowed() {
	# Checks if the API is a subset of the allowed APIs.
	#
	# Arguments:
	#	$1 List of devmode allowed APIs formatted on the g_key_file_string_list
	#	   value format "value1;value2;value3;"
	#	$2 API to be checked
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local _api

	[ $# -eq 2 ] || {
		syslog_err $COMMON_LOG_TAG 			"is_api_allowed: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_api_allowed: Invalid list of allowed apis"
		return 1
	}

	[ "$2" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_api_allowed: Invalid api"
		return 1
	}
	# Add semicolon as it is used as delimiter in the list of allowed apis.
	# It also protects us from a substring match of an api in the list.
	_api="$2;"

	# If $_api is a substring of $1 then the function returns 0, otherwise 1
	case "$1" in
		*"$_api"*)
			return 0
			;
		*)
			return 1
			;
	esac
}

is_api_available() {
	# Checks if the API exist by calling the addon-registry libwrapper.
	# The library will go through the registry files found under the
	# addon path and if any of the files have a matching service_name
	# with the api_name. A check will be preformed to see if major and
	# minor version matches.
	#
	# Arguments:
	#	$1 D-Bus name
	#	$2 Minor version name
	#	$3 Variable to put the result (TRUE/FALSE)
	#	$4 Variable in which to put the name of the user running the API
	#	   this might or might not get a value depending on whether the
	#	   API has specified a user or not.
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	# Prefixing variable with underscore to prevent variables with the same name
	# when doing the indirect assignment with eval.
	local _result _user major
	# The Major part of the version code has been removed for API's, since the
	# service registry is requiring Major the decision was to internally default
	# the Major to 1.
	major=1

	[ $# -eq 4 ] || {
		syslog_err $COMMON_LOG_TAG 			"is_api_available: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_api_available: Invalid D-Bus name"
		return 1
	}

	[ "$2" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_api_available: Invalid Minor version"
		return 1
	}

	[ "$3" ] || {
		syslog_err $COMMON_LOG_TAG 			"is_api_available: Invalid result variable"
		return 1
	}

	# If addon-registry exit code is zero it found the API
	if _user=$(addon-registry $1 $major $2); then
		_result=$TRUE
	else
		_result=$FALSE
	fi

	eval $3=\$_result
	eval $4=\$_user
}

get_package_name() {
	# Get the package name from the specified OPK filename or path
	#
	# Arguments:
	#	$1 Add-on OPK filename or path.
	#
	# Echo:
	#	Package name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local filename

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"get_package_name: Invalid add-on OPK filename"
		return 1
	}

	# Get basename
	filename=${1##*/}
	# Trim any file extensions
	filename=${filename%%.*}
	# Trim anything after and including underscore
	echo "${filename%%_*}"
}

get_package_version() {
	# Get the version from the add-on manifest.
	#
	# Arguments:
	#	$1 The add-on manifest file to extract the version from.
	#	$2 Variable to put the version in.
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise
	#
	# Prefixing variable with underscore to prevent variables with the same name
	# when doing the indirect assignment with eval.


	[ $# -eq 2 ] && [ "$1" ] && validate_varname $2 || {
		syslog_err $COMMON_LOG_TAG 			"get_package_version: Incorrect usage of the parameters."
		return 1
	}

	local _version

	# Check if version exists
	_version=$(xmllint --xpath 'string(//PackageInfo/Version)' $1) ||
		revert_and_fail $ERR_INSTALL_ERROR "Internal Error when parsing manifest"

	[ "$_version" ] || {

		syslog_err $COMMON_LOG_TAG 			"get_package_version: No version in add-on manifest"
		return 1
	}

	eval $2=\$_version
}

get_manifest_version() {
	# Get the major manifest version number from the add-on manifest.
	#
	# Arguments:
	#	$1 The add-on manifest file to extract the major manifest version from.
	#	$2 Variable to put the manifest version in.
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise
	#
	# Prefixing variable with underscore to prevent variables with the same name
	# when doing the indirect assignment with eval.

	[ $# -eq 2 ] && [ "$1" ] && validate_varname $2 || {
		syslog_err $COMMON_LOG_TAG 			"get_manifest_version: Incorrect usage of the parameters."
		return 1
	}

	local _major_version

	# Check if manifest version exists
	_major_version=$(xmllint --xpath 'string(//Manifest/@Major)' $1) ||
		revert_and_fail $ERR_INSTALL_ERROR "Internal Error when parsing manifest"

	[ "$_major_version" ] || {
		syslog_err $COMMON_LOG_TAG 			"get_manifest_version: No major manifest version in add-on manifest"
		return 1
	}

	eval $2=\$_major_version
}

get_installed_version() {
	# Get the version of an installed add-on
	#
	# Arguments:
	#	$1 Add-on name
	#
	# Echo:
	#	Add-on version
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local version

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"get_installed_version: Invalid add-on name"
		return 1
	}

	version=$($OPKG info $1 | sed -n 's/Version:[[:blank:]]*\(.*\)/\1/p') || {
		syslog_err $COMMON_LOG_TAG 			"get_installed_version: Failed to get version for add-on $1"
		return 1
	}

	echo $version
}

create_addon_lock() {
	# Creates a addon lock file with the specified name and files
	# If the addon-clean service find the lock file after boot, it will
	# remove the files written in the lock and remove the addon package
	# with the same name as the lock.
	#
	# Arguments:
	#	$1 package name
	#	$.. zero or more paths to files that should be cleaned up
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	[ $# -ge 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"create_addon_lock: Invalid Add-on package name"
		return 1
	}

	if [ ! -d $LOCK_PATH ]; then
		mkdir -p $LOCK_PATH
	fi

	local lock_file

	lock_file="$LOCK_PATH/$1.lock"
	touch $lock_file || {
		syslog_err $COMMON_LOG_TAG 			"create_addon_lock: Unable to create addon lock for $1"
		return 1
	}

	# Shift to remove the package name argument from the argument list so only
	# the file paths that should be added to the lock file remains
	shift
	if [ $# -gt 0 ]; then
		for file in "$@"; do
			# Get absolute file path so the cleanup service can find the
			# files in the file system.
			readlink -f $file >> $lock_file || :
		done
	fi

	# syncfile will ensure that the lock file is written to permanent storage
	# It will also sync the directory the lock file contains so a separate
	# call for the directory is not necessary.
	syncfile $lock_file || {
		syslog_err $COMMON_LOG_TAG 			"create_addon_lock: Failed to sync $lock_file"
		return 1
	}
}

create_rewrites_symlinks() {
	# Will create symblic link of the add-ons configuration files containing
	# rewrite rules. The links will be available for the HTTPD server.
	# Reload of httpd is not needed, since it is always done after install.
	#
	# $1  package name
	#

	local addon_rewrite_conf_file rewrites_symlink

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"create_rewrite_conf_symlinks: Invalid use of inparameter"
		return 1
	}

	# Handle alternative webroot for the add-on (alias)
	rewrites_symlink=$REWRITES_CONF_DIR/$ADDON_MAIN_REWRITE_FILE
	addon_rewrite_conf_file=$OPKG_DEST/$1/usr/conf/$ADDON_MAIN_REWRITE_FILE
	[ ! -w $REWRITES_CONF_DIR ] || [ ! -f $addon_rewrite_conf_file ] ||  {

		# If the symlink exists then remove it!
		[ ! -f $rewrites_symlink ] ||
			rm -f $rewrites_symlink

		ln -s $addon_rewrite_conf_file $REWRITES_CONF_DIR || syslog_err 			$COMMON_LOG_TAG "Could not create a symlink: $addon_rewrite_conf_file"
	}

	# Handle alternative webroot for the camera (redirect to add-on for /)
	rewrites_symlink=$REWRITES_CONF_DIR/40_addon_webroot_alt_"$1".conf
	addon_rewrite_conf_file=$OPKG_DEST/$1/usr/conf/40_addon_webroot_alt_"$1".conf
	[ ! -w $REWRITES_CONF_DIR ] || [ ! -f $addon_rewrite_conf_file ] ||  {

		# If the symlink exists then remove it!
		[ ! -f $rewrites_symlink ] ||
			rm -f $rewrites_symlink

		ln -s $addon_rewrite_conf_file $REWRITES_CONF_DIR || syslog_err 			$COMMON_LOG_TAG "Could not create a symlink: $addon_rewrite_conf_file"
	}
}

remove_rewrite_conf_symlinks() {
	# Will remove any symblic link of the add-ons configuration files containing
	# rewrite rules. The links will then be unavailable for the HTTPD server.
	# Reload of httpd is not needed, since it is always done after uninstall.
	#
	# $1  package name

	local rewrites_webroot_link addon_rewrite_conf_path link_path file

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"remove_rewrite_conf_symlinks: Invalid use of inparameter"
	}

	addon_rewrite_conf_path=$OPKG_DEST/$1/usr/conf/*

	# Remove all symbolic links
	for file in $addon_rewrite_conf_path; do
		# If it's not a file then continue
		[ -f $file ] || continue

		[ ! -f $REWRITES_CONF_DIR/${file##*/} ] ||
			rm -rf $REWRITES_CONF_DIR/${file##*/}
	done
}

remove_addon_lock() {
	# Removes the addon lock with the specified name.
	#
	# Arguments:
	#	$1 package name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"remove_addon_lock: Invalid Add-on package name"
		return 1
	}

	rm -f "$LOCK_PATH/$1.lock"
}

remove_pkg() {
	# Removes the package with the specified name with opkg
	#
	# Arguments:
	#	$1 package name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local result

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG "remove_pkg: Invalid Add-on package name"
		return 1
	}

	is_package_installed $1 result || {
		syslog_err $COMMON_LOG_TAG 			"remove_pkg: Failed to check if package is installed"
		return 1
	}

	[ $result -eq $TRUE ] || {
		# Package not installed, nothing to do here just return
		return 0
	}

	$OPKG remove $1 > /dev/null 2>&1 || {
		syslog_err $COMMON_LOG_TAG 			"remove_pkg: Failed to remove package $1"
		return 1
	}
}

remove_layer() {
	# Removes the layers of a layered addon.
	#
	# Arguments:
	# 	$1: The layer to remove
	#
	local layer refdir

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG "remove_layer: Invalid input"
		return 1
	}
	layer=$1
	# If the addon has a lower layer
	if [ -f $layer/lower ]; then
		# Read what the lower layer is
		[ -r $layer/lower ] ||
			revert_and_fail $ERR_UNINSTALL_ERROR "lower layer file cannot be read"
		read lowerlayer <$layer/lower
		# Remove reference in the lower layer
		rm -f "$lowerlayer/ref/${layer##*/}"
		# If the lower layer does not have any more references, is should also be removed
		refdir=$lowerlayer/ref/*
		[ "$(echo $refdir)" != "$refdir" ] || remove_layer $lowerlayer
	fi
	rm -r $layer
}

remove_addon_folder() {
	# Removes the addon folder for the specified addon
	#
	# Arguments:
	#	$1 Add-on name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local addon_path

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG "remove_addon_folder: Invalid add-on name"
		return 1
	}

	addon_path=$OPKG_DEST/$1

	if [ -f $addon_path/.layered ]; then
		[ -r $addon_path/.layered ] ||
			revert_and_fail $ERR_UNINSTALL_ERROR ".layered file cannot be read"
		read upperlayer <$addon_path/.layered
		umount $addon_path
		remove_layer $upperlayer
		sed -i "/^overlay \\/opt\\/$1/d" /etc/fstab
		fsync /etc/fstab
	fi

	if [ -d $addon_path ]; then
		rm -rf $addon_path || syslog_err $COMMON_LOG_TAG 			"remove_addon_folder: Failed to remove $addon_path"
	fi
}

remove_addon_web_folder_symlink() {
	# Remove the addon web folder symbolic link for the specified addon
	#
	# Arguments:
	#	$1 add-on name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local addon_web_link_path

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"remove_addon_web_folder_symlink: Invalid add-on name"
		return 1
	}

	addon_web_link_path=$WEB_ROOT$1

	if [ -L "$addon_web_link_path" ]; then
		rm -f $addon_web_link_path ||
			syslog_err $COMMON_LOG_TAG 				"remove_addon_web_folder_symlink: " 				"Failed to remove symlink $addon_web_link_path"
	fi
}

remove_addon_url_alias() {
	# Remove all URL aliases for the specified addon,
	# which requires a reload of Apache HTTPD.
	#
	# Arguments:
	#	$1 add-on name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local alias_conf_file

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"remove_addon_url_alias: Invalid add-on name"
		return 1
	}

	alias_conf_file=$ALIAS_CONF_DIR/$1.conf

	if [ -f "$alias_conf_file" ]; then
		rm $alias_conf_file || syslog_err $COMMON_LOG_TAG 			"remove_addon_url_alias: Failed to remove alias file"
	fi
}

remove_service() {
	# Remove service with the specified name
	#
	# Arguments:
	#	$1 service name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local servicefile dropdownfolder dropdownfile

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG "remove_service: Invalid service name"
		return 1
	}

	servicefile=$SERVICE_PATH/$1.service

	if systemctl -q is-active $1 > /dev/null 2>&; then
		# The service file is being removed due to re-setup or
		# uninstallation,the fact that it is being stopped should
		# not be reflected in the saved_state file and therefore
		# it is correct to use systemctl directly here.
		systemctl stop $1 > /dev/null 2>&1
	fi

	if systemctl is-enabled $1 > /dev/null 2>&; then
		systemctl disable $1 > /dev/null 2>&1
	fi

	systemctl reset-failed $1 > /dev/null 2>&1 || :
	rm -f $servicefile || syslog_err $COMMON_LOG_TAG 		"remove_service: Failed to remove service for $1"

	dropdownfolder=$servicefile.d
	if [ -d  $dropdownfolder ]; then
		dropdownfile=$dropdownfolder/50-axis.conf

		rm -f $dropdownfile
		rmdir $dropdownfolder || syslog_err $COMMON_LOG_TAG 			"remove_service: Failed to remove service dropdown folder for $1"
	fi
}

remove_manifest() {
	# Remove the manifest file for an add-on
	#
	# Arguments:
	#	$1 Add-on name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise


	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"remove_manifest: Invalid add-on name"
		return 1
	}

	rm -f $MANIFEST_PATH/${1}_manifest.xml ||
		syslog_err $COMMON_LOG_TAG 			"remove_manifest: Failed to remove manifest for $1"
}

remove_apac_grant() {
	# Revoke APAC actions granted for the addon user
	#
	# Arguments:
	#	$1 Add-on username
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"remove_apac_grant: Invalid Add-on username"
		return 1
	}

	apac-update revoke-all $1 || {
		syslog_err $COMMON_LOG_TAG 			"remove_apac_grant: Failed to revoke actions for user $1"
		return 1
	}
}

check_remove_unsigned_from_list() {
	# If addon has been installed as an unsigned add-on
	# remove it from the list of unsigned add-ons.
	#
	# Arguments:
	#	$1 Add-on name
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	[ $# -eq 1 ] && [ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"check_remove_unsigned_from_list: Invalid Add-on name"
		return 1
	}

	if [ -f $UNSIGNED_ADDON_PATH/addonunsigned ]; then
		sed -i "/$1/d" $UNSIGNED_ADDON_PATH/addonunsigned
	fi
}

_call_policykit_dbus_method() {
	# Wrapper for calling a method in the PolicyKit system over D-Bus
	#
	# Arguments:
	#	$1 name of the method to call
	#	$.. zero or more arguments to pass to the D-Bus method
	#
	# Example:
	#	_call_policykit_dbus_method CheckUser string:"$user"
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local IFS=" "
	gdbus call -y -d com.axis.PolicyKitSystem -o /com/axis/PolicyKitSystem 	-m com.axis.PolicyKitSystem."$@"
}

create_addon_user() {
	# Create a new addon user with the specified username
	#
	# Arguments:
	#	$1 Add-on username
	#	$2 List of secondary groups the user should be apart of. May be empty
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	[ $# -eq 2 ] || {
		syslog_err $COMMON_LOG_TAG 			"create_addon_user: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"create_addon_user: Invalid username"
		return 1
	}

	tempvar=$(_call_policykit_dbus_method AddSystemUser "$1" "" "addon" "[$2]" 	"/bin/false" "User for addon $1")
	syslog_notice $COMMON_LOG_TAG "Create addon-user: $tempvar"
}

update_addon_user_groups() {
	# Update addon user with the new secondary groups
	#
	# Arguments:
	#	$1 username
	#	$2 a list of secondary groups. An empty list will clear the users
	#	secondary groups.

	[ $# -eq 2 ] && [ "$1" ] || {
		logger -p error "Invalid number of arguments"
		return 1
	}
	# The user D-Bus interface requires different methods for removing all
	# groups and changing groups

	if [ -z "$2" ]; then
		# Group list is empty, clear all secondary groups
		tempvar=$(_call_policykit_dbus_method ClearUserFields $1 false true false)
		syslog_notice $COMMON_LOG_TAG 			"Clear secondary groups: $tempvar"
	else
		# Update secondary groups
		tempvar=$(_call_policykit_dbus_method ModifyUser $1 "" "" "[$2]" 			/bin/false "User for addon $1")
		syslog_notice $COMMON_LOG_TAG 			"Update secondary groups: $tempvar"
	fi
}

get_uid_for_user() {
	# Check if a addon user with the specified username exists,
	# and return the UID
	#
	# Arguments:
	#	$1 Add-on username
	#	$2 Variable to put the UID in, or -1 if user does not exist
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	# Prefixing variable with underscore to prevent variables with the same name
	# when doing the indirect assignment with eval.
	local user_id

	[ $# -eq 2 ] || {
		syslog_err $COMMON_LOG_TAG 			"do_addon_user_exist: Invalid number of arguments"
		return 1
	}

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"do_addon_user_exist: Invalid add-on username"
		return 1
	}

	[ "$2" ] || {
		syslog_err $COMMON_LOG_TAG 			"do_addon_user_exist: Invalid result variable"
		return 1
	}

	user_id=$(grep "^$1:" /etc/passwd | cut -d : -f 3)

	if [ "$user_id" ]; then
		_result=$user_id
	else
		_result=-1
	fi

	eval $2=\$_result
}

generate_dbus_conf_for_cb() {
	# Add a dbus configuration for an Addon
	# to allow CB from API which allows it.
	#
	# Arguments:
	#	$1 Add-on username
	#	$2 The API username
	#	$3 The API busname
	#
	# Exit status:
	#	0: On success
	#	1: Otherwise

	local _conf_file _busname

	[ $# -eq 3 ] || {
		syslog_err $COMMON_LOG_TAG 			"generate_dbus_conf_for_cb: Invalid number of arguments"
		return 1
	}
	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"generate_dbus_conf_for_cb: Invalid add-on username"
		return 1
	}

	[ "$2" ] || {
		syslog_err $COMMON_LOG_TAG 			"generate_dbus_conf_for_cb: Invalid API user name"
		return 1
	}

	[ "$3" ] || {
		syslog_err $COMMON_LOG_TAG 			"generate_dbus_conf_for_cb: Invalid API busname"
		return 1
	}

	_busname=$(echo "$1" | sed "s/-/_/g")

	mkdir -p "/opt/$1/usr/etc/dbus-1/system.d/"

	if [ -d "/opt/$1/usr/etc/dbus-1/system.d/" ]; then

		_conf_file="/opt/$1/usr/etc/dbus-1/system.d/$1.$3.conf"

		if [ -f $_conf_file.tmp ]; then
			rm -f $_conf_file.tmp
		fi

		echo "<!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\"
\"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">
<busconfig>
  <!--
    Explicit policy for the addon to own the busname
  -->
  <policy user=\"$1\">
    <!-- Allow the addon user to own the D-Bus name -->
    <allow own=\"com.axis.callbacks.$_busname\"/>
  </policy>

  <!-- Policy for the API -->
  <policy user=\"$2\">
    <!--
      send_destination:
      Allow API to call the Add-ons callback
    -->
    <allow send_destination=\"com.axis.callbacks.$_busname\"/>

    <!--
      receive_sender:
      Allow messages sent from CB D-Bus name to be received
    -->
    <allow receive_sender=\"com.axis.callbacks.$_busname\"/>

  </policy>

</busconfig>" >$_conf_file.tmp
	else
		revert_and_fail $ERR_FILE_SYSTEM 			"Failed to create _conf_file for $OPKGNAME"
	fi
	fsynced_write_or_cleanup $_conf_file.tmp $_conf_file
}

enable_dbus_conf_for_cb() {
	# Enable the CB configurations by
	# moving the .conf files to the conf folder
	# and reload dbus config.
	#
	# Arguments:
	#	$1 The Addon name
	#
	local _conffolder _cmd _reload=0

	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"enable_dbus_conf_for_cb: Invalid add-on username"
		return 1
	}

	_conffolder="/opt/$1/usr/etc/dbus-1/system.d/"

	[ ! -d $_conffolder ] || {
		files=$(ls -A $_conffolder)

		for file in $files
		do
			chmod 644 $_conffolder$file ||
				syslog_warn $COMMON_LOG_TAG 					"Failed to change owner of $_conffolder$file"
			mv "$_conffolder$file" "/etc/dbus-1/system.d/$file.tmp" ||
				syslog_warn $COMMON_LOG_TAG 					"Failed to move $_conffolder$file to /etc/dbus-1/system.d/$file.tmp"
			fsynced_write_or_cleanup 				"/etc/dbus-1/system.d/$file.tmp" "/etc/dbus-1/system.d/$file"
			_reload=1
		done
	}

	_cmd="gdbus call -y -d org.freedesktop.DBus -o /org/freedesktop/DBus -m org.freedesktop.DBus.ReloadConfig"

	[ $_reload = 0 ] || {
		$_cmd > /dev/null 2>&1 || {
			syslog_err $COMMON_LOG_TAG 				"enable_dbus_conf_for_cb: Failed to reload dBus config"
		}
	}
}

disable_dbus_conf_for_cb() {
	# Disable the CB configurations allowing
	# the API to call the add-on.
	#
	# Arguments:
	#	$1 The Addon name
	#
	[ "$1" ] || {
		syslog_err $COMMON_LOG_TAG 			"disable_dbus_conf_for_cb: No Add-on name supplied"
		return 1
	}

	rm -f /etc/dbus-1/system.d/$1*
}

# Reloads HTTPD if needed
reload_httpd() {
	if [ $FIRST_BOOT_MODE ]; then
		taint_pre_install_finalize_file
	else
		systemctl reload httpd >/dev/null 2>&1 ||
			revert_and_fail $ERR_INSTALL_ERROR "Could not reload Apache HTTPD"
	fi
}