mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-13 10:30:16 +00:00
email: Open lock file as plinth user
This commit is contained in:
parent
e2535bad49
commit
91c907f657
@ -1,32 +1,43 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
import contextlib
|
import contextlib
|
||||||
import errno
|
|
||||||
import fcntl
|
import fcntl
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
lock_name_pattern = re.compile('^[0-9a-zA-Z_-]+$')
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RaceCondition(AssertionError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Mutex:
|
class Mutex:
|
||||||
"""File and pthread lock based resource mutex"""
|
"""File and pthread lock based resource mutex"""
|
||||||
|
|
||||||
def __init__(self, lock_file):
|
def __init__(self, lock_name):
|
||||||
self.thread_mutex = threading.Lock()
|
if not lock_name_pattern.match(lock_name):
|
||||||
self.lock_path = '/var/lock/' + lock_file
|
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
|
@contextlib.contextmanager
|
||||||
def lock_threads_only(self):
|
def lock_threads_only(self):
|
||||||
"""Acquire the thread lock but not the file lock"""
|
"""Acquire the thread lock but not the file lock"""
|
||||||
if not self.thread_mutex.acquire(timeout=5):
|
if not self._thread_mutex.acquire(timeout=5):
|
||||||
raise RuntimeError('Could not acquire thread lock')
|
raise RuntimeError('Could not acquire thread lock')
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
self.thread_mutex.release()
|
self._thread_mutex.release()
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def lock_all(self):
|
def lock_all(self):
|
||||||
@ -45,36 +56,48 @@ class Mutex:
|
|||||||
|
|
||||||
def _open_lock_file(self):
|
def _open_lock_file(self):
|
||||||
"""Attempt to open lock file for R&W. Raises OSError on failure"""
|
"""Attempt to open lock file for R&W. Raises OSError on failure"""
|
||||||
if os.getuid() == 0:
|
og_ruid, og_euid, og_suid = os.getresuid()
|
||||||
if os.path.exists(self.lock_path):
|
if og_euid == 0 and threading.active_count() > 1:
|
||||||
# Check its owner and mode
|
raise RaceCondition('setuid in a multi-threaded process')
|
||||||
stat = os.stat(self.lock_path)
|
if not os.path.exists(self.lock_path):
|
||||||
owner = pwd.getpwuid(stat.st_uid).pw_name
|
self._create_lock_file_as_plinth()
|
||||||
mode = stat.st_mode & 0o777
|
|
||||||
if owner != 'plinth' or mode != 0o600:
|
|
||||||
logger.warning('Clean up bad file %s', self.lock_path)
|
|
||||||
os.unlink(self.lock_path)
|
|
||||||
self._create_lock_file_as_plinth()
|
|
||||||
else:
|
|
||||||
self._create_lock_file_as_plinth()
|
|
||||||
|
|
||||||
return open(self.lock_path, 'wb+')
|
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):
|
def _create_lock_file_as_plinth(self):
|
||||||
args = ['sudo', '-n', '-u', 'plinth', 'touch', self.lock_path]
|
# Don't change the current processes umask
|
||||||
|
# Do create a new process
|
||||||
|
args = ['sudo', '-n', '-u', 'plinth', '/bin/sh', '-c']
|
||||||
|
args.append('umask 177 && > ' + self.lock_path)
|
||||||
|
|
||||||
completed = subprocess.run(args, capture_output=True)
|
completed = subprocess.run(args, capture_output=True)
|
||||||
if completed.returncode != 0:
|
if completed.returncode != 0:
|
||||||
logger.critical('Process returned %d', completed.returncode)
|
logger.critical('Subprocess returned %d', completed.returncode)
|
||||||
logger.critical('Stdout: %r', completed.stdout)
|
logger.critical('Stdout: %r', completed.stdout)
|
||||||
logger.critical('Stderr: %r', completed.stderr)
|
logger.critical('Stderr: %r', completed.stderr)
|
||||||
raise OSError('Could not create ' + self.lock_path)
|
raise OSError('Could not create ' + self.lock_path)
|
||||||
os.chmod(self.lock_path, 0o600)
|
|
||||||
|
|
||||||
def _try(self, function):
|
def _checked_setresuid(self, ruid, euid, suid):
|
||||||
try:
|
os.setresuid(ruid, euid, suid)
|
||||||
return 0, function()
|
if os.getresuid() != (ruid, euid, suid):
|
||||||
except OSError as error:
|
try:
|
||||||
if error.errno in (errno.EACCES, errno.EPERM):
|
raise SystemExit('PANIC: setresuid failed')
|
||||||
return error.errno, None
|
except SystemExit as e:
|
||||||
else:
|
# Print stack trace
|
||||||
raise
|
logger.exception(e)
|
||||||
|
# Force exit
|
||||||
|
exit(1)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
from .lock import Mutex
|
from .lock import Mutex
|
||||||
|
|
||||||
postconf_mutex = Mutex('plinth-email-postconf.lock')
|
postconf_mutex = Mutex('email-postconf')
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user