#!/bin/sh

. /lib/functions.sh

SCRIPT=$0
# the lock is meant for ebtables, and other scripts are also
# using ebtables. Needs clean up, maybe move to functions.sh, TBD?
LOCKFILE=/tmp/ebtables.lock
LOGFILE=/tmp/log/parental_control.log
PACKAGE=parental_control
# variable to remember if parental control is enabled
parental_control_enabled=0

# uncomment the next line if matching rules should be logged to syslog
# (for debugging), needs watchers extension
#local EBLOG=--log

_log()
{
    if [ ${LOGGING} -eq 1 ]; then
        local ts=$(date)
        echo "$ts $@" >> ${LOGFILE}
    fi
}

_exit()
{
    local rc=$1
    lock -u ${LOCKFILE}
    exit ${rc}
}

_cron_restart()
{
    /etc/init.d/cron restart > /dev/null
}

_add_cron_script()
{
    (crontab -l ; echo "$1") | sort -u | crontab -
}

_rm_cron_script()
{
    crontab -l | grep -v "$1" | sort -u | crontab -
}

_rm_and_add_cron_script()
{
    (crontab -l | grep -v "$1"; [ -n "$2" ] && echo "$2") | sort -u | crontab -
}

_format_dow_list()
{
    local dow=$1
    local flist=""
    local day
    for day in ${dow}
    do
        if [ ! -z ${flist} ]; then
            flist="${flist},"
        fi
        flist="${flist}${day:0:3}"
    done
    echo ${flist}
}

_shift_dow_list_by_one_day()
{
    local dow=$1
    local flist=""
    local day
    for day in $1
    do
	if [ ! -z ${flist} ]; then
            flist="${flist},"
        fi
        case $day in
            Mon*)
                flist="${flist}Tue"
            ;;
            Tue*)
                flist="${flist}Wed"
            ;;
            Wed*)
                flist="${flist}Thu"
            ;;
            Thu*)
                flist="${flist}Fri"
            ;;
            Fri*)
                flist="${flist}Sat"
            ;;
            Sat*)
                flist="${flist}Sun"
            ;;
            Sun*)
                flist="${flist}Mon"
            ;;
        esac
    done

    echo "$flist"
}

_enable_parental_control()
{
    local entry=$1
    local enabled starttime stoptime dow

    config_get enabled $entry enabled 0
    [ "$enabled" -eq 0 ] && return 0
    config_get starttime $entry starttime
    [ -z "$starttime" ] && return 1
    config_get stoptime $entry stoptime
    [ -z "$stoptime" ] && return 1
    config_get dow $entry daysofweek
    [ -z "$dow" ] && return 1
    
    local fdow=$(_format_dow_list "$dow")

    # If it shall stop on the same day at 00:00, 24:00 is the transition to the following day, if this is the case,
    # the days of the weeks have to be incremented by one
    if [ "${stoptime}" == "24:00" ]; then
        fdow_stop=$(_shift_dow_list_by_one_day "$dow")
        stoptime="0:00"
    else
        fdow_stop=${fdow}
    fi

    local stop_cron_entry="$(echo ${stoptime} | awk -F':' '{print $2, $1}') * * ${fdow_stop} ${SCRIPT} check" # ${entry}"
    echo "${stop_cron_entry}"

    if [[ $starttime != $stoptime ]]                             
    then                                                         
        local start_cron_entry="$(echo ${starttime} | awk -F':' '{print $2, $1}') * * ${fdow} ${SCRIPT} check" # ${entry}"
        echo "${start_cron_entry}"
    fi

    return 0
}

_create_cron_entries()
{
    config_foreach _enable_parental_control entry
}

replace_rules() {
    local mac macs="$1"
    ebtables -F i-parental-control
    ebtables -F o-parental-control
    for mac in $macs; do
        ebtables -A i-parental-control -s $mac -j DROP $EBLOG
        ebtables -A o-parental-control -d $mac -j DROP $EBLOG
    done
}

create_chains() {
    ebtables -N i-parental-control -P RETURN 2>/dev/null || return 0
    ebtables -N o-parental-control -P RETURN 2>/dev/null || return 0
    ebtables -A INPUT   -i ath+ -j i-parental-control $EBLOG
    ebtables -A FORWARD -i ath+ -j i-parental-control $EBLOG
    ebtables -A FORWARD -o ath+ -j o-parental-control $EBLOG
    ebtables -A OUTPUT  -o ath+ -j o-parental-control $EBLOG
}

delete_chains() {
    ebtables -D INPUT   -i ath+ -j i-parental-control $EBLOG 2>/dev/null
    ebtables -D FORWARD -i ath+ -j i-parental-control $EBLOG 2>/dev/null
    ebtables -D FORWARD -o ath+ -j o-parental-control $EBLOG 2>/dev/null
    ebtables -D OUTPUT  -o ath+ -j o-parental-control $EBLOG 2>/dev/null
    ebtables -X i-parental-control 2>/dev/null
    ebtables -X o-parental-control 2>/dev/null
}

check_cron_status()
{
    if [ ${parental_control_enabled} -eq 1 ]; then
        _rm_and_add_cron_script "${SCRIPT}" "$(_create_cron_entries)"
    else
        _rm_cron_script "${SCRIPT}"
    fi
    _cron_restart
}

handle_global() {
    local cfg="$1"
    config_get_bool parental_control_enabled "$cfg" enabled 0
    config_get_bool LOGGING "$cfg" logging 0
}

handle_entry() {
    local cfg="$1"; shift
    local curTime="$1"; shift
    local weekday="$1"; shift
    local blocked_var="$1"; shift
    local allowed_var="$1"; shift
    local list_var
    local entry_enabled starttime stoptime stations dow

    config_get_bool entry_enabled "$cfg" enabled 0
    # Check if current entry is enabled
    [ "$entry_enabled" -eq 0 ] && return 0

    # lowercase:
    stations="$(config_get "$cfg" stations | tr ABCDEF abcdef)"
    config_get dow "$cfg" daysofweek
    [ -z "$dow" ] && return 1
    config_get starttime "$cfg" starttime
    [ -z "$starttime" ] && return 1
    config_get stoptime "$cfg" stoptime
    [ -z "$stoptime" ] && return 1

    # Check if current entry is relevant for today
    echo "$dow" | grep "$weekday" > /dev/null || return 0

    #remove :, so that we can make easy integer comparison
    starttime=${starttime//:}
    stoptime=${stoptime//:}

    if [ $curTime -ge $starttime ] && [ $curTime -lt $stoptime ]; then
        list_var=$allowed_var
    else
        list_var=$blocked_var
    fi

    # add or delete rules to the list of stations
    local st
    for st in $stations; do
        ! list_contains $list_var $st && append $list_var $st
    done
}

# evaluate if a MAC address is allowed at the current time of day
check_and_apply_parental_control_state() {
    local curTime=$(date "+%k%M")
    local weekday=$(date "+%A")

    if [ "$parental_control_enabled" -eq 0 ]; then
        delete_chains
    else
        local blocked_macs allowed_macs st stations
        create_chains
        # collect which MACs are allowed or blocked by a config entry in separate lists
        config_foreach handle_entry entry "$curTime" "$weekday" blocked_macs allowed_macs
        # allowed wins
        for st in $blocked_macs; do
            ! list_contains allowed_macs $st && append stations $st
        done
        replace_rules "$stations"
    fi
}

usage()
{
    echo ""
    echo "$0 cron|check|help"
    echo ""
    echo "    UCI Config File: /etc/config/${PACKAGE}"
    echo ""
    echo "    cron: Create cronjob entries."
    echo "    check: Check if stations should get access to the network or should be blocked"
    echo "    help: This description."
    echo ""
}

###############################################################################
# MAIN
###############################################################################
config_load "${PACKAGE}"
config_foreach handle_global global

_log ${SCRIPT} $1 $2
lock ${LOCKFILE}

case "$1" in
    cron) check_cron_status ;;
    check) check_and_apply_parental_control_state;;
    help|--help|-h|*) usage ;;
esac

_exit 0
