#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Convierte un log de eventos de correo (Cloudron UI export, texto + JSON)
a un CSV listo para importar en Maltego.

Uso:
    python mail_log_to_maltego.py mail_events_raw.txt mail_events_maltego.csv
"""

import sys
import json
import csv
from datetime import datetime, timezone

def extract_json_objects(text: str):
    """
    Extrae objetos JSON de un texto donde hay bloques como:

        1 dic ...
        {
            "ts": 1764634589157,
            ...
        }
        2 dic ...
        {
            ...
        }

    Usa conteo de llaves { } para agrupar cada objeto.
    """
    objs = []
    buf = []
    depth = 0
    in_json = False

    for ch in text:
        if ch == '{':
            in_json = True
            depth += 1

        if in_json:
            buf.append(ch)

        if ch == '}' and in_json:
            depth -= 1
            if depth == 0:
                # fin de objeto
                raw = ''.join(buf)
                buf = []
                in_json = False
                try:
                    obj = json.loads(raw)
                    objs.append(obj)
                except Exception:
                    # si falla el parseo, lo ignoramos
                    pass

    return objs


def ts_to_iso(ts_value):
    """
    Convierte ts (en milisegundos desde epoch) a ISO 8601 UTC.
    Si falla, devuelve cadena vacía.
    """
    try:
        ts = float(ts_value) / 1000.0
        dt = datetime.fromtimestamp(ts, tz=timezone.utc)
        return dt.isoformat()
    except Exception:
        return ""


def event_to_rows(ev: dict):
    """
    Convierte un evento (dict) a lista de filas (por cada rcptTo).
    Cada fila es una lista en este orden:

        [timestamp_iso, type, direction, mail_from, rcpt_to, ip, host, message, plugin]
    """
    rows = []

    timestamp_iso = ts_to_iso(ev.get("ts"))
    ev_type = ev.get("type", "") or ""
    direction = ev.get("direction", "") or ""
    mail_from = ev.get("mailFrom", "") or ""

    # rcptTo puede ser lista o None
    rcpts = ev.get("rcptTo") or [""]
    if isinstance(rcpts, str):
        rcpts = [rcpts]

    remote = ev.get("remote") or {}
    ip = remote.get("ip", "") or ""
    host = remote.get("host", "") or ""
    message = ev.get("message", "") or ""
    plugin = ev.get("pluginName", "") or ""

    for rcpt in rcpts:
        rcpt_str = rcpt or ""
        rows.append([
            timestamp_iso,
            ev_type,
            direction,
            mail_from,
            rcpt_str,
            ip,
            host,
            message,
            plugin
        ])

    return rows


def main():
    if len(sys.argv) != 3:
        print("Uso: python mail_log_to_maltego.py <entrada.log> <salida.csv>")
        sys.exit(1)

    in_path = sys.argv[1]
    out_path = sys.argv[2]

    # Leer todo el fichero de entrada (texto)
    with open(in_path, "r", encoding="utf-8", errors="ignore") as f:
        raw = f.read()

    # Extraer objetos JSON
    events = extract_json_objects(raw)
    if not events:
        print("No se detectaron objetos JSON en el archivo de entrada.")
        sys.exit(1)

    # Escribir CSV
    with open(out_path, "w", encoding="utf-8", newline="") as f:
        writer = csv.writer(f)
        # cabecera pensada para Maltego
        writer.writerow([
            "timestamp_iso",
            "type",
            "direction",
            "mail_from",
            "rcpt_to",
            "ip",
            "host",
            "message",
            "plugin"
        ])

        for ev in events:
            for row in event_to_rows(ev):
                writer.writerow(row)

    print(f"OK. Eventos procesados: {len(events)}")
    print(f"CSV generado: {out_path}")


if __name__ == "__main__":
    main()
