#!/bin/sh


BRANCH=$(uci get autoupdater.settings.branch)

force=0
check=0

[ "$1" = "-f" ] && force=1
[ "$1" = "-c" ] && force=1 && check=1

if [ $force != 1 -a $(uci get autoupdater.settings.enabled) != 1 ]; then
	echo "autoupdater is disabled"
	exit 0
fi

FETCH_DELAY=$(uci -q get autoupdater.${BRANCH}.fetch_delay)
FETCH_DELAY=${FETCH_DELAY-0}
APPLY_DELAY=$(uci -q get autoupdater.${BRANCH}.apply_delay)
APPLY_DELAY=${APPLY_DELAY-0}

BRANCH_NAME=$(uci get autoupdater.${BRANCH}.name)
MIRRORS=$(for mirror in $(uci get autoupdater.${BRANCH}.mirror); do \
            hexdump -n1 -e '/1 "%d '"$mirror"'\n"' /dev/urandom; \
          done | sort -n | cut -d' ' -f2)
PUBKEYS=$(uci get autoupdater.${BRANCH}.pubkey)
GOOD_SIGNATURES=$(uci get autoupdater.${BRANCH}.good_signatures)

# returns 0 when $1 is a higher version number than $2
newer_than() {
  vnum() {
    local major="${1:-0}" minor="${2:-0}" patch="${3:-0}"
    echo $((major * 10000 + minor * 100 + patch))
  }
  local OLD_IFS="$IFS"
  IFS="."
  local v1=$(vnum $1)
  local v2=$(vnum $2)
  IFS="$OLD_IFS"

  [ $v1 -gt $v2 ]
}

fetch_manifest() {
  local MIRROR=$1
  local manifest=$2

  wget -T 120 -O$manifest "$MIRROR"/manifest &> /dev/null

  if test $? -ne 0; then
    echo "Couldn't fetch manifest from $MIRROR" >&2
    return 1
  fi

  return 0
}

verify_manifest() {
  local manifest=$1
  local manifest_upper=$2
  local manifest_lower=$(mktemp)
  awk "BEGIN    { sep=0 }
     /^---\$/ { sep=1; next }
              { if(sep==0) print > \"$manifest_upper\";
                else       print > \"$manifest_lower\"}" \
    $manifest

  local signatures=""
  while read sig; do
    echo "$sig" | grep -q "^[0-9a-f]\{128\}$"
    if test $? -ne 0; then
      continue
    fi
    signatures="$signatures -s $sig"
  done < $manifest_lower

  local pubkeys=""
  for key in $PUBKEYS; do
    pubkeys="$pubkeys -p $key"
  done

  rm -f $manifest_lower

  ecdsaverify -n $GOOD_SIGNATURES $pubkeys $signatures $manifest_upper

  if test $? -ne 0; then
    echo "Not enough valid signatures!" >&2
    return 1
  fi

  return 0
}

analyse_manifest() {
  local manifest_upper=$1

  grep -q "^BRANCH=${BRANCH_NAME}$" $manifest_upper

  if test $? -ne 0; then
    echo "Wrong branch. We are on ${BRANCH_NAME}" >&2
    return 1
  fi

  local my_firmware
  my_firmware=$(grep -m1 "^${my_model} " $manifest_upper)

  if test $? -ne 0; then
    echo "No matching firmware found (model ${my_model})" >&2

    # report back missing model
    #wget -T 3 --spider "${MIRROR}/request_image.sh?model=${my_model}" -O /dev/null &> /dev/null
    return 1
  fi

  fw_version=$(echo "${my_firmware}" | cut -d' ' -f2)
  fw_checksum=$(echo "${my_firmware}" | cut -d' ' -f3)
  fw_file=$(echo "${my_firmware}" | cut -d' ' -f4)

  return 0
}

fetch_firmware() {
  local MIRROR=$1
  local fw_image=$2

  wget -T 120 -O$fw_image "${MIRROR}/${fw_file}" &> /dev/null

  if test $? -ne 0; then
    echo "Cannot contact update server: $MIRROR" >&2
    return 1
  fi

  return 0
}

autoupdate() {
  local MIRROR=$1

  local manifest=$(mktemp)
  fetch_manifest $MIRROR $manifest || { rm -f $manifest; return 1; }

  local manifest_upper=$(mktemp)
  verify_manifest $manifest $manifest_upper || { rm -f $manifest $manifest_upper; return 1; }
  rm -f $manifest

  analyse_manifest $manifest_upper || { rm -f $manifest_upper; return 1; }
  rm -f $manifest_upper

  if newer_than "$(echo $fw_version | tr -dc '.0-9')" "$(echo $my_version | tr -dc '.0-9')"; then
    echo "New version available: $fw_version"

    [ $check -eq 1 ] && return 0

    # drop caches to make room for firmware image
    sync
    sysctl -w vm.drop_caches=3

    local fw_image=$(mktemp)
    fetch_firmware $MIRROR $fw_image || { rm -f $fw_image; return 1; }

    image_sha512=$(sha512sum "$fw_image" | awk '{print $1}')
    if [ "$image_sha512" != "$fw_checksum" ]; then
      echo "Invalid image checksum" >&2
      rm -f $fw_image
      return 1
    fi
    echo "Upgrading firmware."
    [ $force -eq 0 ] && sleep $APPLY_DELAY
    sysupgrade "${fw_image}"
  else
    echo "No new firmware available" >&2
  fi

  return 0
}

trap 'echo Signal ignored.' INT TERM PIPE

my_model="$(cat /tmp/sysinfo/model | tr '[A-Z]' '[a-z]' | sed -r 's/[^a-z0-9]+/-/g;s/-$//')"
my_version="$(uci -q get freifunk.@settings[0].version)"

lock_dir=/var/lock/autoupdater.lock
mkdir $lock_dir || { echo "Autoupdater already running" >&2; exit 1; }

# wait up to 15 minutes to spread the load for the server
[ $force -eq 0 ] && sleep $(awk -v val=$FETCH_DELAY 'BEGIN{srand(); print(int(val * rand()));}')

for mirror in $MIRRORS; do

  autoupdate $mirror && break

  unset fw_version
  unset fw_checksum
  unset fw_file

done

rmdir $lock_dir
