#!/bin/sh

po_domain="alterator-ulogd"
alterator_api_version=1

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

LOGFILE="/var/log/configd.log"
EFW="/etc/net/scripts/contrib/efw"
IP="/sbin/ip"
SQLITE3DB="/var/lib/ulogd/sqlite3.db"
SQLITE3="/usr/bin/sqlite3"
PROTOCOLS="/etc/protocols"
ULOGD_CONF=/etc/ulogd.conf
CHKCONFIG=/sbin/chkconfig
SERVICE=/sbin/service
ULOGD_PLUGIN_REGEXP='plugin=\".*ulogd_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" ulogd status)" = 'ulogd is running' ] &&
    grep -qs "^$ULOGD_PLUGIN_REGEXP" "$ULOGD_CONF"
}

ulogd_state()
{
    local sc= dc= scom= ccom=
    [ -n "$1" ] || return
    if [ "$1" = '#f' ]; then
        ! is_ulogd_enabled && return
        dc='#'
        scom=stop
        ccom=off
    else
        is_ulogd_enabled && return
        sc='#'
        scom=start
        ccom=on
    fi
    "$CHKCONFIG" ulogd "$ccom" 2>/dev/null
    sed -i "s;^[[:blank:]]*$sc[[:blank:]]*\($ULOGD_PLUGIN_REGEXP\);$dc\1;" "$ULOGD_CONF" 2>/dev/null
    iptables_helper ulog "$ccom"
    "$SERVICE" ulogd "$scom" &>/dev/null
}

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

