#!/usr/bin/php 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 | 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";