#!/usr/bin/env python3
"""
Balse — escaneo de puertos (para correr en TU propio computador)
Proyecto educativo de Datapoint

A diferencia de la herramienta de escaneo de puertos del sitio web
(que corre en el servidor de Balse), este script corre en TU propio
computador. Eso significa que la responsabilidad y el permiso son
tuyos: solo debes escanear equipos de tu propiedad o con autorización
explícita del dueño.

Escanear redes ajenas sin autorización es ilegal en Chile (Ley 19.223,
Delitos Informáticos) y en la mayoría de los países. Este script pide
confirmación explícita antes de escanear, y no lo hace por defecto.

Cómo usarlo:
    python3 port_scan_local.py 192.168.1.1
    python3 port_scan_local.py mi-servidor-casero.local --timeout 1.0

Requiere: Python 3.7 o más nuevo. No necesita instalar nada más
(usa solo el módulo 'socket' de la librería estándar).
"""

import argparse
import socket
import sys

# Misma lista curada de puertos comunes que usa la herramienta del sitio web.
PUERTOS_COMUNES = {
    21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS",
    80: "HTTP", 110: "POP3", 143: "IMAP", 443: "HTTPS",
    3306: "MySQL", 3389: "RDP", 8080: "HTTP alternativo",
}


def pedir_confirmacion(host: str) -> bool:
    print("=" * 64)
    print("AVISO IMPORTANTE")
    print("=" * 64)
    print(
        f"Estás por escanear los puertos de: {host}\n\n"
        "Solo continúa si este equipo es tuyo, o cuentas con autorización\n"
        "explícita de su dueño para escanearlo. Escanear redes ajenas sin\n"
        "autorización es ilegal en Chile (Ley 19.223) y en la mayoría de\n"
        "los países, incluso si no causas ningún daño.\n"
    )
    respuesta = input("Escribe CONFIRMO para continuar: ").strip()
    return respuesta.upper() == "CONFIRMO"


def escanear(host: str, timeout: float) -> None:
    try:
        ip = socket.gethostbyname(host)
    except socket.gaierror:
        print(f"No se pudo resolver el host '{host}'.")
        sys.exit(1)

    print(f"\nEscaneando {host} ({ip})...\n")
    abiertos = []

    for puerto, nombre in PUERTOS_COMUNES.items():
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        resultado = sock.connect_ex((ip, puerto))
        sock.close()
        estado = "ABIERTO" if resultado == 0 else "cerrado"
        if resultado == 0:
            abiertos.append(puerto)
        print(f"  puerto {puerto:>5} ({nombre:<18}) — {estado}")

    print(f"\nResumen: {len(abiertos)} de {len(PUERTOS_COMUNES)} puertos abiertos.")
    if abiertos:
        print(f"Abiertos: {', '.join(str(p) for p in abiertos)}")


def pausar_al_salir() -> None:
    """Evita que la ventana se cierre de golpe si el script se abrió con doble clic."""
    try:
        input("\nPresiona Enter para cerrar...")
    except (EOFError, KeyboardInterrupt):
        pass


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Escáner de puertos educativo, para correr en tu propio equipo — Balse / Datapoint"
    )
    parser.add_argument(
        "host", nargs="?", default=None,
        help="IP o nombre del host a escanear (tuyo, o autorizado)"
    )
    parser.add_argument(
        "--timeout", type=float, default=0.6,
        help="Segundos de espera por puerto (por defecto 0.6)"
    )
    args = parser.parse_args()

    host = args.host
    if not host:
        print("Balse — escaneo de puertos local\n")
        host = input("¿Qué host quieres escanear? (ej: 192.168.1.1, o tu-servidor.local): ").strip()
        if not host:
            print("No escribiste nada, saliendo.")
            pausar_al_salir()
            return

    if not pedir_confirmacion(host):
        print("\nCancelado. No se realizó ningún escaneo.")
        pausar_al_salir()
        return

    escanear(host, args.timeout)
    pausar_al_salir()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nInterrumpido por el usuario.")
        pausar_al_salir()
