🔒 Repository is read-only – file editing is disabled.

PaganLinux/tmp-verify-critical-sums.py main

138 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
#!/usr/bin/env python3
"""Weryfikuje sumy SHA256 dla receptur KRYTYCZNYCH: pobiera kazde zrodlo,
liczy sha256 i porownuje z suma w recepturze.

To samo sprawdzenie, ktore wykrylo bledna sume tarballa util-linux 2.42.2
(pozostalosc po 2.41.x). Wyniki zapisywane przyrostowo.
"""
import hashlib
import json
import os
import re
import subprocess
import sys
import tempfile
import time

import yaml

R = "/var/lib/pagan-sync/recipes"
OUT = "/tmp/critical-sums-report.json"
CRIT = ("glibc gcc binutils openssl gnutls nettle sudo shadow pam systemd dbus bash coreutils "
        "util-linux curl wget gnupg libgcrypt libgpg-error libassuan libksba libxcrypt make-ca "
        "openssh polkit seatd kernel kernel-lts kernel-mainline libcap e2fsprogs procps-ng python "
        "perl zlib xz tar libarchive pcre2 expat iproute2 kmod elfutils attr acl gzip bzip2 less "
        "sed grep findutils gawk").split()
PH = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
MAXBYTES = 600 * 1024 * 1024


def as_list(v):
    if v is None:
        return []
    return v if isinstance(v, list) else [v]


def expand(s, d):
    def rep(m):
        k = m.group(1)
        return str(d[k]) if k in d and not isinstance(d[k], (dict, list)) else m.group(0)
    return PH.sub(rep, s)


def sha_file(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for c in iter(lambda: f.read(1 << 20), b""):
            h.update(c)
    return h.hexdigest()


def check_url(url, expected):
    fd, path = tempfile.mkstemp(prefix="pagsum-")
    os.close(fd)
    try:
        p = subprocess.run(["curl", "-sS", "-L", "--max-time", "900", "-o", path, "-w",
                            "%{http_code} %{size_download}", "-A", "curl/8.7.1", url],
                           capture_output=True, text=True)
        parts = (p.stdout or "").strip().split()
        code = parts[0] if parts else "?"
        size = int(parts[1]) if len(parts) > 1 else 0
        if code != "200":
            return "FAIL", f"HTTP {code}", size
        got = sha_file(path)
        if got == expected:
            return "OK", got, size
        return "MISMATCH", got, size
    finally:
        try:
            os.unlink(path)
        except OSError:
            pass


def main():
    report = {}
    results = []
    targets = []
    for dp, _d, fs in os.walk(R):
        if ".git" in dp or "PAGBUILD.yaml" not in fs:
            continue
        p = os.path.join(dp, "PAGBUILD.yaml")
        rel = os.path.relpath(p, R)
        try:
            d = yaml.safe_load(open(p, newline="", encoding="utf-8")) or {}
        except Exception:
            continue
        if d.get("pkgname") not in CRIT:
            continue
        srcs = as_list(d.get("source"))
        sums = as_list(d.get("sha256sums"))
        for i, (s, h) in enumerate(zip(srcs, sums)):
            if str(h).strip().upper().startswith("SKIP"):
                continue
            targets.append((rel, d, i, str(s), str(h)))

    print(f"zrodel do sprawdzenia: {len(targets)}", flush=True)
    ok = mism = fail = 0
    total_bytes = 0
    t0 = time.time()
    for n, (rel, d, i, src, expected) in enumerate(targets, 1):
        exp = expand(src, d)
        if exp.startswith(("http://", "https://", "ftp://")):
            status, got, size = check_url(exp, expected)
            total_bytes += size
        else:
            local = os.path.join(os.path.dirname(f"{R}/{rel}"), exp)
            if os.path.isfile(local):
                g = sha_file(local)
                status, got, size = ("OK" if g == expected else "MISMATCH"), g, os.path.getsize(local)
                total_bytes += size
            else:
                status, got, size = "FAIL", "brak pliku lokalnego", 0
        rec = {"recipe": rel, "source": src, "expected": expected, "got": got,
               "status": status, "size": size}
        results.append(rec)
        if status == "OK":
            ok += 1
        elif status == "MISMATCH":
            mism += 1
        else:
            fail += 1
        if status != "OK":
            print(f"  !! {status:8s} {rel} :: {exp[:90]}", flush=True)
            print(f"       oczekiwano: {expected}", flush=True)
            print(f"       otrzymano : {got}", flush=True)
        if n % 10 == 0:
            print(f"  ... {n}/{len(targets)}  (ok={ok} mismatch={mism} fail={fail})", flush=True)
            json.dump(results, open(OUT, "w"), indent=1, ensure_ascii=False)

    json.dump(results, open(OUT, "w"), indent=1, ensure_ascii=False)
    print()
    print(f"OK: {ok} | MISMATCH (zla suma): {mism} | FAIL (nie pobrano): {fail}")
    print(f"pobrano lacznie: {total_bytes/1048576:.1f} MB w {time.time()-t0:.0f}s")
    print(f"zapisano {OUT}")


if __name__ == "__main__":
    sys.exit(main())