#!/bin/sh /etc/rc.common
#
# description: Startup/shutdown script for nodogsplash captive portal
#
# Alexander Couzens <lynxis@fe80.eu> 2014
# P. Kube 2007
#
# (Based on wifidog startup script
# Date    : 2004-08-25
# Version : 1.0
# Comment by that author: Could be better, but it's working as expected)
#

START=95
STOP=95

USE_PROCD=1

IPT=/usr/sbin/iptables
WD_DIR=/usr/bin
# -s -d 5 runs in background, with level 5 (not so verbose) messages to syslog
# -f -d 7 runs in foreground, with level 7 (verbose) debug messages to terminal
OPTIONS="-s -f -d 5"
CONFIGFILE="/tmp/invalid_nodogsplash.conf"

# nolog(loglevel message ...)
nolog() {
  local level=$1
  shift
  logger -s -t nodogsplash -p daemon.$level $@
}

# append_config_option_map <cfgfile> <uci_cfg_obj> <option_name> <config_counterpart> [<optional default>]
# append "$config_counterpart $value" to cfgfile if option_name exists
# e.g. append_config_option "$CONFIGFILE" "$cfg" bind_address BindAddress 0.0.0.0
# will append "BindAddress 192.168.1.1" if uci bind_address is '192.168.1.1'
append_config_option_map() {
  local val=""
  local config_file="$1"
  local cfg="$2"
  local option_name="$3"
  local config_counterpart="$4"
  local default="$5"
  config_get val "$cfg" "$option_name" "$default"
  [ -n "$val" ] && echo "$config_counterpart $val" >> $config_file
}

# append_config_option <cfgfile> <uci_cfg_obj> <option_name> [<optional default>]
# append "$option_name $value" to cfgfile if option_name exists
# e.g. append_config_option "$CONFIGFILE" "$cfg" bind_address 0.0.0.0
# will append "bind_address 192.168.1.1" if uci bind_address is '192.168.1.1'
# if uci bind_address is unset append "bind_address 0.0.0.0"
append_config_option() {
  local val=""
  local config_file="$1"
  local cfg="$2"
  local option_name="$3"
  local default="$4"
  config_get val "$cfg" "$option_name" "$default"
  [ -n "$val" ] && echo "$option_name $val" >> $config_file
}

setup_mac_lists() {
  local cfg="$1"
  local MAC=""
  local val

  append_mac() {
    append MAC "$1" ","
  }

  config_get val "$cfg" macmechanism
  if [ -z "$val" ] ; then
    # check if we have AllowedMACList or BlockedMACList defined they will be ignored
    config_get val "$cfg" allowedmac
    if [ -n "$val" ] ; then
      echo "Ignoring allowedmac - macmechanism not \"allow\"" >&2
    fi

    config_get val "$cfg" blockedmac
    if [ -n "$val" ] ; then
      echo "Ignoring blockedmac - macmechanism not \"block\"" >&2
    fi
  elif [ "$val" == "allow" ] ; then
    MAC=""
    config_list_foreach "$cfg" allowedmac append_mac
    echo "AllowedMACList $MAC" >> $CONFIGFILE
  elif [ "$val" == "block" ] ; then
    MAC=""
    config_list_foreach "$cfg" blockedmac append_mac
    echo "BlockedMACList $MAC" >> $CONFIGFILE
  else
    nolog error "$cfg Invalid macmechanism '$val' - allow or block are valid."
    return 1
  fi
  MAC=""
  config_list_foreach "$cfg" trustedmac append_mac
  [ -n "$MAC" ] && echo "TrustedMACList $MAC" >> $CONFIGFILE
}

setup_firewall() {
  local cfg="$1"
  local uciname
  local val

  append_firewall() {
    echo "    FirewallRule $1" >> $CONFIGFILE
  }

  for rule in $(echo authenticated-users preauthenticated-users users-to-router trusted-users trusted-users-to-router)
  do
    uci_name=${rule//-/_}
    # uci does not allow - dashes
    echo "FirewallRuleSet $rule {" >> $CONFIGFILE
    config_list_foreach "$cfg" ${uci_name} append_firewall
    echo "}" >> $CONFIGFILE
    config_get val "$cfg" policy_${uci_name}
    [ -n "$val" ] && echo "EmptyRuleSetPolicy $rule $val" >> $CONFIGFILE
  done
}

wait_for_interface()
{
  local ifname="$1"
  local timeout=10
  for i in $(seq $timeout); do
    if [ $(ip -4 addr show dev $ifname 2> /dev/null | grep -c inet) -ne 0 ]; then
      break
    fi
    sleep 1
    if [ $i == $timeout ] ; then
      nolog error "$ifname not detected, NoDogSplash not starting."
      exit 1
    fi
  done
}

generate_uci_config() {
  local cfg="$1"
  local val
  local ifname
  local download
  local upload

  CONFIGFILE="/tmp/etc/nodogsplash_$cfg.conf"

  echo "# auto-generated config file from /etc/config/nodogsplash" > $CONFIGFILE

  config_get val "$cfg" config
  if [ -n "$val" ] ; then
    if [ ! -f "$val" ] ; then
      nolog error "Configuration file '$file' doesn't exist"
      return 0
    fi
    cat "$val" >> $CONFIGFILE
  fi

  config_get val "$cfg" network
  if [ -n "$val" ] ; then
    if ! network_get_device ifname "$val" ; then
      nolog error "$cfg can not find ifname for network '$val'"
      return 1
    fi
  fi

  config_get val "$cfg" gatewayinterface
  if [ -n "$val" ] ; then
    if [ -n "$ifname" ] ; then
      nolog error "$cfg cannot use both option network and gatewayinterface"
      return 1
    fi
    ifname="$val"
  fi

  if [ -z "$ifname" ] ; then
      nolog error "$cfg option network or gatewayinterface missing"
      return 1
  fi

  wait_for_interface "$ifname"

  echo "GatewayInterface $ifname" >> $CONFIGFILE

  append_config_option "$CONFIGFILE" "$cfg" gatewayname
  append_config_option "$CONFIGFILE" "$cfg" gatewayaddress
  append_config_option "$CONFIGFILE" "$cfg" gatewayport
  append_config_option "$CONFIGFILE" "$cfg" maxclients
  append_config_option "$CONFIGFILE" "$cfg" webroot
  append_config_option "$CONFIGFILE" "$cfg" debuglevel
  append_config_option "$CONFIGFILE" "$cfg" splashpage
  append_config_option "$CONFIGFILE" "$cfg" pagesdir
  append_config_option "$CONFIGFILE" "$cfg" checkinterval
  append_config_option "$CONFIGFILE" "$cfg" syslogfacility
  append_config_option "$CONFIGFILE" "$cfg" gatewayiprange
  append_config_option "$CONFIGFILE" "$cfg" imagedir
  append_config_option "$CONFIGFILE" "$cfg" redirecturl
  append_config_option "$CONFIGFILE" "$cfg" clientidletimeout
  append_config_option "$CONFIGFILE" "$cfg" clientforcetimeout
  append_config_option "$CONFIGFILE" "$cfg" gatewayiprange
  append_config_option "$CONFIGFILE" "$cfg" passwordattempts
  append_config_option "$CONFIGFILE" "$cfg" macmechanism
  append_config_option "$CONFIGFILE" "$cfg" uploadlimit
  append_config_option "$CONFIGFILE" "$cfg" downloadlimit
  append_config_option "$CONFIGFILE" "$cfg" remoteauthenticatoraction
  append_config_option "$CONFIGFILE" "$cfg" enablepreauth
  append_config_option "$CONFIGFILE" "$cfg" binvoucher
  append_config_option "$CONFIGFILE" "$cfg" forcevoucher
  append_config_option "$CONFIGFILE" "$cfg" passwordauthentication
  append_config_option "$CONFIGFILE" "$cfg" usernameauthentication
  append_config_option "$CONFIGFILE" "$cfg" passwordattempts
  append_config_option "$CONFIGFILE" "$cfg" username
  append_config_option "$CONFIGFILE" "$cfg" password
  append_config_option "$CONFIGFILE" "$cfg" authenticateimmediately
  append_config_option "$CONFIGFILE" "$cfg" decongesthttpdthreads
  append_config_option "$CONFIGFILE" "$cfg" httpdthreadthreshold
  append_config_option "$CONFIGFILE" "$cfg" httpdthreaddelayms
  append_config_option "$CONFIGFILE" "$cfg" fw_mark_authenticated
  append_config_option "$CONFIGFILE" "$cfg" fw_mark_trusted
  append_config_option "$CONFIGFILE" "$cfg" fw_mark_blocked

  config_get download "$cfg" downloadlimit
  config_get upload "$cfg" uploadlimit
  [ -n "$upload" -o -n "$download" ] && echo "TrafficControl yes" >> $CONFIGFILE

  setup_mac_lists "$cfg"
  setup_firewall "$cfg"
}

# setup configuration and start instance
create_instance() {
  local cfg="$1"
  local manual_config
  local val

  config_get_bool val "$cfg" enabled 0
  [ $val -gt 0 ] || return 0

  generate_uci_config "$cfg"

  if ! test_module ;  then
    logger -s -t nodogsplash -p daemon.error "nodogsplash is missing some kernel modules"
  fi

  procd_open_instance $cfg
  procd_set_param command /usr/bin/nodogsplash -c $CONFIGFILE $OPTIONS
  procd_set_param respawn
  procd_set_param file $CONFIGFILE
  procd_close_instance
}

start_service() {
  include /lib/functions

  mkdir -p /tmp/etc/
  config_load nodogsplash

  config_foreach create_instance nodogsplash
}

stop_service() {
  # nodogsplash doesn't exit fast enought, when procd terminates it.
  # otherwise procd will restart nodogsplash twice. first time starting nodogsplash fails, second time it succeeds
  sleep 1
}

status() {
  $WD_DIR/ndsctl status
}

# Test if we got all modules loaded
test_module() {
  ### Test ipt_mark with iptables
  test_ipt_mark () {
    ($IPT -A FORWARD -m mark --mark 2 -j ACCEPT 2>&1) > /dev/null
    IPTABLES_OK=$?
    if [ "$IPTABLES_OK" -eq 0 ]; then
      ($IPT -D FORWARD -m mark --mark 2 -j ACCEPT 2>&1) > /dev/null
      return 0
    else
      return 1
    fi
  }

  ### Test ipt_mac with iptables
  test_ipt_mac () {
    ($IPT -A INPUT -m mac --mac-source 00:00:00:00:00:00 -j ACCEPT 2>&1) > /dev/null
    IPTABLES_OK=$?
    if [ "$IPTABLES_OK" -eq 0 ]; then
      ($IPT -D INPUT -m mac --mac-source 00:00:00:00:00:00 -j ACCEPT 2>&1) > /dev/null
      return 0
    else
      return 1
    fi
  }

  ### Test ipt_IMQ with iptables
  test_ipt_IMQ () {
    ($IPT -t mangle -A PREROUTING -j IMQ --todev 0 2>&1) > /dev/null
    IPTABLES_OK=$?
    if [ "$IPTABLES_OK" -eq 0 ]; then
      ($IPT -t mangle -D PREROUTING -j IMQ --todev 0 2>&1) > /dev/null
      return 0
    else
      return 1
    fi
  }

  ### Test imq with ip
  test_imq () {
    (ip link set imq0 up 2>&1) > /dev/null
    IMQ0_OK=$?
    (ip link set imq1 up 2>&1) > /dev/null
    IMQ1_OK=$?
    if [ "$IMQ0_OK" -eq 0 -a "$IMQ1_OK" -eq 0 ]; then
      (ip link set imq0 down 2>&1) > /dev/null
      (ip link set imq1 down 2>&1) > /dev/null
      return 0
    else
      return 1
    fi
  }

  ### Test sch_htb with tc; requires imq0
  test_sch_htb () {
    (tc qdisc del dev imq0 root 2>&1) > /dev/null
    (tc qdisc add dev imq0 root htb 2>&1) > /dev/null
    TC_OK=$?
    if [ "$TC_OK" -eq 0 ]; then
      (tc qdisc del dev imq0 root 2>&1) > /dev/null
      return 0
    else
      return 1
    fi
  }

  ### Find a module on disk
  module_exists () {
    EXIST=$(find /lib/modules/`uname -r` -name $1.*o 2> /dev/null)
    if [ -n "$EXIST" ]; then
      return 0
    else
      return 1
    fi
  }

  ### Test if a module is in memory
  module_in_memory () {
    MODULE=$(lsmod | grep $1 | awk '{print $1}')
    if [ "$MODULE" = "$1" ]; then
      return 0
    else
      return 1
    fi
  }

  ### Test functionality of a module; load if necessary
  do_module_tests () {
    echo "  Testing module $1 $2"
    "test_$1"
    if [ $? -ne 0 ]; then
      echo "   Module $1 $2 needed"
      echo "   Scanning disk for $1 module"
      module_exists $1
      if [ $? -ne 0 ]; then
        echo "   $1 module missing: please install it"
        exit 1
      else
        echo "   $1 exists, trying to load"
        insmod $1 $2 > /dev/null
        if [ $? -ne 0 ]; then
          echo "   Error: insmod $1 $2 failed"
          exit 1
        else
          echo "   $1 $2 loaded successfully"
        fi
      fi
    else
      echo "   $1 is working"
    fi
  }

  echo " Testing required modules"

  do_module_tests "ipt_mac"
  do_module_tests "ipt_mark"

  # test for imq modules, only if TrafficControl is enabled in conf
  if ( grep -q -E '^[[:space:]]*TrafficControl[[:space:]]+(yes|true|1)' "$CONFIGFILE" ) ; then
    do_module_tests "imq" "numdevs=2"
    do_module_tests "ipt_IMQ"
    do_module_tests "sch_htb"
  fi
}
