🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
#!/usr/bin/env python3
"""Audyt http:// w zrodlach receptur - wersja przyrostowa.
Faza A: skan receptur -> mapa host -> lista (rel, key, url). Zapisywana od razu.
Faza B: test https:// dla kazdego hosta (max 2 URL-e), wynik logowany na biezaco
i dopisywany do cache, zeby przerwanie nie tracilo pracy.
"""
import collections
import json
import os
import re
import ssl
import sys
import urllib.error
import urllib.request
import yaml
R = "/var/lib/pagan-sync/recipes"
KEYS = ("source", "sources", "url")
SCAN = "/tmp/http-audit-scan.json"
CACHE = "/tmp/http-audit-cache.json"
UA = {"User-Agent": "pagan-audit/1.0"}
def as_list(v):
if v is None:
return []
return v if isinstance(v, list) else [v]
def log(msg):
print(msg, flush=True)
def phase_scan():
findings = collections.defaultdict(list)
for dp, _d, fs in os.walk(R):
if "PAGBUILD.yaml" not in fs:
continue
p = os.path.join(dp, "PAGBUILD.yaml")
rel = p[len(R) + 1:]
try:
d = yaml.safe_load(open(p, encoding="utf-8")) or {}
except Exception:
continue
for k in KEYS:
for v in as_list(d.get(k)):
s = str(v)
m = re.match(r"^https?://([^/\s]+)", s)
if s.startswith("http://") and m:
findings[m.group(1)].append([rel, k, s])
json.dump(findings, open(SCAN, "w"), indent=1, ensure_ascii=False)
log(f"Faza A: hostow={len(findings)} wystapien={sum(len(v) for v in findings.values())}")
log(f"zapisano {SCAN}")
return findings
def try_url(url, timeout=12):
ctx = ssl.create_default_context()
try:
req = urllib.request.Request(url, headers=UA, method="HEAD")
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r:
return True, r.status
except urllib.error.HTTPError as e:
if e.code in (403, 405, 400, 501):
pass # sprobuj GET
else:
return False, f"HTTP {e.code}"
except Exception:
pass
try:
req = urllib.request.Request(url, headers=UA, method="GET")
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r:
return True, r.status
except urllib.error.HTTPError as e:
if e.code in (403, 405):
return True, e.code
return False, f"HTTP {e.code}"
except Exception as e:
return False, type(e).__name__
def phase_test(findings):
cache = {}
if os.path.exists(CACHE):
try:
cache = json.load(open(CACHE))
except Exception:
cache = {}
def save():
json.dump(cache, open(CACHE, "w"), indent=1, ensure_ascii=False)
results = {}
order = sorted(findings, key=lambda h: -len(findings[h]))
for host in order:
urls = findings[host]
ok_any = False
statuses = []
for rel, k, url in urls[:2]:
hu = "https://" + url[len("http://"):]
if hu in cache:
ok, st = cache[hu]
else:
ok, st = try_url(hu)
cache[hu] = [ok, st]
save()
statuses.append(f"{st}")
if ok:
ok_any = True
break
results[host] = {"n": len(urls), "ok": ok_any, "statuses": statuses,
"sample": urls[0][2]}
log(f"{'OK ' if ok_any else 'FAIL'} n={len(urls):4d} {host:45s} {','.join(statuses)}")
json.dump(results, open("/tmp/http-audit-results.json", "w"), indent=1, ensure_ascii=False)
return results
def main():
findings = phase_scan()
results = phase_test(findings)
n_ok = sum(1 for r in results.values() if r["ok"])
ent_ok = sum(r["n"] for r in results.values() if r["ok"])
total = sum(r["n"] for r in results.values())
log("")
log(f"hosty https dziala: {n_ok}/{len(results)}")
log(f"wystapien do zmiany: {ent_ok}/{total}")
if __name__ == "__main__":
sys.exit(main())