atlus/backend/privdrop.py
roberts 6a0c8757f8 Run terminals and GUI apps as the authenticated user, not root
Atlus runs as root (systemd) but user-facing processes must run under the
authenticated user's identity. Added privilege-dropping via preexec_fn
(os.setgid + os.initgroups + os.setuid) to both terminal PTY spawning
and GUI app launching. System admin operations (services, packages,
network, updates) intentionally remain root.

Autostart apps now support a configurable default_user; without one set,
autostart defers until the first user logs in and runs as that user.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 00:35:52 -05:00

37 lines
1.3 KiB
Python

"""Privilege-dropping helpers for spawning processes as non-root users.
Atlus runs as root (systemd service) but user-facing processes — terminal
PTYs and GUI applications — must run as the authenticated user. This
module provides a ``preexec_fn`` factory that drops privileges after
``fork()`` but before ``exec()``.
"""
import os
import pwd
from typing import Callable
def get_user_info(username: str) -> tuple[int, int, str, str]:
"""Return (uid, gid, home_dir, shell) for *username*.
Raises ``KeyError`` if the user does not exist on the system.
"""
pw = pwd.getpwnam(username)
return pw.pw_uid, pw.pw_gid, pw.pw_dir, pw.pw_shell
def make_preexec_fn(username: str) -> Callable[[], None]:
"""Return a closure suitable for ``preexec_fn`` that drops to *username*.
The user lookup (``pwd.getpwnam``) happens eagerly in the **parent**
process so that errors surface immediately. The ``os.set*`` calls
execute in the **child** process after ``fork()``.
"""
uid, gid, home, _shell = get_user_info(username)
def _drop_privileges() -> None:
os.setgid(gid)
os.initgroups(username, gid) # supplementary groups (sudo, video …)
os.setuid(uid) # must be last — can't setgid after this
return _drop_privileges