diff --git a/correcao_p1.sh b/correcao_p1.sh new file mode 100644 index 0000000..b3c5b1a --- /dev/null +++ b/correcao_p1.sh @@ -0,0 +1,655 @@ +#!/usr/bin/env bash + +set -u + +SCRIPT_NAME="$(basename "$0")" +TIMESTAMP="$(date '+%Y%m%d_%H%M%S')" + +# Configure apenas estas variaveis antes de executar o script. +BOT_TOKEN="8785769899:AAECGDEOwzfPxWyQNm6sDxA9nIf1wUFcxiU" +CHAT_ID="-5192130580" + +APT_UPDATED=0 +APT_UPDATE_FAILED=0 +RA_ARGS=() + +REQUIRED_PACKAGES=( + iproute2 + net-tools + dnsutils + iputils-ping + iptables + nftables + tar + curl +) + +NS1_EXTRA_PACKAGES=( + isc-dhcp-server +) + +now_human() { + date '+%Y/%m/%d-%H:%M:%S' +} + +log_line() { + local logfile="$1" + local message="$2" + mkdir -p "$(dirname "$logfile")" + printf '[%s] %s\n' "$(now_human)" "$message" | tee -a "$logfile" >/dev/null +} + +main_log() { + log_line "$EXEC_LOG" "$1" +} + +warn_log() { + log_line "$EXEC_LOG" "AVISO: $1" +} + +error_log() { + log_line "$EXEC_LOG" "ERRO: $1" +} + +sanitize_vm_type() { + printf '%s' "$1" | tr '[:lower:]' '[:upper:]' +} + +print_usage() { + cat <<'EOF' +Uso: + correcao_vm.sh [RA1 RA2 [RA3]] + +Opcoes: + -h, --help Exibe esta ajuda. +EOF +} + +require_root() { + if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then + printf 'Este script precisa ser executado como root.\n' >&2 + exit 1 + fi +} + +prompt_nonempty() { + local prompt="$1" + local value="" + while :; do + if ! read -r -p "$prompt" value; then + return 1 + fi + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + if [[ -n "$value" ]]; then + printf '%s\n' "$value" + return 0 + fi + printf 'Valor obrigatorio.\n' + done +} + +prompt_numeric() { + local prompt="$1" + local value="" + while :; do + if ! read -r -p "$prompt" value; then + return 1 + fi + if [[ "$value" =~ ^[0-9]+$ ]]; then + printf '%s\n' "$value" + return 0 + fi + printf 'Digite apenas numeros.\n' + done +} + +prompt_yes_no() { + local prompt="$1" + local value="" + while :; do + if ! read -r -p "$prompt [s/n]: " value; then + return 1 + fi + value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')" + case "$value" in + s|sim|y|yes) printf 'yes\n'; return 0 ;; + n|nao|não|no) printf 'no\n'; return 0 ;; + *) printf 'Resposta invalida.\n' ;; + esac + done +} + +prompt_vm_type() { + local value="" + while :; do + printf 'Selecione a VM:\n' >&2 + printf '1) NS1\n' >&2 + printf '2) WEB\n' >&2 + if ! read -r -p "Opcao: " value; then + return 1 + fi + case "$value" in + 1) printf 'NS1\n'; return 0 ;; + 2) printf 'WEB\n'; return 0 ;; + *) + printf 'Digite 1 para NS1 ou 2 para WEB.\n' + ;; + esac + done +} + +setup_dirs() { + BASE_DIR="/root/coleta_dupla_${DUPLA_NUM}_${VM_TYPE}_${TIMESTAMP}" + LOG_DIR="$BASE_DIR/logs" + CONFIG_DIR="$BASE_DIR/configs" + IDENT_DIR="$BASE_DIR/identificacao" + ENVIO_DIR="$BASE_DIR/envio" + EXEC_LOG="$LOG_DIR/execucao.log" + INSTAL_LOG="$LOG_DIR/instalacao.log" + REDE_LOG="$LOG_DIR/rede.log" + DHCP_LOG="$LOG_DIR/dhcp.log" + TELEGRAM_LOG="$ENVIO_DIR/telegram.log" + SYSTEM_INSTALL_LOG="$LOG_DIR/instalacao_pacotes.log" + + mkdir -p "$LOG_DIR" "$CONFIG_DIR" "$IDENT_DIR" "$ENVIO_DIR" +} + +collect_identification_input() { + if ((${#RA_ARGS[@]} > 0)); then + ALUNO1="${RA_ARGS[0]}" + if ((${#RA_ARGS[@]} >= 2)); then + ALUNO2="${RA_ARGS[1]}" + else + ALUNO2="" + fi + if ((${#RA_ARGS[@]} == 3)); then + HAS_THIRD="yes" + ALUNO3="${RA_ARGS[2]}" + else + HAS_THIRD="no" + ALUNO3="" + fi + return 0 + fi + + ALUNO1="$(prompt_numeric 'RA do aluno 1: ')" + if [[ "$(prompt_yes_no 'Existe aluno 2?')" == "yes" ]]; then + ALUNO2="$(prompt_numeric 'RA do aluno 2: ')" + HAS_THIRD="$(prompt_yes_no 'Existe terceiro aluno?')" + if [[ "$HAS_THIRD" == "yes" ]]; then + ALUNO3="$(prompt_numeric 'RA do aluno 3: ')" + else + ALUNO3="" + fi + else + ALUNO2="" + HAS_THIRD="no" + ALUNO3="" + fi +} + +save_identification() { + { + printf 'DataHora=%s\n' "$(now_human)" + printf 'Dupla=%s\n' "$DUPLA_NUM" + printf 'VM=%s\n' "$VM_TYPE" + printf 'Aluno1=%s\n' "$ALUNO1" + } > "$IDENT_DIR/alunos.txt" + + if [[ -n "$ALUNO2" ]]; then + printf 'Aluno2=%s\n' "$ALUNO2" >> "$IDENT_DIR/alunos.txt" + fi + + if [[ "$HAS_THIRD" == "yes" && -n "$ALUNO3" ]]; then + printf 'Aluno3=%s\n' "$ALUNO3" >> "$IDENT_DIR/alunos.txt" + fi + + main_log "Identificacao da dupla registrada em $IDENT_DIR/alunos.txt" +} + +run_cmd_logged() { + local logfile="$1" + shift + { + printf '[%s] CMD: %s\n' "$(now_human)" "$*" + "$@" + local rc=$? + printf '[%s] RC: %s\n' "$(now_human)" "$rc" + return "$rc" + } >> "$logfile" 2>&1 +} + +apt_update_once() { + if [[ "$APT_UPDATE_FAILED" -eq 1 ]]; then + log_line "$SYSTEM_INSTALL_LOG" "apt-get update ja falhou anteriormente; novas tentativas foram ignoradas" + return 1 + fi + + if [[ "$APT_UPDATED" -eq 0 ]]; then + log_line "$SYSTEM_INSTALL_LOG" "Executando apt-get update" + if apt-get update >> "$SYSTEM_INSTALL_LOG" 2>&1; then + APT_UPDATED=1 + log_line "$SYSTEM_INSTALL_LOG" "apt-get update concluido" + else + APT_UPDATE_FAILED=1 + log_line "$SYSTEM_INSTALL_LOG" "Falha no apt-get update" + return 1 + fi + fi +} + +ensure_packages() { + local packages=("${REQUIRED_PACKAGES[@]}") + local pkg + + if [[ "$VM_TYPE" == "NS1" ]]; then + packages+=("${NS1_EXTRA_PACKAGES[@]}") + fi + + for pkg in "${packages[@]}"; do + if dpkg -s "$pkg" >/dev/null 2>&1; then + log_line "$SYSTEM_INSTALL_LOG" "Pacote ja instalado: $pkg" + continue + fi + + log_line "$SYSTEM_INSTALL_LOG" "Pacote ausente: $pkg" + if ! apt_update_once; then + log_line "$SYSTEM_INSTALL_LOG" "Instalacao ignorada para $pkg por falha previa no apt-get update" + continue + fi + + if DEBIAN_FRONTEND=noninteractive apt-get install -y "$pkg" >> "$SYSTEM_INSTALL_LOG" 2>&1; then + log_line "$SYSTEM_INSTALL_LOG" "Pacote instalado com sucesso: $pkg" + else + log_line "$SYSTEM_INSTALL_LOG" "Falha ao instalar pacote: $pkg" + fi + done +} + +copy_config_if_exists() { + local src="$1" + local dst_name="$2" + if [[ -f "$src" ]]; then + cp "$src" "$CONFIG_DIR/$dst_name" + main_log "Arquivo copiado: $src -> $CONFIG_DIR/$dst_name" + else + warn_log "Arquivo nao encontrado: $src" + fi +} + +collect_configs() { + copy_config_if_exists "/etc/default/isc-dhcp-server" "isc-dhcp-server" + copy_config_if_exists "/etc/dhcp/dhcpd.conf" "dhcpd.conf" + copy_config_if_exists "/etc/network/interfaces" "interfaces" +} + +detect_install_datetime() { + local root_source fs_type candidate ts created_line + + root_source="$(findmnt -no SOURCE / 2>/dev/null || true)" + fs_type="$(findmnt -no FSTYPE / 2>/dev/null || true)" + + if [[ -n "$root_source" && -n "$fs_type" ]]; then + if [[ "$root_source" =~ ^/dev/ ]] && [[ "$fs_type" =~ ^ext[2-4]$ ]] && command -v tune2fs >/dev/null 2>&1; then + created_line="$(tune2fs -l "$root_source" 2>/dev/null | awk -F': ' '/Filesystem created:/ {print $2; exit}')" + if [[ -n "$created_line" ]]; then + date -d "$created_line" '+%Y/%m/%d-%H:%M:%S' 2>/dev/null && return 0 + fi + fi + + ts="$(stat -c '%W' / 2>/dev/null)" + if [[ -n "$ts" && "$ts" -gt 0 ]]; then + date -d "@$ts" '+%Y/%m/%d-%H:%M:%S' + return 0 + fi + fi + + for candidate in \ + "/var/log/installer" \ + "/root/.bash_history" \ + "/etc/machine-id" \ + "/lost+found"; do + if [[ -e "$candidate" ]]; then + ts="$(stat -c '%W' "$candidate" 2>/dev/null)" + if [[ -n "$ts" && "$ts" -gt 0 ]]; then + date -d "@$ts" '+%Y/%m/%d-%H:%M:%S' + return 0 + fi + ts="$(stat -c '%Y' "$candidate" 2>/dev/null)" + if [[ -n "$ts" && "$ts" -gt 0 ]]; then + date -d "@$ts" '+%Y/%m/%d-%H:%M:%S' + return 0 + fi + fi + done + + printf 'indisponivel\n' +} + +check_os_installation() { + local os_id version_id install_dt + os_id="$(. /etc/os-release && printf '%s' "${ID:-desconhecido}")" + version_id="$(. /etc/os-release && printf '%s' "${VERSION_ID:-desconhecida}")" + install_dt="$(detect_install_datetime)" + + log_line "$INSTAL_LOG" "Sistema identificado: ID=$os_id VERSION_ID=$version_id" + if [[ "$os_id" == "debian" && "$version_id" == "12" ]]; then + log_line "$INSTAL_LOG" "Validacao do SO: OK (Debian 12)" + else + log_line "$INSTAL_LOG" "Validacao do SO: FALHA (esperado Debian 12)" + fi + log_line "$INSTAL_LOG" "Data/hora aproximada da instalacao: $install_dt" +} + +list_interfaces_ipv4() { + ip -o -4 addr show scope global 2>/dev/null | awk '{print $2" "$4}' +} + +detect_default_iface() { + ip route show default 2>/dev/null | awk '/default/ {print $5; exit}' +} + +test_connectivity() { + local target="$1" + local label="$2" + if ping -c 2 -W 3 "$target" >> "$REDE_LOG" 2>&1; then + log_line "$REDE_LOG" "Conectividade $label: OK ($target)" + else + log_line "$REDE_LOG" "Conectividade $label: FALHA ($target)" + fi +} + +check_gateway() { + local gateway + gateway="$(ip route show default 2>/dev/null | awk '/default/ {print $3; exit}')" + if [[ -n "$gateway" ]]; then + log_line "$REDE_LOG" "Gateway padrao: $gateway" + else + log_line "$REDE_LOG" "Gateway padrao nao encontrado" + fi +} + +check_forwarding() { + local current persisted + current="$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || printf 'indisponivel')" + persisted="$(sysctl -n net.ipv4.ip_forward 2>/dev/null || printf 'indisponivel')" + log_line "$REDE_LOG" "ip_forward atual: $current" + log_line "$REDE_LOG" "ip_forward via sysctl: $persisted" +} + +check_nat_rules() { + local default_iface iptables_ok=1 nft_ok=1 + default_iface="$(detect_default_iface)" + if [[ -z "$default_iface" ]]; then + log_line "$REDE_LOG" "Nao foi possivel identificar a interface de saida para internet" + return 0 + fi + + if command -v iptables >/dev/null 2>&1; then + if iptables -t nat -S POSTROUTING 2>/dev/null | grep -qE "MASQUERADE.*-o ${default_iface}|-o ${default_iface} .*MASQUERADE"; then + log_line "$REDE_LOG" "Regra NAT via iptables encontrada para interface $default_iface" + iptables_ok=0 + else + log_line "$REDE_LOG" "Regra NAT via iptables nao encontrada para interface $default_iface" + fi + else + log_line "$REDE_LOG" "iptables nao disponivel" + fi + + if command -v nft >/dev/null 2>&1; then + if nft list ruleset 2>/dev/null | grep -qE "masquerade"; then + log_line "$REDE_LOG" "Regra NAT via nftables encontrada" + nft_ok=0 + else + log_line "$REDE_LOG" "Regra NAT via nftables nao encontrada" + fi + else + log_line "$REDE_LOG" "nftables nao disponivel" + fi + + if [[ "$iptables_ok" -ne 0 && "$nft_ok" -ne 0 ]]; then + log_line "$REDE_LOG" "Nenhuma regra de mascaramento foi confirmada" + fi +} + +check_network_common() { + local iface_count + iface_count="$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -vc '^lo$')" + log_line "$REDE_LOG" "Quantidade de interfaces (excluindo loopback): $iface_count" + log_line "$REDE_LOG" "Enderecos IPv4 detectados:" + list_interfaces_ipv4 >> "$REDE_LOG" 2>&1 || true + + test_connectivity "8.8.8.8" "via IP" + test_connectivity "google.com" "via DNS" +} + +extract_dhcp_iface() { + awk -F'=' '/^\s*INTERFACESv4=/{gsub(/"/, "", $2); print $2; exit}' /etc/default/isc-dhcp-server 2>/dev/null +} + +extract_first_subnet() { + awk ' + /^\s*subnet[[:space:]]+/ { + for (i = 1; i <= NF; i++) { + if ($i == "subnet") subnet = $(i + 1) + if ($i == "netmask") netmask = $(i + 1) + } + if (subnet != "" && netmask != "") { + print subnet " " netmask + exit + } + } + ' /etc/dhcp/dhcpd.conf 2>/dev/null +} + +ip_to_int() { + local a b c d + IFS=. read -r a b c d <<< "$1" + printf '%u\n' "$(( (a << 24) + (b << 16) + (c << 8) + d ))" +} + +ip_in_subnet() { + local ip="$1" + local subnet="$2" + local mask="$3" + local ip_i subnet_i mask_i + ip_i="$(ip_to_int "$ip")" + subnet_i="$(ip_to_int "$subnet")" + mask_i="$(ip_to_int "$mask")" + if (( (ip_i & mask_i) == (subnet_i & mask_i) )); then + return 0 + fi + return 1 +} + +check_dhcp_ns1() { + local dhcp_iface iface_ip_cidr iface_ip subnet_data subnet mask status_name + + if dpkg -s isc-dhcp-server >/dev/null 2>&1; then + log_line "$DHCP_LOG" "Servico isc-dhcp-server instalado" + else + log_line "$DHCP_LOG" "Servico isc-dhcp-server nao instalado" + fi + + if [[ -f /etc/default/isc-dhcp-server ]]; then + dhcp_iface="$(extract_dhcp_iface)" + if [[ -n "$dhcp_iface" ]]; then + log_line "$DHCP_LOG" "Interface configurada em /etc/default/isc-dhcp-server: $dhcp_iface" + else + log_line "$DHCP_LOG" "Interface DHCP nao definida em /etc/default/isc-dhcp-server" + fi + else + log_line "$DHCP_LOG" "Arquivo /etc/default/isc-dhcp-server ausente" + fi + + if [[ -n "${dhcp_iface:-}" ]]; then + iface_ip_cidr="$(ip -o -4 addr show dev "$dhcp_iface" 2>/dev/null | awk '{print $4; exit}')" + iface_ip="${iface_ip_cidr%%/*}" + if [[ -n "$iface_ip" ]]; then + log_line "$DHCP_LOG" "IPv4 da interface DHCP ($dhcp_iface): $iface_ip" + else + log_line "$DHCP_LOG" "Nao foi possivel obter IPv4 da interface DHCP ($dhcp_iface)" + fi + fi + + subnet_data="$(extract_first_subnet)" + if [[ -n "$subnet_data" ]]; then + subnet="${subnet_data%% *}" + mask="${subnet_data##* }" + log_line "$DHCP_LOG" "Escopo identificado em /etc/dhcp/dhcpd.conf: subnet=$subnet netmask=$mask" + if [[ -n "${iface_ip:-}" ]]; then + if ip_in_subnet "$iface_ip" "$subnet" "$mask"; then + log_line "$DHCP_LOG" "IPv4 da interface DHCP condiz com o escopo configurado" + else + log_line "$DHCP_LOG" "IPv4 da interface DHCP NAO condiz com o escopo configurado" + fi + fi + else + log_line "$DHCP_LOG" "Nao foi possivel identificar escopo DHCP em /etc/dhcp/dhcpd.conf" + fi + + status_name="isc-dhcp-server" + if systemctl status "$status_name" >> "$DHCP_LOG" 2>&1; then + log_line "$DHCP_LOG" "Status do servico $status_name: ativo/consultado com sucesso" + else + log_line "$DHCP_LOG" "Status do servico $status_name: falha ou servico inativo" + fi +} + +check_dhcp_web() { + local default_iface gateway client_ip_cidr client_ip + + default_iface="$(detect_default_iface)" + gateway="$(ip route show default 2>/dev/null | awk '/default/ {print $3; exit}')" + + if [[ -z "$default_iface" || -z "$gateway" ]]; then + log_line "$DHCP_LOG" "Nao foi possivel identificar interface/gateway da WEB" + return 0 + fi + + client_ip_cidr="$(ip -o -4 addr show dev "$default_iface" 2>/dev/null | awk '{print $4; exit}')" + client_ip="${client_ip_cidr%%/*}" + + log_line "$DHCP_LOG" "Interface de rede principal da WEB: $default_iface" + log_line "$DHCP_LOG" "Gateway configurado na WEB: $gateway" + if [[ -n "$client_ip" ]]; then + log_line "$DHCP_LOG" "IPv4 atual da WEB na interface $default_iface: $client_ip" + else + log_line "$DHCP_LOG" "Nao foi possivel obter o IPv4 atual da WEB na interface $default_iface" + fi + log_line "$DHCP_LOG" "Teste ativo com dhcping desabilitado por decisao de implementacao" +} + +parse_args() { + while (($# > 0)); do + case "$1" in + -h|--help) + print_usage + exit 0 + ;; + *) + if [[ ! "$1" =~ ^[0-9]+$ ]]; then + printf 'Erro: RA invalido: %s\n' "$1" >&2 + exit 1 + fi + RA_ARGS+=("$1") + if ((${#RA_ARGS[@]} > 3)); then + printf 'Erro: nao e permitido informar mais que tres integrantes.\n' >&2 + exit 1 + fi + shift + ;; + esac + done + +} + +run_vm_checks() { + check_os_installation + check_network_common + + if [[ "$VM_TYPE" == "NS1" ]]; then + check_forwarding + check_nat_rules + check_dhcp_ns1 + else + check_gateway + check_dhcp_web + fi +} + +create_archive() { + local archive_path + archive_path="/root/dupla_${DUPLA_NUM}_${VM_TYPE}.tar.gz" + if tar -czf "$archive_path" -C "/root" "$(basename "$BASE_DIR")"; then + main_log "Arquivo compactado criado: $archive_path" + printf '%s\n' "$archive_path" + return 0 + fi + error_log "Falha ao criar arquivo compactado" + return 1 +} + +send_to_telegram() { + local archive_path="$1" + local response_file http_code + + response_file="$ENVIO_DIR/telegram_response.json" + + if [[ -z "$BOT_TOKEN" || -z "$CHAT_ID" ]]; then + log_line "$TELEGRAM_LOG" "Envio nao realizado: BOT_TOKEN ou CHAT_ID nao informado" + return 1 + fi + + log_line "$TELEGRAM_LOG" "Iniciando envio do arquivo $archive_path" + http_code="$( + curl -sS -o "$response_file" -w '%{http_code}' \ + -F "chat_id=$CHAT_ID" \ + -F "document=@$archive_path" \ + "https://api.telegram.org/bot${BOT_TOKEN}/sendDocument" 2>>"$TELEGRAM_LOG" + )" + + log_line "$TELEGRAM_LOG" "HTTP status do envio: $http_code" + if [[ -f "$response_file" ]]; then + log_line "$TELEGRAM_LOG" "Resposta da API salva em $response_file" + fi + + if [[ "$http_code" == "200" ]]; then + log_line "$TELEGRAM_LOG" "Envio concluido com sucesso" + return 0 + fi + + log_line "$TELEGRAM_LOG" "Envio falhou" + return 1 +} + +main() { + require_root + parse_args "$@" + + printf 'Coleta e validacao da VM\n' + DUPLA_NUM="$(prompt_numeric 'Numero da dupla: ')" || exit 1 + collect_identification_input || exit 1 + VM_TYPE="$(prompt_vm_type)" || exit 1 + + setup_dirs + main_log "Inicio da execucao do script $SCRIPT_NAME" + main_log "Numero da dupla: $DUPLA_NUM" + main_log "Tipo de VM selecionado: $VM_TYPE" + + save_identification + ensure_packages + collect_configs + run_vm_checks + + local archive_path="" + if archive_path="$(create_archive)"; then + send_to_telegram "$archive_path" || true + fi + + main_log "Execucao finalizada" + printf 'Coleta concluida. Evidencias em: %s\n' "$BASE_DIR" +} + +main "$@"