#!/bin/sh

po_domain="alterator-ulogd"
alterator_api_version=1

. alterator-sh-functions
. shell-config
. alterator-net-functions
. alterator-service-functions

LOGFILE="/var/log/configd.log"
EFW="/etc/net/scripts/contrib/efw"
IP="/sbin/ip"
SQLITE3DB="/var/lib/ulogd/alterator_sqlite3.db"
SQLITE3="/usr/bin/sqlite3"
PROTOCOLS="/etc/protocols"
ULOGD_CONF=/etc/ulogd.conf
ULOGD_PLUGIN_REGEXP='plugin=\".*ulogd_output_SQLITE3\.so\"'

make_port_proto_pair()
{
    local port="${1#*:}"
    local proto="$(grep -is "^${1%:*}" "$PROTOCOLS" | cut -f2)"

    [ -n "$port" -a -n "$proto" ] && echo "${2}port=$port AND protocol=$proto"
}

proto_to_num()
{
    grep -is "^$1" "$PROTOCOLS" | cut -f2
}

print_bytes()
{
    [ -n "$1" ] || echo '?'
    echo -n "$1" | awk -v d=1024.0 '{printf "%0.1f\n", $1/d}'
}

read_ip()
{
    [ -n "$1" ] && netdev_read_ip "$1" | sed 's;/.*;;'
}

is_ulogd_enabled()
{
    service_control ulogd is-active &&
    grep -qs "^$ULOGD_PLUGIN_REGEXP" "$ULOGD_CONF"
}

ulogd_state()
{
    local sc= dc= scom= ccom=
    [ -n "$1" ] || return
    if test_bool "$1"; then
        is_ulogd_enabled && return 0
        service_control ulogd start &&
        service_control ulogd on &&
        iptables_helper ulog on
    else
        is_ulogd_enabled || return 0
        service_control ulogd stop &&
        service_control ulogd off &&
        iptables_helper ulog off
    fi
}

read_table()
{
    local bytes=
    [ -n "$1" ] || return

    local errors="$(mktemp -t alterator-ulogd_sqlerror.XXXXXXXX)"

    while :;
    do
        bytes="$("$SQLITE3" -batch "$SQLITE3DB" "$1" 2>"$errors")"
        [ "$?" -eq 0 -o "$(cat "$errors")" != 'SQL error: database is locked' ] && break
    done
    /bin/rm "$errors"
    echo "${bytes:-0}"
}

pairs_for_services()
{
    local pairs=
    local not_first=
    pairs="("
    for p in $(iptables_helper list | cut -f2); do
        for i in $(echo "$p" | tr ';' ' '); do
            local str="$(make_port_proto_pair "$i" "$1")"
            [ -n "$str" ] || continue
            if [ -z "$not_first" ]; then
                pairs="${pairs}$str"
                not_first=1
            else
                pairs="$pairs OR $str"
            fi
        done
    done

    pairs="$pairs)"
    echo "$pairs"
}

read_daily_table()
{
    local iface="$1"; shift
    local direction="$1"; shift
    local start_date="$1"; shift
    local stop_date="$1"; shift
    local pairs="$1"; shift
    local ip="$1"; shift
    local total="$1";shift
    local sql= d= c=
    local bytes=

    case "$direction" in
        in)
        d=d
        c=i
        ;;
        out)
        d=s
        c=o
        ;;
    esac

    start_date="$(date -u --date="$start_date" +%s)"
    end_date="$(date -u --date="$end_date" +%s)"

    if [ -z "$total" ]; then
        sql="SELECT ${d}port,protocol,SUM(bytes) FROM ulog_daily WHERE iface='$iface' AND prefix='${c}count' \
                                                 AND time>=$start_date  AND time<=$end_date"
    else
        sql="SELECT SUM(bytes) FROM ulog_daily WHERE iface='$iface' AND prefix='${c}count' AND time>=$start_date \
                                                 AND time<=$end_date"
    fi
    [ -n "$pairs" ] && sql="$sql AND $pairs"
    [ -n "$ip" ] && sql="$sql AND ${d}addr='$ip'"

    [ -z "$total" ] && sql="$sql group by ${d}port,protocol"

#    echo "sql: $sql" >>/tmp/debug-sql.log
    read_table "$sql;"
}

read_date()
{
    [ -n "$1" -a -n "$2" -a "$2" != '#f' ] || return

    local date_sec="$(read_table "SELECT $1(time) FROM ulog_daily WHERE iface='$2';")"
    date -u --date="@$date_sec" +%F 2>/dev/null
}

read_start_date()
{
    read_date MIN "$1"
}

read_end_date()
{
    read_date MAX "$1"
}

calc_sum()
{
    local pairs=
    for i in $(echo "$1" | tr ';' ' '); do
        if [ -z "$pairs" ]; then
            pairs="${i##*:}|$(proto_to_num ${i%%:*})"
        else
            pairs="$pairs\|${i##*:}|$(proto_to_num ${i%%:*})"
        fi
     done

     echo "$2" | sed -n "s;^\($pairs\)|;;p" |
            awk 'BEGIN {s=0}
            {s = s + $1}
            END {print s}'
}


on_message()
{
	case "$in_action" in
		type)
        write_type_item start_date date
        write_type_item end_date date
		;;
		read)
		case "$in__objects" in
			/)
            local start_date="$(read_start_date "$in_iface")"
            local end_date="$(read_end_date "$in_iface")"
            local state=
            write_string_param start_date "${start_date:-$(date -u +%F)}"
            write_string_param end_date "${end_date:-$(date -u +%F)}"
            is_ulogd_enabled && state=on || state=off
            write_bool_param state_enabled "$state"
            ;;
		esac
		;;
        write)
        ulogd_state "$in_state_enabled"
        ;;
		list)
		case "${in__objects##*/}" in
            services)
            local ip=
            if [ -n "$in_iface" ]; then
                [ -n "$in_checkip" -a "$in_checkip" != '#f' ] && ip="$(read_ip "$in_iface")"
                if [ -n "$in_start_date" -a -n "$in_end_date" -a "$in_start_date" != '#f' -a "$in_end_date" != '#f' ]; then
                    local pairs_in="$(pairs_for_services d)"
                    local pairs_out="$(pairs_for_services s)"
                    local data_in="$(read_daily_table "$in_iface" in "$in_start_date" "$in_end_date" "$pairs_in" "$ip")"
                    local data_out="$(read_daily_table "$in_iface" out "$in_start_date" "$in_end_date" "$pairs_out" "$ip")"
                    local sumin_other="$(read_daily_table "$in_iface" in "$in_start_date" "$in_end_date" \
                                                          "NOT $pairs_in" "$ip" 1)"
                    local sumout_other="$(read_daily_table "$in_iface" out "$in_start_date" "$in_end_date" \
                                                           "NOT $pairs_out" "$ip" 1)"
                    local totalin="$(read_daily_table "$in_iface" in "$in_start_date" "$in_end_date" "" "$ip" 1)"
                    local totalout="$(read_daily_table "$in_iface" out "$in_start_date" "$in_end_date" "" "$ip" 1)"

                    local s_ifs="$IFS"
                    local IFS=$'
'

                    set_locale
                    for str in $(IFS="$s_ifs";iptables_helper list | cut -f2,3 | tr '\t' '|'); do
                        local IFS="$s_ifs"
                        local sumin="$(calc_sum "${str%%|*}" "$data_in")"
                        local sumout="$(calc_sum "${str%%|*}" "$data_out")"

                        write_table_item description "${str##*|}" \
                                         in "$(print_bytes "$sumin")" \
                                         out "$(print_bytes "$sumout")"
                    done
                    write_table_item description "`_ "Other"`" \
                                     in "$(print_bytes "$sumin_other")" \
                                     out "$(print_bytes "$sumout_other")"
                    write_table_item description "`_ "Total"`" \
                                     in "$(print_bytes "$totalin")" \
                                     out "$(print_bytes "$totalout")"
                fi
            fi
            ;;
            avail_ifaces)
            for iface in $(list_iface); do
                local iface_ip="$(read_ip "$iface")"
                write_enum_item "$iface" "$iface${iface_ip:+ - }$iface_ip"
            done
            ;;
		esac
		;;
	esac
}

message_loop

