Files
tiago.aica 64883d4f02 style(debian): add -e flag to echo commands in log functions
Enable interpretation of backslash escapes in log messages across all debian scripts for consistent formatting and proper escape sequence handling
2026-02-15 17:52:34 +00:00

443 lines
15 KiB
Bash

#!/bin/bash
# Script Dinâmico de Configuração de Rede para Raspberry Pi
# Localização: /usr/local/bin/configure_network.sh
# Descrição: Configura MAC address persistente para interface eth0 e monitoriza estado da rede
# --- Configurações ---
LOG_FILE="/var/log/network_config.log"
UDEV_RULES_FILE="/etc/udev/rules.d/81-mac-spoof.rules"
IFACE="eth0"
WLAN_IFACE="wlan0"
MAX_LOG_SIZE_KB=1024 # 1MB
# --- Funções Auxiliares ---
# Função de Log (modificada para usar logger)
log() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Escreve no ficheiro de log
echo -e "$timestamp: $message" | tee -a "$LOG_FILE"
# Envia para o syslog (opcional)
logger -t "network_config" "$message"
}
# Função para limitar o tamanho do log
limit_log_size() {
local log_file="$1"
local max_size_kb="$2"
local max_size_bytes=$((max_size_kb * 1024))
if [ -f "$log_file" ]; then
local current_size=$(stat -c %s "$log_file" 2>/dev/null || wc -c < "$log_file" 2>/dev/null)
if [ "$current_size" -gt "$max_size_bytes" ]; then
log "Aviso: Ficheiro de log $log_file excedeu $max_size_kb KB. A truncar..."
tail -n 500 "$log_file" > "${log_file}.tmp" && mv "${log_file}.tmp" "$log_file"
log "Ficheiro de log truncado. As últimas 500 linhas foram mantidas."
fi
fi
}
# Função para verificar se a interface tem IP
has_ip_address() {
local iface="$1"
if ip addr show "$iface" | grep -q "inet "; then
return 0
else
return 1
fi
}
# Função para perguntar confirmação ao utilizador
ask_confirmation() {
local message="$1"
local default="$2" # "y" or "n"
while true; do
if [ "$default" = "y" ]; then
read -p "$message [Y/n]: " choice < /dev/tty
else
read -p "$message [y/N]: " choice < /dev/tty
fi
# Default choice if user just presses Enter
if [ -z "$choice" ]; then
choice="$default"
fi
case "$choice" in
y|Y|yes|Yes|YES)
return 0
;;
n|N|no|No|NO)
return 1
;;
*)
echo "Por favor, responda sim ou não."
;;
esac
done
}
# Função para gerar MAC único baseado no Serial do CPU
generate_unique_mac() {
local serial
serial=$(awk '/Serial/ {print $3}' /proc/cpuinfo | tr -d ' ')
if [ -z "$serial" ]; then
# Fallback: generate random MAC if serial not available
printf '%02X:%02X:%02X:%02X:%02X:%02X' $((0x02 | (RANDOM % 256) & 0xFE)) \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256))
else
local hash
hash=$(echo -n "$serial-dm9601" | md5sum | cut -c1-10)
echo "02:${hash:0:2}:${hash:2:2}:${hash:4:2}:${hash:6:2}:${hash:8:2}"
fi
}
# Função para obter o MAC atual da interface
get_current_mac() {
local interface="$1"
ip link show "$interface" | awk '/ether/ {print $2}' | tr '[:upper:]' '[:lower:]'
}
# Função para definir MAC imediatamente
set_mac() {
local interface="$1"
local mac="$2"
if ! ip link show "$interface" &> /dev/null; then
log "Interface $interface não encontrada em set_mac."
return 1
fi
log "A definir MAC $mac em $interface..."
if ip link set "$interface" down && \
ip link set dev "$interface" address "$mac" && \
ip link set "$interface" up; then
log "MAC alterado com sucesso na sessão atual."
return 0
else
log "Falha ao alterar MAC na sessão atual."
return 1
fi
}
# Função para criar/atualizar regra Udev
setup_udev_persistence() {
local interface="$1"
local target_mac="$2"
local original_mac
local rule_content
original_mac=$(get_current_mac "$interface")
target_mac=$(echo "$target_mac" | tr '[:upper:]' '[:lower:]')
if [ -z "$original_mac" ]; then
log "Erro crítico: Não foi possível obter o MAC original para criar regra udev."
return 1
fi
# Cria o conteúdo da regra
rule_content="ACTION==\"add\", SUBSYSTEM==\"net\", ATTR{address}==\"$original_mac\", RUN+=\"/usr/bin/ip link set dev \$name address $target_mac\""
# Cria diretório se não existir
mkdir -p "$(dirname "$UDEV_RULES_FILE")"
# Escreve no ficheiro de regras
echo "$rule_content" > "$UDEV_RULES_FILE"
# Verifica se o ficheiro foi criado com sucesso
if [ -f "$UDEV_RULES_FILE" ]; then
log "Sucesso: Ficheiro de regras Udev criado/atualizado em $UDEV_RULES_FILE."
log "Conteúdo da regra: $rule_content"
else
log "Erro: Falha ao criar/atualizar o ficheiro de regras Udev em $UDEV_RULES_FILE."
return 1
fi
# Recarrega as regras do udev
if udevadm control --reload-rules && udevadm trigger; then
log "Regras Udev recarregadas e aplicadas com sucesso."
return 0
else
log "Erro: Falha ao recarregar/aplicar regras Udev."
return 1
fi
}
# Função para verificar se a persistência está configurada corretamente
is_persistence_configured() {
local current_mac
local udev_mac
if [ ! -f "$UDEV_RULES_FILE" ] || [ ! -s "$UDEV_RULES_FILE" ]; then
log "Ficheiro de persistência não encontrado ou vazio."
return 1
fi
current_mac=$(get_current_mac "$IFACE")
udev_mac=$(grep -oP 'address \K([0-9a-f]{2}:){5}[0-9a-f]{2}' "$UDEV_RULES_FILE")
if [ "$current_mac" = "$udev_mac" ]; then
log "Persistência verificada: MAC atual ($current_mac) coincide com regra Udev."
return 0
else
log "Persistência desatualizada: MAC atual ($current_mac) não coincide com regra Udev ($udev_mac)."
return 1
fi
}
# Função para desativar Wi-Fi
disable_wifi() {
# Cria diretório de log se não existir
mkdir -p /var/log
# Aguarda 60 segundos antes de verificar IP e desativar Wi-Fi
log "A aguardar 60 segundos antes de verificar IP de $IFACE e desativar Wi-Fi..."
sleep 60
# Verifica se eth0 tem IP antes de desativar Wi-Fi
if has_ip_address "$IFACE"; then
if ip link show "$WLAN_IFACE" &> /dev/null; then
if ip link set "$WLAN_IFACE" down; then
log "Wi-Fi ($WLAN_IFACE) desativado com sucesso ($IFACE tem IP)"
else
log "Falha ao desativar Wi-Fi ($WLAN_IFACE)"
fi
else
log "Interface $WLAN_IFACE não encontrada (Wi-Fi já desativado?)"
fi
else
log "$IFACE não tem endereço IP. Wi-Fi permanece ativo."
fi
}
# Função para forçar renovação de IP
force_ip_renewal() {
local iface="$1"
local success=0
# Tentativa 1: Reiniciar dhcpcd (Raspberry Pi padrão)
if systemctl is-active --quiet dhcpcd; then
log "Tentativa 1/3: Reiniciando dhcpcd para renovar IP em $iface..."
if systemctl restart dhcpcd; then
log "dhcpcd reiniciado com sucesso."
success=1
else
log "Falha ao reiniciar dhcpcd."
fi
fi
# Tentativa 2: Usar dhclient (se disponível)
if [ "$success" -eq 0 ] && command -v dhclient > /dev/null; then
log "Tentativa 2/3: Usando dhclient para renovar IP em $iface..."
if dhclient -r "$iface" && dhclient "$iface"; then
log "IP renovado com sucesso usando dhclient."
success=1
else
log "Falha ao renovar IP usando dhclient."
fi
fi
# Tentativa 3: Reiniciar a interface
if [ "$success" -eq 0 ]; then
log "Tentativa 3/3: Reiniciando interface $iface..."
if ip link set "$iface" down && sleep 2 && ip link set "$iface" up; then
log "Interface $iface reiniciada. IP deve ser atribuído em breve."
success=1
else
log "Falha ao reiniciar interface $iface."
fi
fi
return "$success"
}
# Tarefa em Background para monitorizar rede
monitor_network_task() {
log "Monitor: A iniciar monitorização contínua da rede..."
while true; do
sleep 60 # Verifica a cada 60 segundos
if ip link show "$IFACE" &> /dev/null; then
# Verifica se eth0 está sem cabo (NO-CARRIER)
if ip link show "$IFACE" | grep -q "NO-CARRIER"; then
log "Monitor: Interface $IFACE sem cabo (NO-CARRIER). A ativar Wi-Fi..."
if ip link show "$WLAN_IFACE" &> /dev/null; then
if ip link set "$WLAN_IFACE" up; then
log "Monitor: Wi-Fi ($WLAN_IFACE) ativado com sucesso."
else
log "Monitor: Falha ao ativar Wi-Fi ($WLAN_IFACE)."
fi
else
log "Monitor: Interface $WLAN_IFACE não encontrada."
fi
else
# eth0 está com cabo, verifica se tem IP
if has_ip_address "$IFACE"; then
log "Monitor: Interface $IFACE ativa e com IP. A verificar Wi-Fi..."
if ip link show "$WLAN_IFACE" &> /dev/null && ip link show "$WLAN_IFACE" | grep -q "UP"; then
if ip link set "$WLAN_IFACE" down; then
log "Monitor: Wi-Fi ($WLAN_IFACE) desativado (eth0 tem IP)."
else
log "Monitor: Falha ao desativar Wi-Fi ($WLAN_IFACE)."
fi
else
log "Monitor: Wi-Fi ($WLAN_IFACE) já está desativado ou não encontrado."
fi
else
log "Monitor: Interface $IFACE ativa mas sem IP. Tentando forçar renovação de IP..."
if force_ip_renewal "$IFACE"; then
log "Monitor: Renovação de IP bem-sucedida. Wi-Fi permanece ativo até confirmação."
else
log "Monitor: Falha ao renovar IP."
if ip link show "$WLAN_IFACE" &> /dev/null && ip link show "$WLAN_IFACE" | grep -q "DOWN"; then
log "Ativando Wi-Fi como fallback..."
if ip link set "$WLAN_IFACE" up; then
log "Monitor: Wi-Fi ($WLAN_IFACE) ativado como fallback."
else
log "Monitor: Falha ao ativar Wi-Fi ($WLAN_IFACE)."
fi
fi
fi
fi
fi
else
log "Monitor: Interface $IFACE não encontrada."
fi
done
}
# Função principal para configurar MAC
configure_mac() {
local target_mac="$1"
# Validação do MAC
if [[ ! "$target_mac" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then
log "MAC Address inválido: $target_mac"
return 1
fi
log "A configurar sistema com MAC: $target_mac"
# Aplica o MAC na sessão atual
if set_mac "$IFACE" "$target_mac"; then
# Atualiza a regra Udev independentemente do estado anterior
if setup_udev_persistence "$IFACE" "$target_mac"; then
log "Configuração de MAC e persistência concluída com sucesso."
return 0
else
log "Aviso: MAC definido mas persistência falhou."
return 1
fi
else
log "Aviso: Falha ao definir MAC em tempo real, mas a tentar criar persistência..."
if setup_udev_persistence "$IFACE" "$target_mac"; then
log "Persistência criada, mas MAC não foi aplicado na sessão atual."
return 1
else
log "Erro: Falha completa na configuração de MAC."
return 1
fi
fi
}
# --- Execução Principal ---
if [ "$(id -u)" -ne 0 ]; then
echo "Este script deve ser executado como root." >&2
exit 1
fi
mkdir -p "$(dirname "$LOG_FILE")"
limit_log_size "$LOG_FILE" "$MAX_LOG_SIZE_KB" # Verifica o tamanho do log no início
# 1. Verificar se a interface existe
if ! ip link show "$IFACE" &> /dev/null; then
log "Erro: Interface $IFACE não disponível. A sair."
exit 1
fi
# 2. Verificar se já existe configuração de persistência
if is_persistence_configured; then
log "Configuração de persistência verificada. A saltar configuração inicial."
# Verifica se eth0 tem IP antes de desativar Wi-Fi
if has_ip_address "$IFACE"; then
if ! pgrep -f "disable_wifi" > /dev/null; then
disable_wifi &
else
log "disable_wifi já está em execução."
fi
else
log "$IFACE não tem endereço IP. Wi-Fi permanece ativo."
fi
monitor_network_task &
exit 0
fi
# 3. Determinar o MAC Address
TARGET_MAC=""
# Verificar argumento ($1)
if [ -n "$1" ]; then
TARGET_MAC="$1"
log "MAC definido via argumento: $TARGET_MAC"
else
# Verificar se é boot (não interativo e sem /dev/tty disponível)
if [ ! -t 0 ] && [ ! -e /dev/tty ]; then
# Modo boot: Não fazer nada, assumindo que já foi configurado
log "Modo boot: A saltar configuração de MAC (assumindo que já foi configurado)"
if has_ip_address "$IFACE"; then
if ! pgrep -f "disable_wifi" > /dev/null; then
disable_wifi &
else
log "disable_wifi já está em execução."
fi
else
log "$IFACE não tem endereço IP. Wi-Fi permanece ativo."
fi
exit 0
else
# Modo interativo: Perguntar ao utilizador o que fazer
if ask_confirmation "Deseja configurar um novo MAC para a interface $IFACE?" "y"; then
if ask_confirmation "Deseja gerar um novo MAC único automaticamente?" "y"; then
TARGET_MAC=$(generate_unique_mac)
log "MAC gerado automaticamente: $TARGET_MAC"
else
# Pergunta para introduzir MAC manualmente
while true; do
read -p "Introduza o MAC (formato XX:XX:XX:XX:XX:XX): " TARGET_MAC < /dev/tty
if [[ "$TARGET_MAC" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then
break
else
echo "Formato de MAC inválido. Por favor, introduza no formato XX:XX:XX:XX:XX:XX."
fi
done
fi
else
log "Configuração de MAC cancelada pelo utilizador"
exit 0
fi
fi
fi
# 4. Configurar MAC e persistência
if configure_mac "$TARGET_MAC"; then
log "Configuração concluída com sucesso."
else
log "Aviso: Configuração concluída com alguns erros."
fi
# 5. Lançar monitor de rede
monitor_network_task &
exit 0