mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
- Avoid flake8 warnings. - Makes the call more explicitly readable in case an exception is expected but check=True is not passed by mistake. Tests: - Many tests are skipped since the changes are considered trivial. check=False is already the default for subprocess.run() method. - actions/package: Install an app when it is not installed. - actions/upgrade: Run manual upgrades. - actions/users: Change a user password. Login. Create/remove a user. - actions/zoph: Restore a database. - container: On a fresh repository, run ./container up,ssh,stop,destroy for a testing container. - plinth/action_utils.py: Enable/disable an app that has a running service. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
107 lines
3.3 KiB
Python
107 lines
3.3 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
import contextlib
|
|
import fcntl
|
|
import logging
|
|
import os
|
|
import pwd
|
|
import re
|
|
import subprocess
|
|
import threading
|
|
|
|
from . import interproc
|
|
|
|
lock_name_pattern = re.compile('^[0-9a-zA-Z_-]+$')
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class RaceCondition(AssertionError):
|
|
pass
|
|
|
|
|
|
class Mutex:
|
|
"""File and pthread lock based resource mutex"""
|
|
|
|
def __init__(self, lock_name):
|
|
if not lock_name_pattern.match(lock_name):
|
|
raise ValueError('Bad lock name')
|
|
self._lock_path = '/var/lock/plinth-%s.lock' % lock_name
|
|
self._thread_mutex = threading.Lock()
|
|
|
|
@property
|
|
def lock_path(self):
|
|
return self._lock_path
|
|
|
|
@contextlib.contextmanager
|
|
def lock_threads_only(self):
|
|
"""Acquire the thread lock but not the file lock"""
|
|
if not self._thread_mutex.acquire(timeout=5):
|
|
raise RuntimeError('Could not acquire thread lock')
|
|
try:
|
|
yield
|
|
finally:
|
|
self._thread_mutex.release()
|
|
|
|
@contextlib.contextmanager
|
|
def lock_all(self):
|
|
"""Acquire both the thread lock and the file lock"""
|
|
with self.lock_threads_only():
|
|
# Set up
|
|
fd = self._open_lock_file()
|
|
fcntl.lockf(fd, fcntl.LOCK_EX)
|
|
# Enter context
|
|
try:
|
|
yield
|
|
finally:
|
|
# Clean up
|
|
fcntl.lockf(fd, fcntl.LOCK_UN)
|
|
fd.close()
|
|
|
|
def _open_lock_file(self):
|
|
"""Attempt to open lock file for R&W. Raises OSError on failure"""
|
|
og_ruid, og_euid, og_suid = os.getresuid()
|
|
if og_euid == 0 and threading.active_count() > 1:
|
|
raise RaceCondition('setuid in a multi-threaded process')
|
|
if not os.path.exists(self.lock_path):
|
|
self._create_lock_file_as_plinth(og_euid)
|
|
|
|
fd = None
|
|
try:
|
|
if og_euid == 0:
|
|
# Temporarily run the current process as plinth
|
|
plinth_uid = pwd.getpwnam('plinth').pw_uid
|
|
self._checked_setresuid(og_ruid, plinth_uid, 0)
|
|
fd = open(self.lock_path, 'w+b')
|
|
finally:
|
|
# Restore resuid
|
|
if og_euid == 0:
|
|
self._checked_setresuid(og_ruid, 0, 0)
|
|
if og_suid != 0:
|
|
self._checked_setresuid(og_ruid, 0, og_suid)
|
|
|
|
return fd
|
|
|
|
def _create_lock_file_as_plinth(self, your_euid):
|
|
# Don't change the current processes umask
|
|
# Do create a new process
|
|
args = []
|
|
if your_euid == 0:
|
|
args.extend(['sudo', '-n', '-u', 'plinth'])
|
|
args.extend(['/bin/sh', '-c'])
|
|
args.append('umask 177 && > ' + self.lock_path)
|
|
|
|
completed = subprocess.run(args, capture_output=True, check=False)
|
|
if completed.returncode != 0:
|
|
interproc.log_subprocess(completed)
|
|
raise OSError('Could not create ' + self.lock_path)
|
|
|
|
def _checked_setresuid(self, ruid, euid, suid):
|
|
os.setresuid(ruid, euid, suid)
|
|
if os.getresuid() != (ruid, euid, suid):
|
|
try:
|
|
raise SystemExit('PANIC: setresuid failed')
|
|
except SystemExit as e:
|
|
# Print stack trace
|
|
logger.exception(e)
|
|
# Force exit
|
|
exit(1)
|