#!/usr/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin

IFACE="eth0"
HSC_SPF="_netblocks.hsclabs.com"

STATIC_HOSTS="
51.38.81.110/32
187.108.192.0/20
186.227.197.32/28
177.53.150.0/29
186.227.202.128/28
187.45.183.224/28
187.45.189.80/28
131.100.228.128/25
131.100.25.18/28
177.136.253.0/26
manager-appserver.hsclabs.com
manager-smartdefender.hsclabs.com
"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

resolve_spf_ipv4() {
    local DOMAIN="$1"
    local SEEN="$2"

    echo "$SEEN" | grep -qw "$DOMAIN" && return 0
    SEEN="$SEEN $DOMAIN"

    local TXT
    TXT=$(dig +short TXT "$DOMAIN" | tr -d '"' | tr ' ' '\n')

    for ITEM in $TXT; do
        case "$ITEM" in
            ip4:*)
                echo "${ITEM#ip4:}"
                ;;
            include:*)
                resolve_spf_ipv4 "${ITEM#include:}" "$SEEN"
                ;;
            redirect=*)
                resolve_spf_ipv4 "${ITEM#redirect=}" "$SEEN"
                ;;
        esac
    done
}

is_valid_source() {
    local SRC="$1"

    echo "$SRC" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$' && return 0
    echo "$SRC" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$' && return 0
    echo "$SRC" | grep -Eq '^[a-zA-Z0-9._-]+$' && return 0

    return 1
}

container_running() {
    local NAME="$1"

    docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$NAME"
}

build_hsc_hosts() {
    local SPF_HOSTS

    log "Consultando SPF HSC: $HSC_SPF"

    SPF_HOSTS=$(resolve_spf_ipv4 "$HSC_SPF" "" | sort -u)

    if [ -z "$SPF_HOSTS" ]; then
        log "AVISO: nenhum IP encontrado no SPF $HSC_SPF"
    fi

    echo "$STATIC_HOSTS"
    echo "$SPF_HOSTS"
}

reset_rules() {
    log "Limpando apenas regras gerenciadas pela HSC"

    iptables -F INPUT

    iptables -F DOCKER-USER 2>/dev/null

    iptables -F LOGGING 2>/dev/null
    iptables -X LOGGING 2>/dev/null
}

allow_public_ports_by_container() {
    log "Verificando containers MailInspector para liberação pública"

    if ! command -v docker >/dev/null 2>&1; then
        log "AVISO: docker não encontrado. Nenhuma porta pública liberada por container."
        return
    fi

    if container_running "mailinspector-accesspoint"; then
        log "Container mailinspector-accesspoint em execução: liberando porta 25 para todos"
        iptables -A DOCKER-USER -i "$IFACE" -p tcp --dport 25 -j ACCEPT
    else
        log "Container mailinspector-accesspoint não está em execução: porta 25 não será liberada"
    fi

    if container_running "mailinspector-admsuite"; then
        log "Container mailinspector-admsuite em execução: liberando portas 80 e 443 para todos"
        iptables -A DOCKER-USER -i "$IFACE" -p tcp --dport 80 -j ACCEPT
        iptables -A DOCKER-USER -i "$IFACE" -p tcp --dport 443 -j ACCEPT
    else
        log "Container mailinspector-admsuite não está em execução: portas 80/443 não serão liberadas"
    fi
}

apply_firewall() {
    local HOSTS
    HOSTS=$(build_hsc_hosts | awk 'NF {print $1}' | sort -u)

    reset_rules

    log "Aplicando políticas padrão"

    iptables -P INPUT DROP
    iptables -P OUTPUT ACCEPT

    # IMPORTANTE:
    # Não alterar política nem limpar FORWARD,
    # pois Docker gerencia regras próprias nessa chain.
    # iptables -P FORWARD DROP
    # iptables -F FORWARD

    log "Liberando loopback"

    iptables -A INPUT -i lo -j ACCEPT
    iptables -A OUTPUT -o lo -j ACCEPT

    log "Liberando conexões estabelecidas"

    iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

    log "Liberando redes e hosts HSC no INPUT"

    for HOST in $HOSTS; do
        if is_valid_source "$HOST"; then
            iptables -A INPUT -s "$HOST" -j ACCEPT
            log "INPUT ACCEPT HSC: $HOST"
        else
            log "IGNORADO origem inválida: $HOST"
        fi
    done

    log "Liberando redes Docker no INPUT"

    if command -v docker >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
        for NETWORK in $(docker network ls -q | xargs -r -n 1 docker network inspect | jq -r '.[].IPAM.Config[]?.Subnet' | grep -v null); do
            iptables -A INPUT -s "$NETWORK" -j ACCEPT
            log "INPUT ACCEPT Docker: $NETWORK"
        done
    else
        log "AVISO: docker ou jq não encontrado. Pulando liberação automática de redes Docker."
    fi

    log "Configurando DOCKER-USER"

    iptables -A DOCKER-USER -i "$IFACE" -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT

    log "Liberando redes e hosts HSC no DOCKER-USER"

    for HOST in $HOSTS; do
        if is_valid_source "$HOST"; then
            iptables -A DOCKER-USER -i "$IFACE" -s "$HOST" -j ACCEPT
            log "DOCKER-USER ACCEPT HSC: $HOST"
        fi
    done

    allow_public_ports_by_container

    log "Aplicando bloqueio padrão para TCP externo no DOCKER-USER"

    iptables -A DOCKER-USER -i "$IFACE" -p tcp -j REJECT

    # Essencial para Docker continuar processando as chains internas
    iptables -A DOCKER-USER -j RETURN

    log "Configurando logs de pacotes inválidos"

    iptables -A INPUT -m conntrack --ctstate INVALID -j LOG --log-prefix "Invalid Packet: "
    iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

    log "Configurando chain LOGGING"

    iptables -N LOGGING
    iptables -A INPUT -j LOGGING
    iptables -A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables-Dropped: " --log-level 4
    iptables -A LOGGING -j DROP

    log "Firewall aplicado com sucesso"
}

case "$1" in
    ""|start|apply)
        apply_firewall
        ;;

    list)
        iptables -L -v -n
        ;;

    spf)
        resolve_spf_ipv4 "$HSC_SPF" "" | sort -u
        ;;

    *)
	apply_firewall
        /opt/hsc/firewall/hsc_cloud_block.sh update_dynamic_whitelist
        /opt/hsc/firewall/hsc_cloud_block.sh sync
        exit 0

        ;;
esac
