#!/bin/sh

module_name=alterator-snort
po_domain="$module_name"
alterator_api_version=1

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

CONFDIR=/etc/snort
CONFIG="$CONFDIR/snort.conf"
RULESDIR=
RULESET_BEGIN_DEFAULT_TEMPLATE='# Include all relevant rulesets here'
RULESET_BEGIN_ALTERATOR_SNORT_TEMPLATE="# Rulesets list. Generated by $module_name."
BARNYARDCONF=/etc/barnyard2/barnyard2.conf
MYSQL_PASSWD="$(sed -n 's;^output[[:blank:]]\+database:.*mysql.*password=\([^[:blank:]]\+\).*;\1;p' "$BARNYARDCONF")"
MYSQL_COMM="/usr/bin/mysql --skip-column-names --batch -u snort --password='$MYSQL_PASSWD' snort"
SNORT=/usr/sbin/snort
RULES_URL='http://www.snort.org/pub-bin/oinkmaster.cgi'
RULES_FILENAME=snortrules-snapshot
RULES_EXT=tar.gz
OINKMASTER=/usr/bin/oinkmaster
OINKMASTER_CONF=/etc/oinkmaster.conf
CRON_FILE=/etc/cron.d/alterator-snort
NOTIFICATIONS_CRON_FILE=/etc/cron.d/alterator-snort-notifications
ALTERATOR_SNORT_CONF=/etc/sysconfig/alterator-snort
SNORT_CONF=/etc/sysconfig/snort
BARNYARD2_CONF=/etc/sysconfig/barnyard2
CREATE_SIDMAP=/usr/share/doc/oinkmaster-2.0/contrib/create-sidmap.pl
JAIL2BAN_JAIL_DIR=/etc/fail2ban/jail.d
JAIL2BAN_FILTER_DIR=/etc/fail2ban/filter.d
SNORT_CLASSIFICATION_FILE=/etc/snort/classification.config
IPTABLES_SAVE=/sbin/iptables-save
NSLOOKUP=/usr/bin/nslookup
FAIL2BAN_CLIENT=/usr/bin/fail2ban-client
BASE_ROLE_ID='1'
if egrep -qs '^config[[:blank:]]+utc[[:blank:]]*$' "$BARNYARDCONF"; then
    BARNYARD_TIME=utc
else
    BARNYARD_TIME=localtime
fi

###

list_weekday()
{
    write_enum_item "1" "`_ "monday"`"
    write_enum_item "2" "`_ "tuesday"`"
    write_enum_item "3" "`_ "wednesday"`"
    write_enum_item "4" "`_ "thursday"`"
    write_enum_item "5" "`_ "friday"`"
    write_enum_item "6" "`_ "saturday"`"
    write_enum_item "0" "`_ "sunday"`"
}

set_rules_dir()
{
    local rules_path=

    [ -r "$CONFIG" ] || return
    rules_path="$(sed -n 's;^var[[:blank:]]\+RULE_PATH[[:blank:]]\+\(.\+\)$;\1;p' "$CONFIG")"
    [ -n "$rules_path" ] && RULESDIR="$rules_path"
}

list_rules()
{
    sed -n "s;^[[:blank:]]*include[[:blank:]]\+\$RULE_PATH/\(.*\).rules.*$;\1;p" "$CONFIG" 
}

list_rules_unused()
{
    for rule in $(ls -1 "$RULESDIR" | sed -n 's;^\(.\+\)\.rules$;\1;p'); do
        if grep -qs "^[[:blank:]]*include[[:blank:]]\$RULE_PATH/$rule.rules" "$CONFIG" || \
           ! grep -qs '^[^#]' "$RULESDIR/$rule.rules"; then
            continue
        fi
        echo "$rule"
    done
}

read_rule_description()
{
    local id_str='^#[[:blank:]]*\$Id:'
    [ -n "$1" ] || return

    sed -n "/$id_str/,/^[^#]/ { /$id_str/d; s|^#[[:blank:]]*\(.*\)$|\1|p }" "$RULESDIR/$1.rules"
}

rules_list_to_ruleset()
{
    [ -n "$1" ] || return
    local IFS=';'
    local ruleset_str=
    for i in $1; do
        ruleset_str="$ruleset_str${ruleset_str:+\n}include \$RULE_PATH/$i.rules"
    done
    echo "$ruleset_str"
}

write_ruleset()
{
    local rule_regexp='^#*[[:blank:]]*include[[:blank:]]\+\$RULE_PATH/.*\.rules.*$'


    if ! grep -qs "^$RULESET_BEGIN_ALTERATOR_SNORT_TEMPLATE" "$CONFIG"; then
        sed -i "\|$RULESET_BEGIN_DEFAULT_TEMPLATE|,\|$rule_regexp| { \|$rule_regexp|i$RULESET_BEGIN_ALTERATOR_SNORT_TEMPLATE
            }" "$CONFIG"
        grep -qs "^$RULESET_BEGIN_ALTERATOR_SNORT_TEMPLATE" "$CONFIG" ||
        printf "\n$RULESET_BEGIN_ALTERATOR_SNORT_TEMPLATE" >>"$CONFIG"
    fi  
    sed -i "\|$rule_regexp|d" "$CONFIG"

    [ -n "$1" ] || return
    sed -i "\|^$RULESET_BEGIN_ALTERATOR_SNORT_TEMPLATE|a$1" "$CONFIG"
}

num_to_ipv4addr()
{
    num="$1"; shift

    printf '%s.%s.%s.%s\n' \
        "$(($num >> 24 & 0xff))" \
        "$(($num >> 16 & 0xff))" \
        "$(($num >> 8 & 0xff))" \
        "$(($num & 0xff))"
}

utc_to_local_time()
{
    if [ "$BARNYARD_TIME" = utc ]; then
        date --date=$1+00:00 +'%F %T'
    else
		echo "$1"
    fi
}

local_to_utc_time()
{
    if [ "$BARNYARD_TIME" = utc ]; then
        local z="$(date +%z)"
        date -u --date="$1$z" +'%F %T'
    else
		echo "$1"
    fi
}

get_sig_name()
{
    echo "SELECT sig_name FROM signature WHERE sig_id=$1;" | eval "$MYSQL_COMM"
}

list_events()
{
    local start_date="$1"; shift
    local start_time="$1"; shift
    local end_date="$1"; shift
    local end_time="$1"; shift
    local iface= sid= sign_name=
    local start_datetime="$(local_to_utc_time "$start_date $start_time")"
    local end_datetime="$(local_to_utc_time "$end_date $end_time")"
    local IFS=$'\n'

    for i in $(echo "SELECT sid,interface FROM sensor;" | eval "$MYSQL_COMM"); do
        iface="$(echo "$i" | cut -f2)"
        sid="$(echo "$i" | cut -f1)"
        for j in $(echo "SELECT signature,COUNT(cid),MAX(timestamp) FROM event WHERE sid=$sid AND \
                         timestamp>='$start_datetime' AND timestamp<='$end_datetime' \
                         GROUP BY signature ORDER BY COUNT(cid) DESC;" | eval "$MYSQL_COMM"); do
            sig_id="$(echo "$j" | cut -f1)"
            count="$(echo "$j" | cut -f2)"
            [ "$count" -gt 0 ] || continue
            last_event_time="$(echo "$j" | cut -f3)"
            sign_name="$(get_sig_name "$sig_id")"
            write_table_item \
                name "$sid/$sig_id" \
                iface "$iface" \
                description "$sign_name" \
                count "$count" \
                last_event_time "$(utc_to_local_time "$last_event_time")"
        done
    done
}

is_snortd_enabled()
{
    service_control snortd status
}

is_mysqld_enabled()
{
    service_control mysqld status
}

is_barnyard2_enabled()
{
    service_control barnyard2 status
}

read_state()
{
    if is_snortd_enabled; then
        write_bool_param state_enabled true
    else
        write_bool_param state_enabled false
    fi
}

read_dates()
{
    local curr_date="$(date +%F 2>/dev/null)"

    write_string_param start_date "$curr_date"
    write_string_param start_time '00:00:00'
    write_string_param end_date "$curr_date"
    write_string_param end_time '23:59:59'
}

write_state()
{
    # stanv@: Always do STOP services.
    # Reason to do: We have done some configuration. But, barnyard & fail2ban are active. Result: they run with old configuration.
    service_control snortd off
    service_control snortd stop
    service_control barnyard2 off
    service_control barnyard2 stop
    service_control fail2ban off
    service_control fail2ban stop

    # stanv@: Long story.
    # Up for this moment snort should be stopped.
    # But, it may still runs.
    # How? Easy! We add listen interface and remove listen interface in same time.
    # Snort will listen old interface. And "services" scripts will ignore them. As new config do not have entry for old interface.
    # Same for Barnyard2
    killall /usr/sbin/snort
    killall /usr/bin/barnyard2

    if [ "$1" = '#t' ]; then
            service_control mysqld on
            service_control snortd on
            service_control barnyard2 on
            service_control fail2ban on
            if ! is_mysqld_enabled; then
                service_start_wait mysqld start
            fi
            service_start_wait snortd start
            if ! service_control snortd status; then
                # EXIT
                # may be still active on some interfaces, only part interfaces may fail to start
                service_control snortd stop
                write_error "$(_ "Cannot start Snort service. Go to 'System log' module to find the reason.")"
                return
            fi
            if ! is_barnyard2_enabled; then
                sleep 1
                service_control barnyard2 start
            fi
            service_control fail2ban start
    fi
}

list_details()
{
    local list="$1"; shift
    local start_date="$1"; shift
    local start_time="$1"; shift
    local end_date="$1"; shift
    local end_time="$1"; shift
    local IFS=$'\n'
    local IFS="$IFS;"
    local iface= sid= sig_id= sig_name= source_ip= dest_ip= min_time= max_time=
    local start_datetime="$(local_to_utc_time "$start_date $start_time")"
    local end_datetime="$(local_to_utc_time "$end_date $end_time")"

    for i in $list; do
        [ "$i" = on ] && continue
        sid="${i%/*}"
        sig_id="${i#*/}"
        iface="$(echo "SELECT interface FROM sensor WHERE sid=$sid;" | eval "$MYSQL_COMM")"
        sig_name="$(get_sig_name "$sig_id")"

        for tmp in $(echo "SELECT COUNT(event.cid),MIN(event.timestamp),MAX(event.timestamp),iphdr.ip_src,iphdr.ip_dst \
                FROM event,iphdr WHERE event.sid=$sid AND iphdr.sid=$sid AND event.signature=$sig_id \
                AND event.cid=iphdr.cid AND event.timestamp>='$start_datetime' \
                AND event.timestamp<='$end_datetime' \
                GROUP BY iphdr.ip_src,iphdr.ip_dst ORDER BY COUNT(event.cid) DESC;" | eval "$MYSQL_COMM"); do
            count="$(echo "$tmp" | cut -f1)"
            min_time="$(echo "$tmp" | cut -f2)"
            max_time="$(echo "$tmp" | cut -f3)"
            source_ip="$(echo "$tmp" | cut -f4)"
            dest_ip="$(echo "$tmp" | cut -f5)"
            write_table_item \
                iface "$iface" \
                description "$sig_name" \
                source_ip "$(num_to_ipv4addr "$source_ip")" \
                dest_ip "$(num_to_ipv4addr "$dest_ip")" \
                count "$count" \
                min_time "$(utc_to_local_time "$min_time")" \
                max_time "$(utc_to_local_time "$max_time")"
        done
    done
}    

check_oinkconf_url()
{
    grep -qs "^[[:blank:]]*url[[:blank:]]*=" "$OINKMASTER_CONF"
}

write_oinkconf_url()
{
    url="$1"; shift

    [ -n "$url" ] || return

    if check_oinkconf_url; then
        sed -i "s;^[[:blank:]]*url[[:blank:]]*=.*$;url = $url;" "$OINKMASTER_CONF"
    else
        echo "url = $url" >>"$OINKMASTER_CONF"
    fi
}

read_oinkconf_url()
{
    sed -n "s;^[[:blank:]]*url[[:blank:]]*=[[:blank:]]*\(.*\)#*.*$;\1;p" "$OINKMASTER_CONF"
}

write_oinkcode()
{
    local oinkcode="$1"; shift
    local snort_version="$($SNORT -V 2>&1 | sed -n 's;^.*Version \([1-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+\).*$;\1;p' | sed 's/\.//g')"
    local url=

    [ -n "$snort_version" -a -n "$oinkcode" ] || return
    url="$RULES_URL/$oinkcode/$RULES_FILENAME-$snort_version.$RULES_EXT"

    write_oinkconf_url "$url"
}

read_oinkcode()
{
    sed -n "s;^[[:blank:]]*url[[:blank:]]*=[[:blank:]]*$RULES_URL/\(.*\)/.*$;\1;p" "$OINKMASTER_CONF"
}

read_cron_data()
{
    if [ -s "$CRON_FILE" ] ;then
        write_bool_param "auto_update" true
        while read min hour monthday month weekday rest;do
            [ -n "${min%\#*}" ] || continue

            write_string_param "time" "$hour:$min:00"
            if [ "$monthday" = "*" -a "$month" = "*" -a "$weekday" = "*" ]; then
                write_string_param "period" "daily"
            elif [ "$monthday" = "*" -a "$month" = "*" -a "$weekday" != "*" ];then
                write_string_param "period" "weekly"
                write_string_param "weekday" "$weekday"
            elif [ "$monthday" != "*" -a "$month" = "*" -a "$weekday" = "*" ]; then
                write_string_param "period" "monthly"
                write_string_param "monthday" "$monthday"
            else
                write_string_param "period" "daily"
            fi
            return
        done <"$CRON_FILE"
    else
        write_bool_param "auto_update" false
        write_string_param "time" "02:00:00"
        write_string_param "period" "daily"
    fi
}

write_cron_data()
{
    if test_bool "$in_auto_update"; then
        if [ "$in_period" = "weekly" -a -z "$in_weekday" ]; then
            write_error "`_ "Day of week should be selected"`"
            return
        fi

        if [ "$in_period" = "monthly" -a -z "$in_monthday" ];then
            write_error "`_ "Day of month should be defined"`"
            return
        fi

        in_time="${in_time%:*}"
        local hour="${in_time%:*}"
        local min="${in_time#*:}"
        local cmd="$OINKMASTER -Q -U $CONFIG -o $RULESDIR && $CREATE_SIDMAP $RULESDIR > $CONFDIR/sid-msg.map && /sbin/service snortd condreload"

        local tmp="$(mktemp "$CRON_FILE.XXXXXXXXXX")"
        if [ -z "$tmp" ]; then
            write_error "`_ "Unable to create temp file"`"
            return
        fi

        printf "#autogenerated by alterator-snort\n" >"$tmp"
        case "$in_period" in
            daily)
            printf '%s %s * * * root %s\n' "$min" "$hour" "$cmd"
            ;;
            weekly)
            printf '%s %s * * %s root %s\n' "$min" "$hour" "$in_weekday" "$cmd"
            ;;
            monthly)
            printf '%s %s %s * * root %s\n' "$min" "$hour" "$in_monthday" "$cmd"
            ;;
        esac >>"$tmp"
        mv -f "$tmp" "$CRON_FILE"
    else
        rm -f "$CRON_FILE"
    fi
}

download_rules()
{
    if ! check_oinkconf_url; then
        write_error "`_ "URL not specified"`"
        return 1
    fi

    "$OINKMASTER" -Q -U $CONFIG -o "$RULESDIR"
    if [ "$?" -ne 0 ];
    then
        write_error "`_ "Download rules failed"`"
        return 2
    fi

    "$CREATE_SIDMAP" "$RULESDIR" > "$CONFDIR/sid-msg.map"
    if [ "$?" -ne 0 ];
    then
        write_error "`_ "Create sid-msg failed"`"
        return 3
    fi

    service_control snortd condreload
}

reload_snort()
{
    if ! is_snortd_enabled; then
        # Snort is OFF. Nothing to check.
        return
    fi

    write_state "#t"
}

reload_fail2ban()
{
	service_control fail2ban reload
}

pass_generate()
{
	write_string_param passwd_auto "$(pwqgen)"
}

get_class_by_name()
{
	name="$1"
	[ -n "$name" ] || return
	[ -r "$SNORT_CLASSIFICATION_FILE" ] || return

	if ! cat "$SNORT_CLASSIFICATION_FILE" | grep -e "^config classification:.*,$name," >/dev/null 2>&1;
	then
		return
	fi
	cat "$SNORT_CLASSIFICATION_FILE" | grep -e "^config classification:.*,$name," | sed 's/^config classification: //' | sed 's/,.*$//'
}

get_name_by_class()
{
	class="$1"
	[ -n "$class" ] || return
	[ -r "$SNORT_CLASSIFICATION_FILE" ] || return

	if ! cat "$SNORT_CLASSIFICATION_FILE" | grep -e "^config classification: $class," >/dev/null 2>&1;
	then
		return
	fi
	cat "$SNORT_CLASSIFICATION_FILE" | grep -e "^config classification: $class," | sed "s/^config classification: $class,//" | sed 's/,.*$//'
}

rules_add()
{
	[ -n "$in_newrules" ] || return

	listrules=$in_newrules

	for i in $(list_rules);
	do
		if [ -z "$listrules" ];
		then
			listrules="$i"
		else
			listrules="$listrules;$i"
		fi
	done

	write_ruleset "$(rules_list_to_ruleset "$listrules")"
}

rules_remove()
{
	[ -n "$in_delrules" ] || return

	listrules=""
	delrules=";$in_delrules;"

	for i in $(list_rules);
	do
		if echo "$delrules" | grep -q -s ";$i;"; then
            # skip rule $i
            continue
        else
            # leave rule $i"
            :
        fi

		if [ -z "$listrules" ];
		then
			listrules="$i"
		else
			listrules="$listrules;$i"
		fi
	done

	write_ruleset "$(rules_list_to_ruleset "$listrules")"
}

notifications_check_db()
{
	echo "CREATE TABLE IF NOT EXISTS alterator_email (id INT UNSIGNED NOT NULL AUTO_INCREMENT, email TEXT, PRIMARY KEY (id));" | eval "$MYSQL_COMM"
	echo "CREATE TABLE IF NOT EXISTS alterator_notifications (id INT UNSIGNED NOT NULL AUTO_INCREMENT, email_id INT UNSIGNED NOT NULL, sig_class_id INT UNSIGNED NOT NULL, PRIMARY KEY (id));" | eval "$MYSQL_COMM"
}

notifications_list_email()
{
	notifications_check_db

	local IFS=$'\n'

	for i in $(echo "SELECT id, email FROM alterator_email;" | eval "$MYSQL_COMM"); do
		id="$(echo "$i" | cut -f1)"
		email="$(echo "$i" | cut -f2)"

		write_enum_item "$id" "$email"
	done
}

notifications_create_email()
{
	[ -n "$in_add_email" ] || return

	id="$(echo "SELECT id FROM alterator_email WHERE email='$in_add_email';" | eval "$MYSQL_COMM")"
	[ -z "$id" ] || return

	echo "INSERT INTO alterator_email(email) VALUES ('$in_add_email');" | eval "$MYSQL_COMM"
}

notifications_in_types()
{
	local IFS=$'\n'

	[ -n "$in_email" ] || return

	for i in $(echo "SELECT notif.sig_class_id, sig.sig_class_name FROM alterator_notifications notif LEFT JOIN sig_class sig ON sig.sig_class_id=notif.sig_class_id WHERE notif.email_id='$in_email';" | eval "$MYSQL_COMM"); do
		id="$(echo "$i" | cut -f1)"
		name="$(echo "$i" | cut -f2)"

		write_enum_item "$id" "$(get_name_by_class "$name")"
	done
}

notifications_out_types()
{
	local IFS=$'\n'

	[ -n "$in_email" ] || return

	for i in $(echo "SELECT sig_class_id, sig_class_name FROM sig_class WHERE sig_class_id NOT IN (SELECT sig_class_id FROM alterator_notifications WHERE email_id='$in_email');" | eval "$MYSQL_COMM"); do
		id="$(echo "$i" | cut -f1)"
		name="$(echo "$i" | cut -f2)"

		write_enum_item "$id" "$(get_name_by_class "$name")"
	done
}

notifications_destroy_email()
{
	[ -n "$in_email" ] || return

	echo "DELETE FROM alterator_email WHERE id='$in_email';" | eval "$MYSQL_COMM"
	echo "DELETE FROM alterator_notifications WHERE email_id='$in_email';" | eval "$MYSQL_COMM"
}

notifications_add_type()
{
	[ -n "$in_email" ] || return
	[ -n "$in_newtype" ] || return

	(IFS=';'; for id in $in_newtype
	do
		echo "INSERT INTO alterator_notifications(email_id, sig_class_id) VALUES ('$in_email', '$id');" | eval "$MYSQL_COMM"
	done)
}

notifications_del_type()
{
	[ -n "$in_email" ] || return
	[ -n "$in_deltype" ] || return

	(IFS=';'; for del in $in_deltype
	do
		echo "DELETE FROM alterator_notifications WHERE email_id='$in_email' AND sig_class_id='$del';" | eval "$MYSQL_COMM"
	done)
}

notifications_read_data()
{
	if [ -s "$NOTIFICATIONS_CRON_FILE" ];
	then
		while read min hour monthday month weekday rest;
		do
			[ -n "${min%\#*}" ] || continue

			if [ "$hour" = "*" ];
			then
				write_string_param "time" "02:00:00"
				if [ "$min" = "0,15,30,45" ]; then
					write_string_param "period" "minute15"
				elif [ "$min" = "0,30" ]; then
					write_string_param "period" "minute30"
				elif [ "$min" = "0" ]; then
					write_string_param "period" "hourly"
				fi
			else
				write_string_param "time" "$hour:$min:00"
				if [ "$monthday" = "*" -a "$month" = "*" -a "$weekday" = "*" ]; then
					write_string_param "period" "daily"
				elif [ "$monthday" = "*" -a "$month" = "*" -a "$weekday" != "*" ]; then
					write_string_param "period" "weekly"
					write_string_param "weekday" "$weekday"
				elif [ "$monthday" != "*" -a "$month" = "*" -a "$weekday" = "*" ]; then
					write_string_param "period" "monthly"
					write_string_param "monthday" "$monthday"
				else
					write_string_param "period" "daily"
				fi
			fi
			return
		done < "$NOTIFICATIONS_CRON_FILE"
	else
		write_string_param "time" "02:00:00"
	fi
}

notifications_write_data()
{
	[ -n "$in_period" ] || return

	if [ "$in_period" = "weekly" -a -z "$in_weekday" ];
	then
		write_error "`_ "Day of week should be selected"`"
		return
	fi

	if [ "$in_period" = "monthly" -a -z "$in_monthday" ];
	then
		write_error "`_ "Day of month should be defined"`"
		return
	fi

	in_time="${in_time%:*}"
	local hour="${in_time%:*}"
	local min="${in_time#*:}"
	local cmd="/usr/lib/alterator-snort/snort-email-notification.sh"

	local tmp="$(mktemp "$NOTIFICATIONS_CRON_FILE.XXXXXXXXXX")"
	if [ -z "$tmp" ];
	then
		write_error "`_ "Unable to create temp file"`"
		return
	fi

	printf "#autogenerated by alterator-snort\n" >"$tmp"
	case "$in_period" in
		minute15)
			printf '%s %s * * * root %s\n' "0,15,30,45" "*" "$cmd"
			;;
		minute30)
			printf '%s %s * * * root %s\n' "0,30" "*" "$cmd"
			;;
		hourly)
			printf '%s %s * * * root %s\n' "0" "*" "$cmd"
			;;
		daily)
			printf '%s %s * * * root %s\n' "$min" "$hour" "$cmd"
			;;
		weekly)
			printf '%s %s * * %s root %s\n' "$min" "$hour" "$in_weekday" "$cmd"
			;;
		monthly)
			printf '%s %s %s * * root %s\n' "$min" "$hour" "$in_monthday" "$cmd"
			;;
	esac >>"$tmp"
	mv -f "$tmp" "$NOTIFICATIONS_CRON_FILE"
}

notifications_read_sender()
{
	[ -r "$ALTERATOR_SNORT_CONF" ] || return

	email=$(cat $ALTERATOR_SNORT_CONF | sed -n "/^NOTIFICATIONS_SENDER=/p" | sed "s/^NOTIFICATIONS_SENDER=//")

	write_string_param "sender" "$email"
}

notifications_apply_sender()
{
	[ -w "$ALTERATOR_SNORT_CONF" ] || return

	sed -i "/^NOTIFICATIONS_SENDER=/d" "$ALTERATOR_SNORT_CONF"
	echo "NOTIFICATIONS_SENDER=$in_sender" >> "$ALTERATOR_SNORT_CONF"
}

notifications_send_test_mail()
{
	[ -n "$in_email" ] || return
	[ -r "$ALTERATOR_SNORT_CONF" ] || return

	local IFS=$'\n'

	from_email=$(cat $ALTERATOR_SNORT_CONF | sed -n "/^NOTIFICATIONS_SENDER=/p" | sed "s/^NOTIFICATIONS_SENDER=//")
	to_email="$(echo "SELECT email FROM alterator_email WHERE id='$in_email';" | eval "$MYSQL_COMM")"

	echo "Subject: [alterator-snort] Test message"$'\n'$'\n'"Generated by alterator-snort" | /usr/sbin/sendmail -f "$from_email" "$to_email"
}

ban_add()
{
	if [ -z "${in_name##*[!0-9A-Za-z_.-]*}" ]; then
		write_error "`_ "Rule name is not valid"`"
		return
	fi

	filename_prefix="alterator-snort"
	filename_suffix=1
	while [ -e "$JAIL2BAN_JAIL_DIR/$filename_prefix-$filename_suffix.conf" -o -e "$JAIL2BAN_FILTER_DIR/$filename_prefix-$filename_suffix.conf" ];
	do
		filename_suffix=$(($filename_suffix + 1))
	done
	filename="$filename_prefix-$filename_suffix"

	cat << EOF > "$JAIL2BAN_JAIL_DIR/$filename.conf"
# Created by alterator-snort
# name: $in_name

[$filename]
enabled  = false
action   = iptables-allports[name=$filename,protocol=all]
filter   = $filename
bantime  = 600
findtime = 600
maxretry = 3
logpath  = /var/log/syslog/alert
EOF

	cat << EOF > "$JAIL2BAN_FILTER_DIR/$filename.conf"
# Created by alterator-snort
# name: $in_name

[Definition]
failregex =
ignoreregex =
EOF
}

ban_list()
{
	for filename in $(ls -1 "$JAIL2BAN_JAIL_DIR" | grep "^alterator-snort-.*.conf$" | sed 's/.conf$//');
	do
		[ -e "$JAIL2BAN_FILTER_DIR/$filename.conf" ] || continue
		rulename="$(cat $JAIL2BAN_JAIL_DIR/$filename.conf | grep -m 1 -e '^# name: ' | sed 's/^# name: //')"
		write_enum_item "$filename" "$rulename"
	done
}

ban_read()
{
	[ -n "$in_rule" ] || return
	[ -r "$JAIL2BAN_JAIL_DIR/$in_rule.conf" ] || return

	enabled="$(cat $JAIL2BAN_JAIL_DIR/$in_rule.conf | grep "^enabled" | cut -d'=' -f2 | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//')"
	bantime="$(cat $JAIL2BAN_JAIL_DIR/$in_rule.conf | grep "^bantime" | cut -d'=' -f2 | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//')"
	findtime="$(cat $JAIL2BAN_JAIL_DIR/$in_rule.conf | grep "^findtime" | cut -d'=' -f2 | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//')"
	maxretry="$(cat $JAIL2BAN_JAIL_DIR/$in_rule.conf | grep "^maxretry" | cut -d'=' -f2 | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//')"

	write_bool_param "upd_enabled" "$enabled"
	write_string_param "upd_bantime" "$bantime"
	write_string_param "upd_findtime" "$findtime"
	write_string_param "upd_maxretry" "$maxretry"
}

ban_read_in_classes()
{
	[ -n "$in_rule" ] || return
	[ -r "$JAIL2BAN_FILTER_DIR/$in_rule.conf" ] || return

	cat "$JAIL2BAN_FILTER_DIR/$in_rule.conf" | sed -n '/^failregex/,$p' | while read line;
	do
		if echo "$line" | grep -e '^failregex' >/dev/null 2>&1;
		then
			classname="$(echo "$line" | cut -d'=' -f2 | sed -e 's/.*Classification: //' | sed -e 's/\].*//')"
			[ -z "$classname" ] && continue
			class="$(get_class_by_name "$classname")"
			write_enum_item "$class" "$classname"
			continue
		fi
		echo "$line" | grep -e '^ignoreregex' >/dev/null 2>&1 && break
		classname="$(echo "$line" | sed -e 's/.*Classification: //' | sed -e 's/\].*//')"
		[ -z "$classname" ] && continue
		class="$(get_class_by_name "$classname")"
		write_enum_item "$class" "$classname"
	done
}

ban_read_out_classes()
{
	[ -n "$in_rule" ] || return
	[ -r "$JAIL2BAN_FILTER_DIR/$in_rule.conf" ] || return

	local IFS=$'\n'

	for i in $(echo "SELECT sig_class_id, sig_class_name FROM sig_class;" | eval "$MYSQL_COMM");
	do
		id="$(echo "$i" | cut -f1)"
		class="$(echo "$i" | cut -f2)"

		classname="$(get_name_by_class "$class")"
		cat "$JAIL2BAN_FILTER_DIR/$in_rule.conf" | sed -n '/^failregex/,$p' | grep "Classification: $classname" >/dev/null 2>&1 && continue

		write_enum_item "$class" "$classname"
	done
}

ban_write()
{
	[ -n "$in_rule" ] || return
	[ -w "$JAIL2BAN_JAIL_DIR/$in_rule.conf" ] || return

	if [ "$in_upd_enabled" = "#t" ];
	then
		sed -i "/^enabled[ \t]*=/{s/.*/enabled  = true/}" $JAIL2BAN_JAIL_DIR/$in_rule.conf
	else
		sed -i "/^enabled[ \t]*=/{s/.*/enabled  = false/}" $JAIL2BAN_JAIL_DIR/$in_rule.conf
	fi
	sed -i "/^bantime[ \t]*=/{s/.*/bantime  = $in_upd_bantime/}" $JAIL2BAN_JAIL_DIR/$in_rule.conf
	sed -i "/^findtime[ \t]*=/{s/.*/findtime = $in_upd_findtime/}" $JAIL2BAN_JAIL_DIR/$in_rule.conf
	sed -i "/^maxretry[ \t]*=/{s/.*/maxretry = $in_upd_maxretry/}" $JAIL2BAN_JAIL_DIR/$in_rule.conf
}

ban_write_in_classes()
{
	[ -n "$in_rule" ] || return
	[ -n "$in_new_classes" ] || return
	[ -w "$JAIL2BAN_FILTER_DIR/$in_rule.conf" ] || return

	local IFS=';'

	for new_class in $in_new_classes
	do
		classname="$(get_name_by_class "$new_class")"
		[ -z "$classname" ] && continue
		sed -i "/^failregex/a \ \ \ \ \ \ \ \ \ \ \ \ .*\\\[Classification: $classname\\\] \\\[Priority: .*\\\]: \\\{.*\\\} <HOST>:.*" $JAIL2BAN_FILTER_DIR/$in_rule.conf
	done
}

ban_write_out_classes()
{
	[ -n "$in_rule" ] || return
	[ -n "$in_del_classes" ] || return
	[ -w "$JAIL2BAN_FILTER_DIR/$in_rule.conf" ] || return

	local IFS=';'

	for del_class in $in_del_classes
	do
		classname="$(get_name_by_class "$del_class")"
		[ -z "$classname" ] && continue
		sed -i "/^\ \ \ \ \ \ \ \ \ \ \ \ .*Classification: $classname\\\].*<HOST>/d" $JAIL2BAN_FILTER_DIR/$in_rule.conf
	done
}

ban_remove()
{
	[ -n "$in_rule" ] || return

	rm -f "$JAIL2BAN_JAIL_DIR/$in_rule.conf"
	rm -f "$JAIL2BAN_FILTER_DIR/$in_rule.conf"
}

ban_banned_list()
{
	local IFS=$'\n'

	for line in $("$IPTABLES_SAVE" | grep -e '-A fail2ban-alterator-snort-' | grep -e '-j REJECT');
	do
		ip="$(echo "$line" | sed 's/.*-s //' | sed 's/ -.*//' | sed 's|/32||')"
		rule="$(echo "$line" | sed 's/.*-A fail2ban-//' | sed 's/ -.*//')"
		rulename="$(cat $JAIL2BAN_JAIL_DIR/$rule.conf | grep -m 1 -e '^# name: ' | sed 's/^# name: //')"
		hostname="$("$NSLOOKUP" "$ip" | grep -m 1 -e "name = " | sed 's/^.*name = //' | sed 's/\.$//')"
		write_table_item \
			name		"$rule|$ip" \
			banned_ip	"$ip" \
			banned_host	"$hostname" \
			banned_rule	"$rulename"
	done
}

ban_banned_unban()
{
	[ -n "$in_list" ] || return
	[ "$in_list" != '#f' ] || return

	local IFS=$'\n'
	local IFS="$IFS;"

	for i in $in_list;
	do
		rule="$(echo "$i" | cut -d'|' -f1)"
		ip="$(echo "$i" | cut -d'|' -f2)"

		"$FAIL2BAN_CLIENT" set "$rule" unbanip "$ip"
	done
}

base_user_add()
{
	if [ -z "$in_new_login" ];
	then
		write_error "`_ "Enter login"`"
		return
	fi
	if [ -z "$in_new_password" ];
	then
		write_error "`_ "Enter password"`"
		return
	fi

	id="$(echo "SELECT usr_id FROM base_users WHERE usr_login='$in_new_login';" | eval "$MYSQL_COMM")"
	[ -z "$id" ] || return

	max_id="$(echo "SELECT MAX(usr_id) FROM base_users;" | eval "$MYSQL_COMM")"

	echo "INSERT INTO base_users(usr_id, usr_login, usr_name, usr_pwd, role_id, usr_enabled) VALUES ('$[$max_id + 1]', '$in_new_login', '$in_new_name', MD5('$in_new_password'), '$BASE_ROLE_ID', '1');" | eval "$MYSQL_COMM"
}

base_user_list()
{
	local IFS=$'\n'

	for i in $(echo "SELECT usr_id,usr_login FROM base_users;" | eval "$MYSQL_COMM"); do
		usr_id="$(echo "$i" | cut -f1)"
		usr_login="$(echo "$i" | cut -f2)"
	
		write_enum_item "$usr_id" "$usr_login"
	done
}

base_user_data()
{
	[ -n "$in_user" ] || return

	local IFS=$'\n'

	query="$(echo "SELECT usr_name,role_id FROM base_users WHERE usr_id='$in_user';" | eval "$MYSQL_COMM")"
	[ -n "$query" ] || return

	usr_name="$(echo "$query" | cut -f1)"

	write_string_param "upd_name" "$usr_name"
}

base_user_apply()
{
	[ -n "$in_user" ] || return
	[ -n "$in_auto" ] || return

	id="$(echo "SELECT usr_id FROM base_users WHERE usr_id='$in_user';" | eval "$MYSQL_COMM")"
	[ -n "$id" ] || return

	passwd=
	if [ "$in_auto" = "#t" ];
	then
		passwd="$in_passwd_auto"
	elif [ "$in_passwd_1" != "$in_passwd_2" ];
	then
		write_error "`_ "Passwords mismatch"`"
		return
	else
		passwd="$in_passwd_1"
	fi

	if [ -n "$passwd" ];
	then
		echo "UPDATE base_users SET usr_name='$in_upd_name',role_id='$BASE_ROLE_ID',usr_pwd=MD5('$passwd') WHERE usr_id='$in_user';" | eval "$MYSQL_COMM"
	else
		echo "UPDATE base_users SET usr_name='$in_upd_name',role_id='$BASE_ROLE_ID' WHERE usr_id='$in_user';" | eval "$MYSQL_COMM"
	fi
}

base_user_del()
{
	[ -n "$in_user" ] || return

	echo "DELETE FROM base_users WHERE usr_id='$in_user';" | eval "$MYSQL_COMM"
}

config_ifaces_mode()
{
	[ -r "$SNORT_CONF" ] || return

	ifaces="$(cat "$SNORT_CONF" | grep "^INTERFACES=" | sed -e 's/^INTERFACES=//' | sed -e 's/^"//' | sed -e 's/"$//')"
	if [ "$ifaces" = "any" ];
	then
		write_string_param "interfaces" "any"
	else
		write_string_param "interfaces" "specific"
	fi
}

print_all_ifaces()
{
	local linenum=0
	netstat -i | egrep -v '^lo' | while read first_word unused_tail; do
		case $[++linenum] in
			1 | 2 ) ;;
			* ) echo $first_word ;;
		esac
	done
}

config_in_ifaces()
{
	[ -r "$SNORT_CONF" ] || return

	ifaces="$(cat "$SNORT_CONF" | grep "^INTERFACES=" | sed -e 's/^INTERFACES=//' | sed -e 's/^"//' | sed -e 's/"$//')"
	[ "$ifaces" = "any" ] && return

	local IFS=','
	for iface in $(echo "$ifaces");
	do
		iface_hw="$(cat "/sys/class/net/$iface/address")"
		iface_inet="$(ip addr show "$iface" | grep -o 'inet [0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' | grep -o [0-9].*)"
		iface_desc="$iface"
		[ -n "$iface_hw" ] && iface_desc="$iface_desc - $iface_hw"
		[ -n "$iface_inet" ] && iface_desc="$iface_desc ($iface_inet)"

		write_enum_item "$iface" "$iface_desc"
	done
}

config_out_ifaces()
{
	[ -r "$SNORT_CONF" ] || return

	ifaces="$(cat "$SNORT_CONF" | grep "^INTERFACES=" | sed -e 's/^INTERFACES=//' | sed -e 's/^"//' | sed -e 's/"$//')"
	ifaces=",$ifaces,"

	for iface in $(print_all_ifaces);
	do
		echo "$ifaces" | grep ",$iface," && continue

		iface_hw="$(cat "/sys/class/net/$iface/address")"
		iface_inet="$(ip addr show "$iface" | grep -o 'inet [0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' | grep -o [0-9].*)"
		iface_desc="$iface"
		[ -n "$iface_hw" ] && iface_desc="$iface_desc - $iface_hw"
		[ -n "$iface_inet" ] && iface_desc="$iface_desc ($iface_inet)"

		write_enum_item "$iface" "$iface_desc"
	done
}

config_ifaces_change()
{
	[ -n "$in_ifaces" ] || return
	[ -w "$SNORT_CONF" ] || return

	sed -i "/^INTERFACES=/d" "$SNORT_CONF"
	echo "INTERFACES=$in_ifaces" | sed -e 's/;/,/g' >> "$SNORT_CONF"

	sed -i "/^INTERFACES=/d" "$BARNYARD2_CONF"
	echo "INTERFACES=$in_ifaces" | sed -e 's/;/,/g' >> "$BARNYARD2_CONF"
}

config_ifaces_add()
{
	[ -n "$in_newifaces" ] || return
	[ -w "$SNORT_CONF" ] || return

	ifaces="$(cat "$SNORT_CONF" | grep "^INTERFACES=" | sed -e 's/^INTERFACES=//' | sed -e 's/^"//' | sed -e 's/"$//')"
	if [ -z "$ifaces" -o "$ifaces" = "any" ];
	then
		ifaces="$(echo -n "$in_newifaces" | sed -e 's/;/,/g')"
	else
		ifaces="$ifaces,$(echo -n "$in_newifaces" | sed -e 's/;/,/g')"
	fi

	sed -i "/^INTERFACES=/d" "$SNORT_CONF"
	echo "INTERFACES=$ifaces" >> "$SNORT_CONF"

	sed -i "/^INTERFACES=/d" "$BARNYARD2_CONF"
	echo "INTERFACES=$ifaces" >> "$BARNYARD2_CONF"
}

config_ifaces_del()
{
	[ -n "$in_delifaces" ] || return
	[ -w "$SNORT_CONF" ] || return

	ifaces="$(cat "$SNORT_CONF" | grep "^INTERFACES=" | sed -e 's/^INTERFACES=//' | sed -e 's/^"//' | sed -e 's/"$//')"
	ifaces=",$ifaces,"

	local IFS=';'
	for iface in $in_delifaces
	do
		ifaces="$(echo "$ifaces" | sed -e "s/,$iface,/,/")"
	done

	ifaces="$(echo "$ifaces" | sed -e 's/^,//' | sed -e 's/,$//')"

	sed -i "/^INTERFACES=/d" "$SNORT_CONF"
	echo "INTERFACES=$ifaces" >> "$SNORT_CONF"

	sed -i "/^INTERFACES=/d" "$BARNYARD2_CONF"
	echo "INTERFACES=$ifaces" >> "$BARNYARD2_CONF"
}

# NETWORK INTERFACES STATUS
ifaces_status() {
    local iface=
    local cpuload=
    local pids=
    local na="$(_ "N/A")"
    local is_stopped=
    local configured="$(cat "$SNORT_CONF" | grep "^INTERFACES=" | sed -e 's/^INTERFACES=//' | sed -e 's/^"//' | sed -e 's/"$//')"
    local result=
    local b_result=
    local stopped=
    local b_stopped=
    local all_active=
    local b_all_active=

    pids="$(pidof "snort")"
    # Active interfaces for SNORT
    for i in $pids; do
        iface="$(ps -w -p "$i" -o "args=" | sed -n -e 's/^.*i[[:space:]]\([^[:space:]]\+\).*$/\1/p')"
        cpuload="$(ps -w -p "$i" -o "pcpu=")"
        if [ -z "$iface" -o -z "$cpuload" ]; then
            continue
        fi
        result="${result}${result:+, }${iface} ${cpuload}%"
        all_active="${all_active}${all_active:+ }${iface}"
    done

    pids="$(pidof "barnyard2")"
    # Active interfaces for BARNYARD2
    for i in $pids; do
        iface="$(ps -w -p "$i" -o "args=" | sed -n -e 's/^.*-d[[:space:]]\+[^[:space:]]\+\/\([^[:space:]]\+\).*$/\1/p')"
        cpuload="$(ps -w -p "$i" -o "pcpu=")"
        if [ -z "$iface" -o -z "$cpuload" ]; then
            continue
        fi
        b_result="${b_result}${b_result:+, }${iface} ${cpuload}%"
        b_all_active="${b_all_active}${b_all_active:+ }${iface}"
    done

    if [ "$configured" = "any" ]; then
        configured="$(print_all_ifaces)"
    fi

    # Snort, configured, but inactive
    for i in ${configured//,/ }; do
        is_stopped="yes"
        for j in $all_active; do
            if [ "$i" = "$j" ]; then
                is_stopped="no"
            fi
        done
        if [ "$is_stopped" = "yes" ]; then
            stopped="${stopped}${stopped:+, }${i}"
        fi
    done

    # Barnyard2, configured, but inactive
    for i in ${configured//,/ }; do
        is_stopped="yes"
        for j in $b_all_active; do
            if [ "$i" = "$j" ]; then
                is_stopped="no"
            fi
        done
        if [ "$is_stopped" = "yes" ]; then
            b_stopped="${b_stopped}${b_stopped:+, }${i}"
        fi
    done

    result="${result:-$na}"
    stopped="${stopped:-$na}"

    b_result="${b_result:-$na}"
    b_stopped="${b_stopped:-$na}"

    write_string_param "label_listen_at" "$result"
    write_string_param "label_stopped_at" "$stopped"

    write_string_param "label_b_listen_at" "$b_result"
    write_string_param "label_b_stopped_at" "$b_stopped"
}

on_message()
{
    [ -n "$RULESDIR" ] || set_rules_dir
	case "$in_action" in
        type)
        write_type_item start_date date
        write_type_item start_time time
        write_type_item end_date date
        write_type_item end_time time
        write_type_item time time
        write_type_item add_email e-mail
        write_type_item sender e-mail
		;;
		read)
		case "$in__objects" in
			state_enabled)
            read_state
            ;;
            rule-description)
            [ -n "$in_rules" ] &&
            write_string_param rule_description "$(read_rule_description "$in_rules")"
			;;
            dates)
            read_dates
            ;;
            status)
            # NET INTERFACE STATUS
            ifaces_status
            ;;
            download-data)
            local oinkcode="$(read_oinkcode)"
            if [ -n "$oinkcode" ]; then
                write_string_param rules_url 'oinkcode'
                write_string_param oinkcode "$oinkcode"
            else
                local custom_url="$(read_oinkconf_url)"
                if [ -n "$custom_url" ]; then
                    write_string_param rules_url 'custom'
                    write_string_param custom_url "$custom_url"
                fi
            fi
            read_cron_data
            ;;
		esac
		;;
		write)
		case "$in__objects" in
			state_enabled)
            [ -n "$in_state_enabled" ] && write_state "$in_state_enabled"
			;;
            rules-list)
            [ -n "$in_rules_list" ] && write_ruleset "$(rules_list_to_ruleset "$in_rules_list")"
            ;;
            download-now)
            download_rules
            ;;
            download-data)
            case "$in_rules_url" in
                oinkcode)
                if [ -n "$in_oinkcode" ]; then
                    write_oinkcode "$in_oinkcode"
                elif test_bool "$in_auto_update"; then
                    write_error "`_ "Oinkcode not specified!"`"
                    return
                fi
                ;;
                custom)
                if [ -n "$in_custom_url" ]; then
                    write_oinkconf_url "$in_custom_url"
                elif test_bool "$in_auto_update"; then
                    write_error "`_ "URL not specified!"`"
                    return
                fi
                ;;
                *)
                ;;
            esac
            write_cron_data
            ;;
		esac
		;;
		list)
		case "${in__objects##*/}" in
            avail_rules) #list_rules | write_enum
            for i in $(list_rules); do
                write_table_item \
                name "$i" \
                rule "$i"
            done
			;;
            avail_unused_rules)
            for i in $(list_rules_unused); do
                write_table_item \
                name "$i" \
                rule "$i"
            done
            ;;
            avail_weekday)
            list_weekday
            ;;
            events)
            [ -n "$in_start_date" -a -n "$in_end_date" -a -n "$in_start_time" -a -n "$in_end_time" ] &&
            list_events "$in_start_date" "$in_start_time" "$in_end_date" "$in_end_time"
            ;;
            details)
            [ -n "$in_details" -a "$in_details" != '#f' -a -n "$in_start_date" -a -n "$in_end_date" \
                                                        -a -n "$in_start_time" -a -n "$in_end_time" ] &&
            list_details "$in_details" "$in_start_date" "$in_start_time" "$in_end_date" "$in_end_time"
            ;;
		esac
		;;
	esac
}

alterator_export_proc reload_snort
alterator_export_proc reload_fail2ban
alterator_export_proc pass_generate
alterator_export_proc rules_add
alterator_export_proc rules_remove
alterator_export_proc notifications_list_email
alterator_export_proc notifications_create_email
alterator_export_proc notifications_in_types
alterator_export_proc notifications_out_types
alterator_export_proc notifications_destroy_email
alterator_export_proc notifications_add_type
alterator_export_proc notifications_del_type
alterator_export_proc notifications_read_data
alterator_export_proc notifications_write_data
alterator_export_proc notifications_read_sender
alterator_export_proc notifications_apply_sender
alterator_export_proc notifications_send_test_mail
alterator_export_proc ban_add
alterator_export_proc ban_list
alterator_export_proc ban_read
alterator_export_proc ban_read_in_classes
alterator_export_proc ban_read_out_classes
alterator_export_proc ban_write
alterator_export_proc ban_write_in_classes
alterator_export_proc ban_write_out_classes
alterator_export_proc ban_remove
alterator_export_proc ban_banned_list
alterator_export_proc ban_banned_unban
alterator_export_proc base_user_add
alterator_export_proc base_user_list
alterator_export_proc base_user_data
alterator_export_proc base_user_apply
alterator_export_proc base_user_del
alterator_export_proc config_ifaces_mode
alterator_export_proc config_in_ifaces
alterator_export_proc config_out_ifaces
alterator_export_proc config_ifaces_change
alterator_export_proc config_ifaces_add
alterator_export_proc config_ifaces_del

message_loop

