email: Open lock file as plinth user

This commit is contained in:
fliu 2021-07-07 21:51:23 +00:00 committed by Sunil Mohan Adapa
parent e2535bad49
commit 91c907f657
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
2 changed files with 54 additions and 31 deletions

View File

@ -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)

View File

@ -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