#!/bin/bash

#        +-----------------------------------------------------------------------------+
#        | OpenFW UTM Community - Subnets Custom Core Engine (DHCPD & FW Version)       |
#        +-----------------------------------------------------------------------------+

# Caminhos e flags totalmente segregados
CONFIG_FILE="/var/efw/ethernet/custom-subnets"
RELOAD_FLAG="/var/efw/ethernet/custom_subnets_reload"

DHCP_CONFIG_FILE="/var/efw/dhcp/custom-subnets-dhcp"
DHCP_RELOAD_FLAG="/var/efw/dhcp/custom_subnets_dhcp_reload"

DHCPD_CUSTOM_TMPL="/var/efw/dhcp/dhcpd.custom.tmpl"
SYSCONFIG_DHCP_CUSTOM="/var/efw/dhcp/sysconfig.dhcp.custom.tmpl"
FW_INPUT_CUSTOM="/etc/firewall/inputfw/custom_subnets.conf"

# Função auxiliar para converter CIDR (ex: /24) para Máscara Decimal Pontuada (ex: 255.255.255.0)
cidr_to_netmask() {
    local bits=$1
    local mask=""
    local full_octets=$((bits / 8))
    local partial_octet=$((bits % 8))
    
    for ((i=0; i<4; i++)); do
        if [ $i -lt $full_octets ]; then
            mask+="255"
        elif [ $i -eq $full_octets ] && [ $partial_octet -gt 0 ]; then
            mask+=$((256 - 2**(8 - partial_octet)))
        else
            mask+="0"
        fi
        [ $i -lt 3 ] && mask+="."
    done
    echo "$mask"
}

# Função auxiliar para converter IP string para inteiro de 32 bits
ip_to_int() {
    local ip=$1
    IFS=. read -r i1 i2 i3 i4 <<< "$ip"
    echo "$(( (i1 << 24) + (i2 << 16) + (i3 << 8) + i4 ))"
}

# Função auxiliar para converter inteiro de 32 bits para IP string
int_to_ip() {
    local int=$1
    echo "$(( (int >> 24) & 255 )).$(( (int >> 16) & 255 )).$(( (int >> 8) & 255 )).$(( int & 255 ))"
}

# Função auxiliar para calcular o IP da Rede (Network) com base no IP e na Máscara
get_network_address() {
    local ip=$1
    local bits=$2
    IFS=. read -r i1 i2 i3 i4 <<< "$ip"
    IFS=. read -r m1 m2 m3 m4 <<< "$(cidr_to_netmask "$bits")"
    
    echo "$((i1 & m1)).$((i2 & m2)).$((i3 & m3)).$((i4 & m4))"
}

do_apply() {
    echo "=== Iniciando aplicação das Subnets Custom ==="

    # Garante que o diretório de destino do DHCP exista antes de qualquer operação
    [ ! -d "/var/efw/dhcp" ] && mkdir -p /var/efw/dhcp
    [ ! -f "$DHCP_CONFIG_FILE" ] && touch "$DHCP_CONFIG_FILE"

    # Adequação estrita de propriedade e permissões para evitar bloqueio do painel CGI (nobody:nogroup)
    chmod 666 "$DHCP_CONFIG_FILE"
    chown nobody:nogroup "$DHCP_CONFIG_FILE"

    # -------------------------------------------------------------------------
    # PASSO 0: Faxina preventiva de DHCP Órfão no arquivo de texto
    # -------------------------------------------------------------------------
    if [ -f "$DHCP_CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
        echo "Verificando se existem configurações de DHCP órfãs para remover..."
        dhcp_zones=$(grep -E '_DHCP=' "$DHCP_CONFIG_FILE" | cut -d'=' -f1 | sed 's/_DHCP//')
        
        for dzone in $dhcp_zones; do
            if ! grep -q "^${dzone}_DEV=" "$CONFIG_FILE"; then
                echo "Removendo DHCP órfão do banco de dados: $dzone"
                sed -i "/^${dzone}_DHCP=/d" "$DHCP_CONFIG_FILE"
            fi
        done
    fi

    # -------------------------------------------------------------------------
    # PASSO 1: Tratar remoções e trocas de interface (Interfaces Órfãs no Linux)
    # -------------------------------------------------------------------------
    if [ -f "$RELOAD_FLAG" ]; then
        echo "Limpando interfaces antigas ou removidas..."
        interfaces_para_limpar=$(grep -E '^eth[0-9]+' "$RELOAD_FLAG" | sort -u)
        
        for iface in $interfaces_para_limpar; do
            if [ -n "$iface" ]; then
                echo "Derrubando e limpando endereço da interface: $iface"
                ip link set dev "$iface" down 2>/dev/null
                ip addr flush dev "$iface" 2>/dev/null
            fi
        done
        rm -f "$RELOAD_FLAG"
    fi

    # Inicia os arquivos de destino limpos
    echo "# Gerado automaticamente por subnets-custom" > "$DHCPD_CUSTOM_TMPL"
    echo "# Gerado automaticamente por subnets-custom" > "$FW_INPUT_CUSTOM"
    
    # Variável para acumular as interfaces que possuem DHCP ativo
    dhcp_interfaces_list=""

    # -------------------------------------------------------------------------
    # PASSO 2: Processar interfaces ativas do arquivo ethernet
    # -------------------------------------------------------------------------
    if [ -f "$CONFIG_FILE" ]; then
        echo "Configurando interfaces ativas e gerando regras de Firewall..."
        
        zones=$(grep -E '_DEV=' "$CONFIG_FILE" | cut -d'=' -f1 | sed 's/_DEV//')

        for zone in $zones; do
            iface=$(grep -E "^${zone}_DEV=" "$CONFIG_FILE" | cut -d'=' -f2)
            ip_cidr=$(grep -E "^${zone}_IPS=" "$CONFIG_FILE" | cut -d'=' -f2)

            if [ -n "$iface" ] && [ -n "$ip_cidr" ]; then
                echo "Subindo interface $iface com IP $ip_cidr"
                
                # --- GARANTE INTERFACE FÍSICA E SUBINTERFACE SEMPRE EM UP ---
                if [[ "$iface" == *.* ]]; then
                    parent_iface=$(echo "$iface" | cut -d'.' -f1)
                    ip link set dev "$parent_iface" up 2>/dev/null
                fi

                # Sobe e aplica o IP na placa de rede Linux
                ip link set dev "$iface" up 2>/dev/null
                ip addr flush dev "$iface" 2>/dev/null
                ip addr add "$ip_cidr" dev "$iface" brd + 2>/dev/null
                
                ip link set dev "$iface" up 2>/dev/null

                # Extrai dados de IP, Bitmask e calcula Máscara e Rede reais
                raw_ip=$(echo "$ip_cidr" | cut -d'/' -f1)
                cidr_bits=$(echo "$ip_cidr" | cut -d'/' -f2)
                
                net_mask=$(cidr_to_netmask "$cidr_bits")
                net_address=$(get_network_address "$raw_ip" "$cidr_bits")

                # -------------------------------------------------------------
                # REGRAS DE FIREWALL (INPUT) - Nativas do Endian
                # -------------------------------------------------------------
                echo "tcp&udp,,67,on,,$iface,off,DHCP_$zone,ACCEPT,," >> "$FW_INPUT_CUSTOM"
                echo "tcp&udp,,53,on,,$iface,off,DNS_$zone,ACCEPT,," >> "$FW_INPUT_CUSTOM"

                # -------------------------------------------------------------
                # AJUSTADO: Autogestão, Sincronismo e Atualização do DHCP
                # -------------------------------------------------------------
                # Obtém dados atuais do escopo do DHCP para checar mudanças reais de escopo/máscara
                current_dhcp_gw=""
                current_dhcp_start=""
                current_dhcp_end=""
                if grep -q "^${zone}_DHCP=" "$DHCP_CONFIG_FILE"; then
                    current_dhcp_data=$(grep "^${zone}_DHCP=" "$DHCP_CONFIG_FILE" | cut -d'=' -f2)
                    current_dhcp_gw=$(echo "$current_dhcp_data" | cut -d',' -f8)
                    current_dhcp_start=$(echo "$current_dhcp_data" | cut -d',' -f2)
                    current_dhcp_end=$(echo "$current_dhcp_data" | cut -d',' -f3)
                fi

                # Validação matemática de compatibilidade do range antigo com os limites novos
                net_int=$(ip_to_int "$net_address")
                hosts_count=$(( 2 ** (32 - cidr_bits) ))
                first_usable=$(( net_int + 1 ))
                last_usable=$(( net_int + hosts_count - 2 ))

                force_reset=0
                if [ -n "$current_dhcp_start" ] && [ -n "$current_dhcp_end" ]; then
                    start_int=$(ip_to_int "$current_dhcp_start")
                    end_int=$(ip_to_int "$current_dhcp_end")
                    # Se o range cadastrado está fora da nova rede física, força recálculo
                    if [ $start_int -lt $first_usable ] || [ $end_int -gt $last_usable ]; then
                        force_reset=1
                    fi
                fi

                # Se o IP mudou OU se o range antigo estourou os limites da nova máscara
                if [ -z "$current_dhcp_gw" ] || [ "$current_dhcp_gw" != "$raw_ip" ] || [ $force_reset -eq 1 ]; then
                    echo "Atualizando/Criando escopo DHCP alinhado com a nova rede para $zone..."
                    
                    raw_ip_int=$(ip_to_int "$raw_ip")
                    
                    # Intervalo inicial seguro padrão para o range baseado no tamanho real do bloco
                    dhcp_start_int=$(( net_int + 10 ))
                    dhcp_end_int=$(( net_int + hosts_count - 5 ))
                    
                    # Proteções para evitar colisão do Range DHCP com o IP estático da própria Interface
                    if [ "$raw_ip_int" -eq "$dhcp_start_int" ] || [ "$raw_ip_int" -lt "$dhcp_start_int" ]; then
                        dhcp_start_int=$(( raw_ip_int + 1 ))
                    fi
                    if [ "$raw_ip_int" -eq "$dhcp_end_int" ] || [ "$raw_ip_int" -gt "$dhcp_end_int" ]; then
                        dhcp_end_int=$(( raw_ip_int - 1 ))
                    fi
                    
                    # Caso a rede seja muito pequena (/29, /30) e estoure as margens, rebaixa pros limites físicos aproveitáveis
                    if [ "$dhcp_start_int" -gt "$last_usable" ] || [ "$dhcp_start_int" -lt "$first_usable" ]; then
                        dhcp_start_int=$first_usable
                    fi
                    if [ "$dhcp_end_int" -lt "$first_usable" ] || [ "$dhcp_end_int" -gt "$last_usable" ]; then
                        dhcp_end_int=$last_usable
                    fi
                    if [ "$dhcp_start_int" -gt "$dhcp_end_int" ]; then
                        dhcp_start_int=$first_usable
                        dhcp_end_int=$last_usable
                    fi
                    
                    dhcp_start=$(int_to_ip "$dhcp_start_int")
                    dhcp_end=$(int_to_ip "$dhcp_end_int")
                    
                    # Preserva o estado de ativação (0 ou 1) se o registro já existia antes de mudar a rede
                    dhcp_status="0"
                    if grep -q "^${zone}_DHCP=" "$DHCP_CONFIG_FILE"; then
                        dhcp_status=$(grep "^${zone}_DHCP=" "$DHCP_CONFIG_FILE" | cut -d'=' -f2 | cut -d',' -f1)
                    fi
                    
                    # Remove a linha antiga se ela já existir para evitar duplicidade
                    sed -i "/^${zone}_DHCP=/d" "$DHCP_CONFIG_FILE"
                    
                    # Insere o escopo totalmente reconfigurado e sincronizado com o novo IP e Máscara
                    echo "${zone}_DHCP=${dhcp_status},${dhcp_start},${dhcp_end},0,60,120,,${raw_ip},${raw_ip},,,,,," >> "$DHCP_CONFIG_FILE"
                    
                    # Restaura a custódia do arquivo após a manipulação do sed/redirecionamento do root
                    chmod 666 "$DHCP_CONFIG_FILE"
                    chown nobody:nogroup "$DHCP_CONFIG_FILE"
                fi

                # -------------------------------------------------------------
                # PASSO 3: Processar configurações de DHCP correspondentes
                # -------------------------------------------------------------
                if [ -f "$DHCP_CONFIG_FILE" ]; then
                    dhcp_data=$(grep -E "^${zone}_DHCP=" "$DHCP_CONFIG_FILE" | cut -d'=' -f2)
                    
                    if [ -n "$dhcp_data" ]; then
                        IFS=''
                        IFS=',' read -r dhcp_enable dhcp_start dhcp_end dhcp_fixed dhcp_ld dhcp_lm dhcp_domain dhcp_gw dhcp_dns1 dhcp_dns2 dhcp_ntp1 dhcp_ntp2 dhcp_wins1 dhcp_wins2 <<< "$dhcp_data"

                        if [ "$dhcp_enable" = "1" ]; then
                            [ -z "$dhcp_ld" ] && dhcp_ld="60"
                            [ -z "$dhcp_lm" ] && dhcp_lm="120"
                            let lease_default=dhcp_ld*60
                            let lease_max=dhcp_lm*60

                            echo "Gerando escopo ISC-DHCPD para a zona $zone ($iface)"
                            
                            # Acumula a interface na lista para o sysconfig
                            if [ -z "$dhcp_interfaces_list" ]; then
                                dhcp_interfaces_list="$iface"
                            else
                                dhcp_interfaces_list="$dhcp_interfaces_list $iface"
                            fi

                            cat >> "$DHCPD_CUSTOM_TMPL" <<EOF

shared-network $zone {
    server-identifier $raw_ip;
    subnet $net_address netmask $net_mask {
EOF

                            if [ "$dhcp_fixed" = "1" ]; then
                                cat >> "$DHCPD_CUSTOM_TMPL" <<EOF
        deny unknown-clients;
EOF
                            elif [ -n "$dhcp_start" ] && [ -n "$dhcp_end" ]; then
                                cat >> "$DHCPD_CUSTOM_TMPL" <<EOF
        pool {
            deny dynamic bootp clients;
            range $dhcp_start $dhcp_end;
        }
EOF
                            fi

                            cat >> "$DHCPD_CUSTOM_TMPL" <<EOF
        default-lease-time $lease_default;
        max-lease-time $lease_max;
        option subnet-mask $net_mask;
EOF

                            if [ -n "$dhcp_gw" ]; then
                                echo "        option routers $dhcp_gw;" >> "$DHCPD_CUSTOM_TMPL"
                            else
                                echo "        option routers $raw_ip;" >> "$DHCPD_CUSTOM_TMPL"
                            fi

                            if [ -n "$dhcp_domain" ]; then
                                echo "        option domain-name \"$dhcp_domain\";" >> "$DHCPD_CUSTOM_TMPL"
                            fi

                            if [ -n "$dhcp_dns1" ] && [ -n "$dhcp_dns2" ]; then
                                echo "        option domain-name-servers $dhcp_dns1, $dhcp_dns2;" >> "$DHCPD_CUSTOM_TMPL"
                            elif [ -n "$dhcp_dns1" ]; then
                                echo "        option domain-name-servers $dhcp_dns1;" >> "$DHCPD_CUSTOM_TMPL"
                            fi

                            if [ -n "$dhcp_ntp1" ] && [ -n "$dhcp_ntp2" ]; then
                                echo "        option ntp-servers $dhcp_ntp1, $dhcp_ntp2;" >> "$DHCPD_CUSTOM_TMPL"
                            elif [ -n "$dhcp_ntp1" ]; then
                                echo "        option ntp-servers $dhcp_ntp1;" >> "$DHCPD_CUSTOM_TMPL"
                            fi

                            if [ -n "$dhcp_wins1" ] && [ -n "$dhcp_wins2" ]; then
                                echo "        option netbios-name-servers $dhcp_wins1, $dhcp_wins2;" >> "$DHCPD_CUSTOM_TMPL"
                            elif [ -n "$dhcp_wins1" ]; then
                                echo "        option netbios-name-servers $dhcp_wins1;" >> "$DHCPD_CUSTOM_TMPL"
                            fi

                            cat >> "$DHCPD_CUSTOM_TMPL" <<EOF
    }
}
EOF
                        fi
                    fi
                fi
            fi
        done
    fi

    # Gera o arquivo sysconfig contendo as interfaces separadas por vírgula
    echo "# Gerado automaticamente por subnets-custom" > "$SYSCONFIG_DHCP_CUSTOM"
    echo "DHCPDARGS=\"$dhcp_interfaces_list\"" >> "$SYSCONFIG_DHCP_CUSTOM"

    # -------------------------------------------------------------------------
    # PASSO 4: Aplicar configurações e reiniciar serviços nativos do Endian
    # -------------------------------------------------------------------------
    echo "Regerando e aplicando arquivo de configuração do DHCP..."
    /usr/local/bin/restartdhcp >/dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo "Utilizando fallback de reinicialização do DHCP..."
        /etc/init.d/dhcp-server restart >/dev/null 2>&1
    fi

    echo "Aplicando novas regras de acesso ao Firewall (Input FW)..."
    /usr/local/bin/setxtaccess >/dev/null 2>&1

    rm -f "$RELOAD_FLAG"
    rm -f "$DHCP_RELOAD_FLAG"

    # Garantia final absoluta de permissão correta para a persistência em consultas futuras do CGI
    chmod 666 "$DHCP_CONFIG_FILE"
    chown nobody:nogroup "$DHCP_CONFIG_FILE"

    echo "=== Aplicação concluída com sucesso ==="
}

case "$1" in
    apply)
        do_apply
        ;;
    *)
        echo "Uso: $0 {apply}"
        exit 1
        ;;
esac

exit 0