#!/bin/sh # parse options while [ -n "$1" ]; do case "$1" in --version|-v) export VERSION=1; break;; --url) export URL=${2%/}; shift;; # strip trailing slash --interval) export INTERVAL=$2; shift;; --verify-ssl) export VERIFY_SSL=$2; shift;; --uuid) export UUID="$2"; shift;; --key) export KEY="$2"; shift;; --shared-secret) export SHARED_SECRET="$2"; shift;; --consistent-key) export CONSISTENT_KEY="$2"; shift;; --hardware-id-script) export HARDWARE_ID_SCRIPT="$2"; shift;; --hardware-id-key) export HARDWARE_ID_KEY="$2"; shift;; --bootup-delay) export BOOTUP_DELAY="$2"; shift;; --merge-config) export MERGE_CONFIG="$2"; shift;; --unmanaged) export UNMANAGED="$2"; shift;; --test-config) export TEST_CONFIG="$2"; shift;; --test-retries) export TEST_RETRIES="$2"; shift;; --test-script) export TEST_SCRIPT="$2"; shift;; --connect-timeout) export CONNECT_TIMEOUT="$2"; shift;; --max-time) export MAX_TIME="$2"; shift;; --capath) export CAPATH="$2"; shift;; --cacert) export CACERT="$2"; shift;; --mac-interface) export MAC_INTERFACE="$2"; shift;; --management-interface) export MANAGEMENT_INTERFACE="$2"; shift;; --default-hostname) export DEFAULT_HOSTNAME="$2"; shift;; --pre-reload-hook) export PRE_RELOAD_HOOK="$2"; shift;; --post-reload-hook) export POST_RELOAD_HOOK="$2"; shift;; --post-reload-delay) export POST_RELOAD_DELAY="$2"; shift;; --post-registration-hook) export POST_REGISTRATION_HOOK="$2"; shift;; -*) echo "Invalid option: $1" exit 1 ;; *) break;; esac shift; done if [ "$VERSION" -eq "1" ]; then VERSION=$(cat /etc/openwisp/VERSION) echo "openwisp-config $VERSION" exit 0 fi if [ -z "$URL" ]; then logger -s "missing required --url option" \ -t openwisp \ -p daemon.err exit 2 fi if ([ -z "$UUID" ] || [ -z "$KEY" ]) && [ -z "$SHARED_SECRET" ]; then logger -s "you must either specify --uuid and --key, or --shared-secret" \ -t openwisp \ -p daemon.err exit 3 fi INTERVAL=${INTERVAL:-120} VERIFY_SSL=${VERIFY_SSL:-1} MERGE_CONFIG=${MERGE_CONFIG:-1} TEST_CONFIG=${TEST_CONFIG:-1} TEST_RETRIES=${TEST_RETRIES:-3} CONSISTENT_KEY=${CONSISTENT_KEY:-1} HARDWARE_ID_KEY=${HARDWARE_ID_KEY:-1} BOOTUP_DELAY=${BOOTUP_DELAY:-0} CONNECT_TIMEOUT=${CONNECT_TIMEOUT:-15} MAX_TIME=${MAX_TIME:-30} MAC_INTERFACE=${MAC_INTERFACE:-eth0} # LEDE was a popular fork of OpenWRT used in 2017 # it's kept here for backward compatibility DEFAULT_HOSTNAME=${DEFAULT_HOSTNAME:-"LEDE"} PRE_RELOAD_HOOK=${PRE_RELOAD_HOOK:-/etc/openwisp/pre-reload-hook} POST_RELOAD_HOOK=${POST_RELOAD_HOOK:-/etc/openwisp/post-reload-hook} POST_RELOAD_DELAY=${POST_RELOAD_DELAY:-5} POST_REGISTRATION_HOOK=${POST_REGISTRATION_HOOK:-/etc/openwisp/post-registration-hook} WORKING_DIR="/tmp/openwisp" BASEURL="$URL/controller" CONFIGURATION_ARCHIVE="$WORKING_DIR/configuration.tar.gz" CONFIGURATION_CHECKSUM="$WORKING_DIR/checksum" CONFIGURATION_BACKUP="$WORKING_DIR/backup.tar.gz" BACKUP_FILE_LIST="$WORKING_DIR/backup_file_list.conf" REGISTRATION_PARAMETERS="$WORKING_DIR/registration_parameters" TEST_CHECKSUM="$WORKING_DIR/test_checksum" UPDATE_INFO="$WORKING_DIR/update_info" STATUS_REPORT="$WORKING_DIR/status_report" APPLYING_CONF="$WORKING_DIR/applying_conf" BOOTUP="$WORKING_DIR/bootup" REGISTRATION_URL="$URL/controller/register/" UNMANAGED_DIR="$WORKING_DIR/unmanaged" FETCH_COMMAND="curl -s --connect-timeout $CONNECT_TIMEOUT --max-time $MAX_TIME" mkdir -p $WORKING_DIR mkdir -p $UNMANAGED_DIR if [ "$VERIFY_SSL" == "0" ]; then FETCH_COMMAND="$FETCH_COMMAND -k" else if [ -n "$CAPATH" ]; then FETCH_COMMAND="$FETCH_COMMAND --capath $CAPATH" fi if [ -n "$CACERT" ]; then FETCH_COMMAND="$FETCH_COMMAND --cacert $CACERT" fi fi if [ ! -x "$HARDWARE_ID_SCRIPT" ]; then HARDWARE_ID_KEY=0 fi if [ -n "$UNMANAGED" ]; then # replace commas with spaces UNMANAGED=$(echo $UNMANAGED | tr ',' ' ') fi # if management interface is not ready, the ip will be empty but other # attempts at determining it will be done before the start-up is completed if [ -n "$MANAGEMENT_INTERFACE" ]; then MANAGEMENT_IP=$(/usr/sbin/openwisp-get-address $MANAGEMENT_INTERFACE) fi get_model(){ cat /tmp/sysinfo/model; } get_os(){ ubus call system board | grep description | awk -F\" '{ print $4 }'; } get_system(){ ubus call system board | grep system | awk -F\" '{ print $4 }'; } # ensures we are dealing with the right web server check_header(){ local is_controller=$(grep -ic "X-Openwisp-Controller: true" $1) if [ $is_controller -lt 1 ]; then logger -s "Invalid url: missing X-Openwisp-Controller header" \ -t openwisp \ -p daemon.err exit 4 fi } # performs automatic registration register() { logger "Registering device..." \ -t openwisp \ -p daemon.info local hostname=$(uci get system.@system[0].hostname) # use macaddr of interface specified in $MAC_INTERFACE local raw=$(ifconfig $MAC_INTERFACE 2&> /dev/null) # if previous command fails, fallback to first non-loopback interface if [ -z "$raw" ]; then raw=$(ifconfig); fi local macaddr=$(echo "$raw " | egrep -v "^(lo|tap[0-9]+)" | awk '/HWaddr/ { print $5 }' | head -n 1) # convert hostname to lowercase for case insensitive check local name=$(echo $hostname | awk '{print tolower($0)}') # use mac address if hostname is the default one or empty if [ "$name" == "openwrt" ] || [ "$hostname" == "$DEFAULT_HOSTNAME" ] || [ -z "$name" ]; then hostname="$macaddr" fi local backend="netjsonconfig.OpenWrt" local tags=$(uci get openwisp.http.tags 2&> /dev/null) # get device model, OS identifier and SOC info local model=$(get_model) local os=$(get_os) local system=$(get_system) # get hardware id if script is given if [ -x "$HARDWARE_ID_SCRIPT" ]; then local hardware_id=$("$HARDWARE_ID_SCRIPT") else local hardware_id="" fi # prepare POST params local params="backend=$backend" if [ "$CONSISTENT_KEY" == "1" ]; then if [ -x "$HARDWARE_ID_SCRIPT" ] && [ "$HARDWARE_ID_KEY" = "1" ]; then # use hardware id as base for the key local keybase=$hardware_id else # use macaddress as base for the key local keybase=$macaddr fi local consistent_key=$(echo -n "$keybase+$SHARED_SECRET" | md5sum | awk '{print $1}') params="$params&key=$consistent_key" fi # add management ip in params if present if [ -n "$MANAGEMENT_IP" ]; then params="$params&management_ip=$MANAGEMENT_IP" fi $($FETCH_COMMAND --data $params \ --data-urlencode secret="$SHARED_SECRET" \ --data-urlencode name="$hostname" \ --data-urlencode hardware_id="$hardware_id" \ --data-urlencode mac_address="$macaddr" \ --data-urlencode tags="$tags" \ --data-urlencode model="$model" \ --data-urlencode os="$os" \ --data-urlencode system="$system" \ -i $REGISTRATION_URL > $REGISTRATION_PARAMETERS) local exit_code=$? # report eventual failures and return if [ "$exit_code" != "0" ]; then logger -s "Failed to connect to controller during registration: curl exit code $exit_code" \ -t openwisp \ -p daemon.err return 1 fi # exit if response does not seem to come from openwisp controller check_header $REGISTRATION_PARAMETERS if [ $(head -n 1 $REGISTRATION_PARAMETERS | grep -c "201 Created") -lt 1 ]; then # get body of failure response, seperated from header by a blank line (CRLF-ended) local message=$(awk 'BEGIN{RS="\r\n\r\n"}{if(NR>1)print $0}' $REGISTRATION_PARAMETERS) logger -s "Registration failed! $message" \ -t openwisp \ -p daemon.err # if controller returns 403, stop retrying because it's useless if [ $(head -n 1 $REGISTRATION_PARAMETERS | grep -c "403 Forbidden") -gt 0 ]; then exit 5 # in all other cases, keep re-trying because the problem can be fixed # on the server (eg: self creation disabled and device needs to be created # or temporary error condition on the server) else return 2 fi fi # set configuration options and reload export UUID=$(cat $REGISTRATION_PARAMETERS | grep uuid | awk '/uuid: / { print $2 }') export KEY=$(cat $REGISTRATION_PARAMETERS | grep key | awk '/key: / { print $2 }') local name=$(cat $REGISTRATION_PARAMETERS | grep hostname | awk '/hostname: / { print $2 }') local new=$(cat $REGISTRATION_PARAMETERS | grep 'is-new' | awk '/is-new: / { print $2 }') # keep backward compatibility with older controller (TODO: remove this in 0.5) [ -z "$name" ] && name="$hostname" [ -z "$new" ] && new="1" # persist uuid and key in conf uci set openwisp.http.uuid=$UUID uci set openwisp.http.key=$KEY # remove shared secret to avoid accidental re-registration uci set openwisp.http.shared_secret="" uci commit openwisp rm $REGISTRATION_PARAMETERS # log operation local prefix=$([ "$new" == "1" ] && echo "New" || echo "Existing") logger "$prefix device registered successfully as $name, id: $UUID" \ -t openwisp \ -p daemon.info # indicates this function has been called # (used by `discover_management_ip`) export REGISTER_CALLED=1 # call post registration hook post_registration_hook } # gets checksum from controller get_checksum() { $($FETCH_COMMAND -i $CHECKSUM_URL > $1) local exit_code=$? if [ "$exit_code" != "0" ]; then logger -s "Failed to connect to controller while getting checksum: curl exit code $exit_code" \ -t openwisp \ -p daemon.err return 2 fi if [ $(head -n 1 $1 | grep -c "200 OK") -lt 1 ]; then local status=$(head -n 1 $1) logger -s "Failed to retrieve checksum: $status" \ -t openwisp \ -p daemon.err return 3 fi check_header $1 } # returns 1 if configuration in controller has changed configuration_changed() { local CURRENT_CHECKSUM=$(tail -n 1 $CONFIGURATION_CHECKSUM 2> /dev/null) get_checksum $CONFIGURATION_CHECKSUM local exit_code=$? if [ "$exit_code" != "0" ]; then return $exit_code fi local REMOTE_CHECKSUM=$(tail -n 1 $CONFIGURATION_CHECKSUM 2> /dev/null) if [ "$CURRENT_CHECKSUM" != "$REMOTE_CHECKSUM" ]; then logger "Local configuration outdated" \ -t openwisp \ -p daemon.info return 1 fi return 0 } # called in `apply_configuration` before services are reloaded pre_reload_hook() { if [ -x "$PRE_RELOAD_HOOK" ]; then $PRE_RELOAD_HOOK local exit_code=$? logger "Called pre-reload-hook: $PRE_RELOAD_HOOK - exit code: $exit_code" \ -t openwisp \ -p daemon.info return $exit_code fi } # called in `apply_configuration` after services are reloaded post_reload_hook() { if [ -x "$POST_RELOAD_HOOK" ]; then $POST_RELOAD_HOOK local exit_code=$? logger "Called post-reload-hook: $POST_RELOAD_HOOK - exit code: $exit_code" \ -t openwisp \ -p daemon.info return $exit_code fi } # called after registration is completed post_registration_hook() { if [ -x "$POST_REGISTRATION_HOOK" ]; then $POST_REGISTRATION_HOOK local exit_code=$? logger "Called post-reload-hook: $POST_REGISTRATION_HOOK - exit code: $exit_code" \ -t openwisp \ -p daemon.info return $exit_code fi } # applies a specified configuration archive apply_configuration() { # store unmanaged config (if enabled) call_store_unmanaged # update local configuration /usr/sbin/openwisp-update-config --conf=$1 --merge=$MERGE_CONFIG local exit_code=$? # restore unmanaged configurations call_restore_unmanaged # if configuration could not be applied # (because of a syntax error) restore a backup if [ "$exit_code" != "0" ]; then logger -s "Could not apply configuration, openwisp-update-config exit code was $exit_code" \ -t openwisp \ -p daemon.crit restore_backup return 1 fi # call pre-reload-hook pre_reload_hook # reload changes and wait $POST_RELOAD_DELAY /usr/sbin/openwisp-reload-config sleep $POST_RELOAD_DELAY # call post-reload-hook post_reload_hook } # send up to date device info to the controller update_info() { # get device model, OS identifier and SOC info local model=$(get_model) local os=$(get_os) local system=$(get_system) # retry several times for i in $(seq 1 10); do $FETCH_COMMAND -i \ --data "key=$KEY" \ --data-urlencode model="$model" \ --data-urlencode os="$os" \ --data-urlencode system="$system" \ "$UPDATE_INFO_URL" > $UPDATE_INFO local exit_code=$? if [ "$exit_code" = "0" ]; then logger "Device info updated on the controller" \ -t openwisp \ -p daemon.info break else sleep 2 fi done if [ "$exit_code" != "0" ]; then logger -s "Failed to connect to controller during update-info: curl exit code $exit_code" \ -t openwisp \ -p daemon.err return 2 fi if [ "$(head -n 1 "$UPDATE_INFO" | grep -c "200 OK")" -lt 1 ]; then local status=$(head -n 1 $UPDATE_INFO) logger -s "Failed to update device info: $status" \ -t openwisp \ -p daemon.err return 3 fi check_header $UPDATE_INFO rm $UPDATE_INFO } # report configuration status: "applied" or "error" report_status() { # retry several times for i in $(seq 1 10); do $($FETCH_COMMAND -i --data "key=$KEY&status=$1" $REPORT_URL > $STATUS_REPORT) local exit_code=$? if [ "$exit_code" == "0" ]; then break else sleep 2 fi done if [ "$exit_code" != "0" ]; then logger -s "Failed to connect to controller during report-status: curl exit code $exit_code" \ -t openwisp \ -p daemon.err return 2 fi if [ $(head -n 1 $STATUS_REPORT | grep -c "200 OK") -lt 1 ]; then local status=$(head -n 1 $STATUS_REPORT) logger -s "Failed to report status: $status" \ -t openwisp \ -p daemon.err return 3 fi check_header $STATUS_REPORT rm $STATUS_REPORT } # performs configuration test and reports result test_configuration() { apply_configuration $1 if [ $? -gt 0 ]; then return 1 fi logger "Testing configuration..." \ -t openwisp \ -p daemon.info if [ -z "$TEST_SCRIPT" ]; then perform_default_test local test_result=$? else $TEST_SCRIPT local test_result=$? fi if [ $test_result -gt 0 ]; then logger -s "Configuration test failed! Restoring previous backup" \ -t openwisp \ -p daemon.err restore_backup local ret=1 else logger "Configuration test succeeded" \ -t openwisp \ -p daemon.info local ret=0 fi rm $CONFIGURATION_BACKUP return $ret } perform_default_test() { # the default test is performed several times, as indicated by $TEST_RETRIES for i in $(seq 1 $TEST_RETRIES); do $($FETCH_COMMAND -i --connect-timeout 5 --max-time 5 $CHECKSUM_URL > $TEST_CHECKSUM) local result=$? if [ $result -gt 0 ]; then logger "Configuration test failed (attempt $i)" \ -t openwisp \ -p daemon.warning sleep 5 else break fi done rm $TEST_CHECKSUM return $result } # stores unmanaged configuration sections that will be merged # with the configuration downloaded from the controller call_store_unmanaged() { if [ -z "$UNMANAGED" ]; then return 0 fi /usr/sbin/openwisp-store-unmanaged -o="$UNMANAGED" } # restores unmanaged configuration sections that will be merged # with the configuration downloaded from the controller call_restore_unmanaged() { if [ -z "$UNMANAGED" ]; then return 0 fi /usr/sbin/openwisp-restore-unmanaged } # 1. removes default wifi-ifaces directive (LEDE or OpenWrt SSID) # 2. ensures there are no anonymous UCI blocks fix_uci_config() { /usr/sbin/openwisp-remove-default-wifi output=$(/usr/sbin/openwisp-uci-autoname) if [ -n "$output" ]; then logger "The following uci configs have been renamed: $output" \ -t openwisp \ -p daemon.info fi } # downloads configuration from controller # performs test (if testing enabled) # and applies it update_configuration() { logger "Downloading configuration from controller..." \ -t openwisp \ -p daemon.info # download configuration $($FETCH_COMMAND $CONFIGURATION_URL -o $CONFIGURATION_ARCHIVE) local exit_code=$? if [ "$exit_code" != "0" ]; then logger -s "Failed to connect to controller while downloading new config: curl exit code $exit_code" \ -t openwisp \ -p daemon.err return 3 fi logger "Configuration downloaded, now applying it..." \ -t openwisp \ -p daemon.info # makes fixes to the default uci config so # it's more suited for remote control fix_uci_config # control file to avoid reloading the agent while # configuration is still being applied touch $APPLYING_CONF # performs backup of files that are going to be changed backup_configuration # testing enabled if [ "$TEST_CONFIG" == "1" ]; then test_configuration $CONFIGURATION_ARCHIVE result=$? # testing diabled else apply_configuration $CONFIGURATION_ARCHIVE result=$? fi rm $APPLYING_CONF if [ "$result" == "0" ]; then logger "Configuration applied successfully" \ -t openwisp \ -p daemon.info report_status "applied" else report_status "error" fi } backup_configuration() { # save list of files that are going to be changed by openwisp # so that if the changes sent by openwisp cause problems, # the previous version can be rolled back tar -ztf $CONFIGURATION_ARCHIVE | sed 's/^/\//' > $BACKUP_FILE_LIST # backup only those files tar -zcf $CONFIGURATION_BACKUP -T $BACKUP_FILE_LIST rm $BACKUP_FILE_LIST } restore_backup() { # restores backup created in backup_configuration() function sysupgrade -r $CONFIGURATION_BACKUP pre_reload_hook /usr/sbin/openwisp-reload-config sleep $POST_RELOAD_DELAY post_reload_hook logger -s "The most recent configuration backup was restored" \ -t openwisp \ -p daemon.info } discover_management_ip(){ # return if not using management interface if [ -z "$MANAGEMENT_INTERFACE" ]; then return fi # if being called just after `register` was called if [ -n "$REGISTER_CALLED" ]; then # don't do anything now, but unset the # control variable so the logic will be # executed at the next iteration unset REGISTER_CALLED return fi MANAGEMENT_IP=$(/usr/sbin/openwisp-get-address $MANAGEMENT_INTERFACE) # if management interface is defined but its ip could not be determined # try waiting for the management interface to be ready if [ -z "$MANAGEMENT_IP" ]; then MAX_ATTEMPTS=3 for attempt in `seq $MAX_ATTEMPTS`; do MANAGEMENT_IP=$(/usr/sbin/openwisp-get-address $MANAGEMENT_INTERFACE) if [ -z "$MANAGEMENT_IP" ]; then retry_after=$(expr $INTERVAL / 12) logger -s "management interface not ready (attempt $attempt of $MAX_ATTEMPTS)" \ -t openwisp \ -p daemon.notice if [ $attempt -lt $MAX_ATTEMPTS ]; then sleep $retry_after else logger -s "could not determine ip address of management interface, giving up..." \ -t openwisp \ -p daemon.warning fi else break fi done fi # add management ip to URLs if present if [ -n "$MANAGEMENT_IP" ]; then export CONFIGURATION_URL="$BASE_CONFIGURATION_URL&management_ip=$MANAGEMENT_IP" export CHECKSUM_URL="$BASE_CHECKSUM_URL&management_ip=$MANAGEMENT_IP" fi } # ensure both UUID and KEY are defined # otherwise perform registration if [ -z "$UUID" ] || [ -z "$KEY" ]; then # do not crash if controller can't be reached # but retry every ($INTERVAL / 4) seconds # (device might be unplugged, unconfigured or connecting) until register do sleep $(expr $INTERVAL / 4) done touch "$BOOTUP" fi # these variables are evaluated here because "register()" might set UUID and KEY BASE_CONFIGURATION_URL="$BASEURL/download-config/$UUID/?key=$KEY" BASE_CHECKSUM_URL="$BASEURL/checksum/$UUID/?key=$KEY" CONFIGURATION_URL=$BASE_CONFIGURATION_URL CHECKSUM_URL=$BASE_CHECKSUM_URL UPDATE_INFO_URL="$BASEURL/update-info/$UUID/" REPORT_URL="$BASEURL/report-status/$UUID/" if [ ! -f "$BOOTUP" ]; then # actions to do only after agent start, not after registration or agent restart if [ "$BOOTUP_DELAY" != "0" ]; then # random bootup delay between 0..300 seconds DELAY="$(($RANDOM%$BOOTUP_DELAY))" logger "Delaying startup for $DELAY seconds..." \ -t openwisp \ -p daemon.info sleep $DELAY fi # send up to date device info update_info touch "$BOOTUP" fi while true do # check management interface at each iteration # (because the address may change due to # configuration updates or manual reconfigurations) discover_management_ip configuration_changed if [ "$?" == "1" ]; then update_configuration fi sleep $INTERVAL done