Wymagane uprawnienia admina
Wymagane uprawnienia admina
Wymagane uprawnienia admina
← pag

Commit 115b374

0
plików
+0
dodanych
-0
usuniętych
@@ -326,6 +326,23 @@ T = {
326 326 "autoremove_ok": "Usunięto {} osieroconych pakietów.",
327 327 "autoremove_none": "Brak osieroconych pakietów.",
328 328 "downloaded": "Pobrano {} do cache ({:.2f} MB).",
329 + "sec_downgrade": "Downgrade blocked: {pkg} {new} < {old}",
330 + "sec_suid": "SUID stripped from {path}",
331 + "sec_https": "HTTPS required for repos",
332 + "sec_badname": "Invalid package name: {name}",
333 + "sec_toobig": "Package too large: {size_mb}MB > {max_mb}MB",
334 + "sec_conflict": "File conflict: {path} owned by {owner}",
335 + "sec_audit": "{pkg} installed by {user}",
336 + "sec_locked": "Another pag process is running",
337 + "sec_downgrade_pl": "Blokada downgrade: {pkg} {new} < {old}",
338 + "sec_suid_pl": "SUID usuniety z {path}",
339 + "sec_https_pl": "Repozytorium wymaga HTTPS",
340 + "sec_badname_pl": "Nieprawidlowa nazwa pakietu: {name}",
341 + "sec_toobig_pl": "Paczka za duza: {size_mb}MB > {max_mb}MB",
342 + "sec_conflict_pl": "Konflikt plikow: {path} nalezy do {owner}",
343 + "sec_audit_pl": "{pkg} zainstalowany przez {user}",
344 + "sec_locked_pl": "Inny proces pag juz dziala",
345 +
329 346 "provides_mapped": "{} → {} (provides)",
330 347 "stats_title": "Statystyki PAG",
331 348 "stats_packages": "Zainstalowane pakiety",
@@ -671,6 +688,44 @@ def _check_path_safety(name: str) -> bool:
671 688 return True
672 689 return False
673 690
691 +
692 +def _validate_pkg_name(name):
693 + return bool(ALLOWED_PKG_RE.match(name))
694 +
695 +def _acquire_lock():
696 + import fcntl
697 + os.makedirs(os.path.dirname(LOCK_FILE), exist_ok=True)
698 + lf = open(LOCK_FILE, "w")
699 + try:
700 + fcntl.flock(lf, fcntl.LOCK_EX | fcntl.LOCK_NB)
701 + return lf
702 + except (IOError, OSError):
703 + print(f" {_("sec_locked")}")
704 + sys.exit(1)
705 +
706 +def _audit(msg):
707 + from datetime import datetime, timezone
708 + os.makedirs(os.path.dirname(AUDIT_LOG), exist_ok=True)
709 + with open(AUDIT_LOG, "a") as f:
710 + f.write(datetime.now(timezone.utc).isoformat() + " " + msg + "\n")
711 +
712 +def _strip_suid(path):
713 + try:
714 + st = os.stat(path)
715 + if st.st_mode & 0o4000:
716 + os.chmod(path, st.st_mode & ~0o4000)
717 + print(f" {_("sec_suid", path=path)}")
718 + except OSError:
719 + pass
720 +
721 +def _check_downgrade(pkg_name, new_ver, installed_db):
722 + if pkg_name in installed_db:
723 + old = installed_db[pkg_name].get("version", "0")
724 + if new_ver < old:
725 + print(f" {_("sec_downgrade", pkg=pkg_name, new=new_ver, old=old)}")
726 + return False
727 + return True
728 +
674 729 def _safe_extractall(tar: tarfile.TarFile, dest: str, *, preserve_perms: bool = True):
675 730 """
676 731 Bezpieczne rozpakowanie archiwum tar z ochroną przed Directory Traversal.
@@ -709,7 +764,9 @@ def _safe_extractall(tar: tarfile.TarFile, dest: str, *, preserve_perms: bool =
709 764 continue
710 765
711 766 # Rozpakuj z zachowaniem metadanych
767 + target = os.path.join(dest, name)
712 768 tar.extract(member, dest, set_attrs=preserve_perms, numeric_owner=False)
769 + _strip_suid(target)
713 770
714 771
715 772 def _sha256_file(path: str) -> str:
@@ -1305,6 +1362,7 @@ def cmd_rollback():
1305 1362 # =============================================================================
1306 1363
1307 1364 def cmd_install(package_names, as_dep=False):
1365 + _lock = _acquire_lock()
1308 1366 ensure_dirs()
1309 1367 installed_db = load_json(INSTALLED_DB)
1310 1368 world = load_world()
@@ -1335,6 +1393,16 @@ def cmd_install(package_names, as_dep=False):
1335 1393 if not to_install:
1336 1394 print(f"✅ {_('all_installed')}"); return 0
1337 1395
1396 + MAX_MB = MAX_PKG_SIZE // 1048576
1397 + for n in to_install:
1398 + if not _validate_pkg_name(n):
1399 + print(f" {_("sec_badname", name=n)}")
1400 + return 1
1401 + sz = repo_pkgs[n].size_bytes if n in repo_pkgs else 0
1402 + if sz > MAX_PKG_SIZE:
1403 + mb = sz // 1048576
1404 + print(f" {_("sec_toobig", size_mb=mb, max_mb=MAX_MB)}")
1405 + return 1
1338 1406 total_size = sum(repo_pkgs[n].size_bytes for n in to_install if n in repo_pkgs)
1339 1407 print(f"\n📦 {_('to_install', len(to_install), total_size/1048576)}")
1340 1408 for name in to_install:
@@ -2011,6 +2079,8 @@ def cmd_stats():
2011 2079
2012 2080
2013 2081 def cmd_repo_add(url):
2082 + if not url.startswith("https://") and not os.environ.get("PAG_INSECURE"):
2083 + print(f" {_("sec_https")}"); return 1
2014 2084 repos = get_repos()
2015 2085 url = url.rstrip("/")
2016 2086 if url in repos: print(f"⚠ {_('repo_exists', url)}"); return