diff --git a/plinth/modules/email_server/__init__.py b/plinth/modules/email_server/__init__.py index ea1e02aeb..7e9fcbaca 100644 --- a/plinth/modules/email_server/__init__.py +++ b/plinth/modules/email_server/__init__.py @@ -14,7 +14,7 @@ from plinth.modules.firewall.components import Firewall from . import audit from . import manifest -version = 31 +version = 1 managed_packages = ['postfix', 'dovecot-pop3d', 'dovecot-imapd', 'dovecot-lmtpd', 'dovecot-ldap', 'dovecot-managesieved'] managed_services = ['postfix', 'dovecot'] diff --git a/plinth/modules/email_server/lock.py b/plinth/modules/email_server/lock.py index bdf138ba7..27f21c124 100644 --- a/plinth/modules/email_server/lock.py +++ b/plinth/modules/email_server/lock.py @@ -1,8 +1,11 @@ # SPDX-License-Identifier: AGPL-3.0-or-later import contextlib +import errno import fcntl import os +import pwd import threading +import time class Mutex: @@ -25,15 +28,50 @@ class Mutex: def lock_all(self): """Acquire both the thread lock and the file lock""" with self.lock_threads_only(): - fd = open(self.lock_path, 'wb') - # FIXME: Who can lock? - try: - os.fchmod(fd.fileno(), 0o666) # rw-rw-rw- - except OSError: - pass + # Set up + fd = self._open_lock_file() fcntl.lockf(fd, fcntl.LOCK_EX) + self._chmod_and_chown(fd) + # 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""" + attempts = 10 + errno = -1 + fd = None + # Simulate a spin lock + while attempts > 0: + errno, fd = self._try(lambda: open(self.lock_path, 'wb')) + if errno == 0: + return fd + else: + attempts -= 1 + time.sleep(0.25) + raise OSError(errno, os.strerror(errno)) + + def _chmod_and_chown(self, fd): + """If the process UID is root, set fd's mode and ownership to + appropriate values. If we are not root, only set the mode""" + if os.getuid() == 0: + user_info = pwd.getpwnam('plinth') + os.fchown(fd.fileno(), 0, 0) + os.fchmod(fd.fileno(), 0o660) # rw-rw---- + fd.truncate(0) + os.fchown(fd.fileno(), user_info.pw_uid, user_info.pw_gid) + else: + self._try(lambda: os.fchmod(fd.fileno(), 0o660)) # rw-rw---- + + def _try(self, function): + try: + return 0, function() + except OSError as error: + if error.errno in (errno.EACCES, errno.EPERM): + return error.errno, None + else: + raise diff --git a/plinth/modules/email_server/postconf.py b/plinth/modules/email_server/postconf.py index 575668913..59b80d19a 100644 --- a/plinth/modules/email_server/postconf.py +++ b/plinth/modules/email_server/postconf.py @@ -4,7 +4,6 @@ import dataclasses import re import subprocess -import plinth.actions from .lock import Mutex postconf_mutex = Mutex('plinth-email-postconf.lock')