#!/bin/bash
###############################################################################
# Script Name : wireguard
# Description : Gerenciamento do WireGuard (Server / Client / Peers)
#
# Author      : Eduardo Jonck
# Organization: OpenFW
###############################################################################

umask 077

SERVER_FILE="/var/efw/wireguard/server.settings"
CLIENTS_FILE="/var/efw/wireguard/clients.settings"
PEERS_FILE="/var/efw/wireguard/peers.settings"
START_HOOK="/var/efw/inithooks/start.local"
LOG="/var/log/wireguard/wireguard.log"

RUN_DIR="/var/run/wireguard"
LOCK_SERVER="/run/wireguard-server.lock"
LOCK_CLIENT="/run/wireguard-client.lock"
STATUS_DIR="/home/httpd/cgi-bin"

log(){ echo "[$(date '+%F %T')] $*" >> "$LOG"; }

ensure_dir(){ mkdir -p "$1"; }

ensure_run_dir(){
    ensure_dir "$RUN_DIR"
    chown nobody:nogroup "$RUN_DIR" 2>/dev/null || true
    chmod 755 "$RUN_DIR"
}

valid_iface(){ [[ "$1" =~ ^wg[0-9A-Za-z_-]+$ ]]; }

wait_dns() {
    local host="$1"
    local timeout="${2:-60}"   # tempo máximo em segundos
    local interval=2
    local waited=0

    [[ "$host" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && return 0

    log "Aguardando DNS do host $host..."
    until dig +time=2 +tries=1 +short "$host" >/dev/null 2>&1; do
        sleep $interval
        waited=$((waited + interval))
        [[ $waited -ge $timeout ]] && { log "Timeout DNS $host após $timeout segundos"; return 1; }
    done
    log "DNS $host resolvido!"
    return 0
}

wg_stop(){
    local iface="$1"
    valid_iface "$iface" || return
    wg-quick down "$iface" 2>/dev/null || ip link del "$iface" 2>/dev/null
    rm -f "$RUN_DIR/$iface.pid"
}

create_status(){
    local iface="$1"
    local role="$2"
    local pidfile="$RUN_DIR/$iface.pid"
    local pl="$STATUS_DIR/status-$iface.pl"
    local label pid

    wg show "$iface" >/dev/null 2>&1 || return

    for _ in 1 2 3 4 5; do
        pid="$(ps -eo pid,comm | awk "\$2==\"wg-crypt-$iface\" {print \$1; exit}")"
        [[ -n "$pid" ]] && break
        sleep 0.1
    done
    [[ -n "$pid" ]] || return

    echo "$pid" > "$pidfile"
    chown nobody:nogroup "$pidfile"

    [[ -f "$pl" ]] && return

    case "$role" in
        server) label="WireGuard Server Interface $iface" ;;
        *)      label="WireGuard Cliente Interface $iface" ;;
    esac

    cat > "$pl" <<EOF
#!/usr/bin/perl
require 'header.pl';
require '/home/httpd/cgi-bin/endianinc.pl';

my \$wireguard = ['wg-crypt-$iface', '$pidfile', ''];
register_status(_('$label'),\$wireguard);
EOF

    chmod 755 "$pl"
}

remove_status(){
    local iface="$1"
    rm -f "$RUN_DIR/$iface.pid"
    rm -f "$STATUS_DIR/status-$iface.pl"
}

cleanup_confs(){
    local tag="$1"
    shift
    local keep=("$@")

    for c in /etc/wireguard/wg*.conf; do
        [[ -f "$c" ]] || continue
        grep -q "^# Managed-by: $tag" "$c" || continue
        iface="${c##*/}"; iface="${iface%.conf}"

        for k in "${keep[@]}"; do
            [[ "$iface" == "$k" ]] && continue 2
        done

        log "Removendo $iface"
        wg_stop "$iface"
        rm -f "$c"
    done
}

ensure_start_local(){
    ensure_dir "$(dirname "$START_HOOK")"
    [[ -f "$START_HOOK" ]] || echo "#!/bin/bash" > "$START_HOOK"
    sed -i '\|/usr/local/bin/wireguard|d;/^exit 0$/d' "$START_HOOK"

    [[ -f "$SERVER_FILE" ]] && grep -q '^status=on' "$SERVER_FILE" &&
        echo "/usr/local/bin/wireguard --server &" >> "$START_HOOK"

    [[ -f "$CLIENTS_FILE" ]] &&
        awk '$1!="0" && $1!~/^#/ {f=1} END{exit !f}' "$CLIENTS_FILE" &&
        echo "/usr/local/bin/wireguard --client &" >> "$START_HOOK"

    echo "exit 0" >> "$START_HOOK"
    chmod +x "$START_HOOK"
}

wg_client(){
    [[ -f "$CLIENTS_FILE" ]] || exit 0
    ensure_run_dir
    exec 200>"$LOCK_CLIENT"; flock -n 200 || exit 0

    declare -A ACTIVE

    while read -r EN NAME IFACE PRIV PUB END END_PUB ALLOWED ADDR KEEP; do
        [[ -z "$NAME" || "$NAME" =~ ^# ]] && continue
        ACTIVE["$IFACE"]=1

        CONF="/etc/wireguard/$IFACE.conf"
        if [[ "$EN" == "0" ]]; then
            wg_stop "$IFACE"
            rm -f "$CONF"
            continue
        fi

        HOST="${END%%:*}"
        wait_dns "$HOST" || { log "Não foi possível resolver $HOST, pulando interface $IFACE"; continue; }


        {
            echo "# Managed-by: wireguard-client"
            echo "[Interface]"
            echo "PrivateKey = ${PRIV%=}="
            [[ -n "$ADDR" ]] && echo "Address = $ADDR"
            echo
            echo "# Peer: $NAME"
            echo "[Peer]"
            echo "PublicKey = ${END_PUB%=}="
            [[ -n "$ALLOWED" ]] && echo "AllowedIPs = $ALLOWED"
            [[ -n "$KEEP" ]] && echo "PersistentKeepalive = $KEEP"
            echo "Endpoint = $END"
        } > "$CONF"

        chmod 600 "$CONF"
        wg_stop "$IFACE"
        wg-quick up "$IFACE" >> "$LOG" 2>&1
        create_status "$IFACE" client
    done < "$CLIENTS_FILE"

    cleanup_confs wireguard-client "${!ACTIVE[@]}"

    for pl in "$STATUS_DIR"/status-wg*.pl; do
        [[ -f "$pl" ]] || continue
        grep -q "WireGuard Server Interface" "$pl" && continue
        iface="${pl##*/}"; iface="${iface#status-}"; iface="${iface%.pl}"
        [[ "${ACTIVE[$iface]}" ]] && continue
        log "Removendo status de cliente inexistente no settings: $iface"
        remove_status "$iface"
    done
}

wg_server(){
    [[ -f "$SERVER_FILE" ]] || exit 0
    ensure_run_dir
    exec 201>"$LOCK_SERVER"; flock -n 201 || exit 0

    declare -A C
    while IFS== read -r k v; do [[ "$k" ]] && C[$k]="$v"; done < "$SERVER_FILE"

    IFACE="${C[interface]}"
    valid_iface "$IFACE" || exit 1

    cleanup_confs wireguard-server "$IFACE"

    [[ "${C[status]}" != "on" ]] && { wg_stop "$IFACE"; exit 0; }

    CONF="/etc/wireguard/$IFACE.conf"
    {
        echo "# Managed-by: wireguard-server"
        echo "[Interface]"
        echo "PrivateKey = ${C[private_key]%=}="
        [[ "${C[address]}" ]] && echo "Address = ${C[address]}"
        [[ "${C[listen_port]}" ]] && echo "ListenPort = ${C[listen_port]}"
        [[ "${C[mtu]}" ]] && echo "MTU = ${C[mtu]}"
        [[ "${C[post_up]}" ]] && echo "PostUp = ${C[post_up]}"
        [[ "${C[post_down]}" ]] && echo "PostDown = ${C[post_down]}"

        if [[ -f "$PEERS_FILE" ]]; then
            while read -r EN NAME PRIVKEY PUBKEY ALLOWED ALLOC KEEP; do
                [[ "$EN" != "1" ]] && continue
                [[ -z "$NAME" || "$NAME" =~ ^# ]] && continue

                echo
                echo "# Peer: $NAME"
                echo "[Peer]"
                echo "PublicKey = $PUBKEY"

                ALLOWED_FINAL="$ALLOC"
                if [[ "$ALLOWED" != "-" ]]; then
                    ALLOWED_FINAL+=", $ALLOWED"
                fi
                echo "AllowedIPs = $ALLOWED_FINAL"
                [[ "$KEEP" =~ ^[0-9]+$ ]] && echo "PersistentKeepalive = $KEEP"
            done < "$PEERS_FILE"
        fi
    } > "$CONF"

    chmod 600 "$CONF"
    wg_stop "$IFACE"
    wg-quick up "$IFACE" >> "$LOG" 2>&1
    create_status "$IFACE" server
}

ensure_start_local

case "$1" in
    --client) wg_client ;;
    --server) wg_server ;;
    *) echo "Uso: $0 --client | --server" ;;
esac