#!/usr/bin/php
<?php
// generate-report.php
// Legge web_list.json, controlla SSL + contenuto, salva web_check.json
// Aggiunge campi ssl-cn e ssl-org (Common Name e Organisation dall'issuer)

declare(strict_types=1);
set_time_limit(0);
date_default_timezone_set('UTC');

// --- Configurazione ---
$inputFile  = __DIR__ . '/web_list.json';
$outputFile = __DIR__ . '/web_check.json';
$timeout    = 10; // timeout secondi per connessioni
$opensslTimeoutBin = (is_executable('/usr/bin/timeout') ? '/usr/bin/timeout' :
                      (is_executable('/bin/timeout') ? '/bin/timeout' : ''));

// --- Helpers ---
function ssl_days_left($expiry_date) {
    if (empty($expiry_date)) return null;
    $expiry = DateTime::createFromFormat('d/m/Y', $expiry_date);
    if (!$expiry) return null;
    $now = new DateTime();
    $interval = $now->diff($expiry);
    return (int)$interval->format('%r%a'); // giorni con segno
}

function normalizeDomainHost(string $domain): string {
    if (preg_match('#^https?://#i', $domain)) {
        $p = parse_url($domain);
        return $p['host'] ?? rtrim($domain, '/');
    }
    $d = preg_replace('#/.*$#', '', $domain);
    $d = preg_replace('/:\d+$/', '', $d);
    return $d;
}

// Estrazione CN / O dall'issuer ottenuto via openssl_x509_parse
function extractIssuerFieldsFromParsed(array $cert_info): array {
    $cn = null;
    $org = null;
    if (!empty($cert_info['issuer']) && is_array($cert_info['issuer'])) {
        $issuer = $cert_info['issuer'];
        // chiavi possibili: CN, commonName, O, organizationName
        $cnKeys = ['CN', 'commonName', 'cn'];
        $orgKeys = ['O', 'organizationName', 'o'];
        foreach ($cnKeys as $k) {
            if (isset($issuer[$k]) && $issuer[$k] !== '') { $cn = $issuer[$k]; break; }
        }
        foreach ($orgKeys as $k) {
            if (isset($issuer[$k]) && $issuer[$k] !== '') { $org = $issuer[$k]; break; }
        }
    }
    return ['cn' => $cn, 'org' => $org];
}

// Ottieni informazioni certificato via stream_socket_client (SNI), fallback a openssl s_client
function getCertInfo(string $host, int $port = 443, int $timeout = 10): array {
    $result = [
        'status' => 0,
        'issued' => null,
        'expiry' => null,
        'cn' => null,
        'org' => null,
        'error' => null
    ];

    // stream context con SNI
    $ctx = stream_context_create([
        'ssl' => [
            'capture_peer_cert' => true,
            'verify_peer' => false,
            'verify_peer_name' => false,
            'peer_name' => $host,
            'SNI_enabled' => true,
        ]
    ]);

    $address = "ssl://{$host}:{$port}";
    $client = @stream_socket_client($address, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $ctx);
    if ($client !== false) {
        $params = stream_context_get_params($client);
        @fclose($client);
        if (!empty($params['options']['ssl']['peer_certificate'])) {
            $cert = $params['options']['ssl']['peer_certificate'];
            $cert_info = @openssl_x509_parse($cert);
            if ($cert_info !== false) {
                $issued = isset($cert_info['validFrom_time_t']) ? date('d/m/Y', $cert_info['validFrom_time_t']) : null;
                $expiry = isset($cert_info['validTo_time_t']) ? date('d/m/Y', $cert_info['validTo_time_t']) : null;
                // preferiamo CN come "company" visibile, ma manteniamo CN e ORG separati
                $fields = extractIssuerFieldsFromParsed($cert_info);
                $company = $fields['cn'] ?? ($fields['org'] ?? null);
                $isValid = (isset($cert_info['validTo_time_t']) && time() < $cert_info['validTo_time_t']) ? 1 : 0;

                $result['status']  = $isValid;
                $result['issued']  = $issued;
                $result['expiry']  = $expiry;
                $result['company'] = $company;
                $result['cn']      = $fields['cn'];
                $result['org']     = $fields['org'];
                return $result;
            } else {
                $result['error'] = 'openssl_x509_parse failed';
                return $result;
            }
        } else {
            $result['error'] = 'no peer_certificate found';
            return $result;
        }
    }

    // fallback: openssl s_client (se exec è permesso)
    $escHost = escapeshellarg($host);
    $cmd = sprintf("openssl s_client -connect %s:443 -servername %s -showcerts </dev/null 2>/dev/null | openssl x509 -noout -dates -issuer", $escHost, $escHost);
    if (!empty($GLOBALS['opensslTimeoutBin'])) {
        $cmd = $GLOBALS['opensslTimeoutBin'] . " {$timeout} " . $cmd;
    }
    @exec($cmd, $out, $ret);
    if ($ret === 0 && !empty($out)) {
        $joined = implode("\n", $out);
        $issued = null; $expiry = null; $company = null; $cn = null; $org = null;
        if (preg_match('/notBefore=(.+)/i', $joined, $m1)) {
            $issuedRaw = trim($m1[1]);
            $issuedTs = strtotime($issuedRaw);
            $issued = $issuedTs ? date('d/m/Y', $issuedTs) : $issuedRaw;
        }
        if (preg_match('/notAfter=(.+)/i', $joined, $m2)) {
            $expiryRaw = trim($m2[1]);
            $expiryTs = strtotime($expiryRaw);
            $expiry = $expiryTs ? date('d/m/Y', $expiryTs) : $expiryRaw;
        }
        // extract CN and O from issuer string
        if (preg_match('/issuer=.*CN=([^,\/]+)/i', $joined, $mCN)) {
            $cn = trim($mCN[1]);
        }
        if (preg_match('/issuer=.*O=([^,\/]+)/i', $joined, $mO)) {
            $org = trim($mO[1]);
        }
        // company fallback: prefer CN then org
        $company = $cn ?? $org ?? null;

        $isValid = 0;
        if ($expiry) {
            $dt = DateTime::createFromFormat('d/m/Y', $expiry);
            if ($dt) $isValid = (time() < $dt->getTimestamp()) ? 1 : 0;
        }

        $result['status']  = $isValid;
        $result['issued']  = $issued;
        $result['expiry']  = $expiry;
        $result['company'] = $company;
        $result['cn']      = $cn;
        $result['org']     = $org;
        return $result;
    }

    $result['error'] = "stream_socket_client failed: $errstr ($errno)";
    return $result;
}

// Fetch content con cURL (supporta basic auth), ritorna array con body, http_code, error
function fetchWithCurl(string $url, ?string $user, ?string $pass, int $timeout = 10) {
    if (!function_exists('curl_init')) return ['ok' => false, 'error' => 'curl missing'];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);

    // non forziamo verifica CA per evitare falsi negativi in ambienti senza CA bundle
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

    // browser-like headers per evitare 403 anti-bot
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117 Safari/537.36');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']);

    if ($user !== null && $pass !== null) {
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
        curl_setopt($ch, CURLOPT_USERPWD, $user . ':' . $pass);
    }

    $body = curl_exec($ch);
    $err  = curl_error($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($body === false) return ['ok' => false, 'error' => $err, 'http_code' => $code];
    return ['ok' => true, 'body' => $body, 'http_code' => $code];
}

// Controlla presenza stringa (1/0) e ritorna anche eventuale debug
function checkContentMatch(string $url, string $match, ?string $user, ?string $pass, int $timeout = 10): array {
    $resp = fetchWithCurl($url, $user, $pass, $timeout);
    if (!$resp['ok']) {
        return ['ok' => 0, 'http_code' => $resp['http_code'] ?? null, 'error' => $resp['error'] ?? 'fetch failed'];
    }
    $found = (strpos($resp['body'], $match) !== false) ? 1 : 0;
    return ['ok' => $found, 'http_code' => $resp['http_code'] ?? null, 'error' => null];
}

// --- Leggi input JSON ---
if (!file_exists($inputFile)) {
    fwrite(STDERR, "File $inputFile non trovato\n");
    exit(1);
}
$raw = file_get_contents($inputFile);
$domains = json_decode($raw, true);
if ($domains === null) {
    fwrite(STDERR, "Errore parsing JSON di input\n");
    exit(1);
}

$results = [];
$totalSSLok = 0;
$totalContentOk = 0;

foreach ($domains as $entry) {
    $host = $entry['host'] ?? null;
    $domain = $entry['domain'] ?? null;
    $path = $entry['path'] ?? '';
    $word = $entry['word-to-check'] ?? '';
    $basicAuth = $entry['basic_auth'] ?? false;

    // supporta diversi nomi per password in input
    $user = ($basicAuth && !empty($entry['basic_auth_name'])) ? $entry['basic_auth_name'] : null;
    $pass = null;
    if ($basicAuth) {
        if (!empty($entry['basic_auth_password'])) $pass = $entry['basic_auth_password'];
        elseif (!empty($entry['basic_auth_pass'])) $pass = $entry['basic_auth_pass'];
        elseif (!empty($entry['basic_auth_passwd'])) $pass = $entry['basic_auth_passwd'];
    }

    // costruisci URL (https://domain + path). se path vuoto -> '/'
    $domainClean = preg_replace('#^https?://#i', '', rtrim($domain ?? '', '/'));
    $pathClean = $path === '' ? '/' : (strpos($path, '/') === 0 ? $path : '/' . $path);
    $url = 'https://' . $domainClean . $pathClean;

    // estrai host per SSL (rimuove eventuale port/path)
    $sslHost = normalizeDomainHost($domain ?? '');

    // --- SSL info ---
    $sslInfo = getCertInfo($sslHost, 443, $timeout);

    // --- content check ---
    $contentCheck = checkContentMatch($url, $word, $user, $pass, $timeout);

    $word_ok = $contentCheck['ok'];
    $http_code = $contentCheck['http_code'] ?? null;
    $content_error = $contentCheck['error'] ?? null;

    if ($sslInfo['status'] === 1) $totalSSLok++;
    if ($word_ok === 1) $totalContentOk++;

    $results[] = [
        'host' => $host,
        'domain' => $domain,
        'path' => $path,
        'wordtocheck' => $word,
        'wordcheck_ok' => $word_ok,
        'sslcheck_ok' => $sslInfo['status'],
        'ssl-released' => $sslInfo['issued'],
        'ssl-expiry' => $sslInfo['expiry'],
        'ssl-cn' => $sslInfo['cn'] ?? null,
        'ssl-org' => $sslInfo['org'] ?? null,
        // campi di debug utili per capire perché auth/content falliscono
        'content_http_code' => $http_code,
        'content_error' => $content_error,
        'ssl_error' => $sslInfo['error'] ?? null
    ];
}

// salva output (unescaped slashes)
file_put_contents($outputFile, json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

// stampa sommario
echo "Check completato. Risultati salvati in $outputFile\n";
echo "Totali: SSL ok = $totalSSLok, Content ok = $totalContentOk\n";
