#!/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
[ $# -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"
}
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
}