From 1836cee5fbc415dd7c71633827e89f8825c752a1 Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sun, 14 Oct 2018 10:33:58 +0200 Subject: [PATCH] Improve code quality move doveadm call to sysutils password hashers name and label are now class properties --- modoboa/core/password_hashers/__init__.py | 34 +++++++------------ modoboa/core/password_hashers/advanced.py | 12 +------ modoboa/core/password_hashers/base.py | 28 ++++++++-------- modoboa/lib/sysutils.py | 40 +++++++++++++++++++++++ 4 files changed, 68 insertions(+), 46 deletions(-) diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 11e076cf1..80a5a8529 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -6,17 +6,13 @@ Password hashers for Modoboa. from __future__ import unicode_literals -import os - -from django.utils.encoding import force_text - from modoboa.core.password_hashers.advanced import ( # NOQA:F401 BLFCRYPTHasher, MD5CRYPTHasher, SHA256CRYPTHasher, SHA512CRYPTHasher ) from modoboa.core.password_hashers.base import ( # NOQA:F401 CRYPTHasher, MD5Hasher, PLAINHasher, SHA256Hasher ) -from modoboa.lib.sysutils import exec_cmd +from modoboa.lib.sysutils import doveadm_cmd def get_password_hasher(scheme): @@ -35,25 +31,19 @@ def get_password_hasher(scheme): hasher = PLAINHasher return hasher + def get_dovecot_schemes(): """Return schemes supported by dovecot""" - supported_schemes = ["{PLAIN}"] - dpath = None - code, output = exec_cmd("which doveadm") - if not code: - dpath = force_text(output).strip() + supported_schemes = None + try: + code, schemes = doveadm_cmd("pw -l") + except OSError: + pass else: - known_paths = ("/usr/bin/doveadm", "/usr/local/bin/doveadm") - for fpath in known_paths: - if os.path.isfile(fpath) and os.access(fpath, os.X_OK): - dpath = fpath - if dpath: - try: - code, schemes = exec_cmd("%s pw -l" % dpath) - except OSError: - pass - else: - supported_schemes = ["{{{}}}".format(scheme) - for scheme in force_text(schemes).split()] + supported_schemes = ["{{{}}}".format(scheme) + for scheme in schemes.split()] + + if not supported_schemes: + supported_schemes = ['{PLAIN}'] return supported_schemes diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py index 40fa9d499..8aaaf506c 100644 --- a/modoboa/core/password_hashers/advanced.py +++ b/modoboa/core/password_hashers/advanced.py @@ -21,8 +21,6 @@ class BLFCRYPTHasher(PasswordHasher): Supports rounds and provides compatibility with dovecot on *BSD systems. """ - name = "blfcrypt" - label = "blfcrypt" @property def scheme(self): @@ -46,14 +44,12 @@ class BLFCRYPTHasher(PasswordHasher): class MD5CRYPTHasher(PasswordHasher): - """ MD5-CRYPT password hasher. This scheme can't be considered as secure anymore. """ - name = "md5crypt" - label = "md5crypt" + _weak = True @property def scheme(self): @@ -70,15 +66,12 @@ class MD5CRYPTHasher(PasswordHasher): class SHA256CRYPTHasher(PasswordHasher): - """ SHA256-CRYPT password hasher. Supports rounds and is a good compromise between security and performance. """ - name = "sha256crypt" - label = "sha256crypt" @property def scheme(self): @@ -97,15 +90,12 @@ class SHA256CRYPTHasher(PasswordHasher): class SHA512CRYPTHasher(PasswordHasher): - """ SHA512-CRYPT password hasher. Supports rounds and is the strongest scheme provided by Modoboa. Requires more resource than SHA256-CRYPT. """ - name = "sha512crypt" - label = "sha512crypt" @property def scheme(self): diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index 3d8d2b354..88652de13 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -19,14 +19,24 @@ from django.utils.encoding import force_bytes, force_text class PasswordHasher(object): - """ Base class of all hashers. """ + _weak = False def __init__(self, target="local"): self._target = target + class __metaclass__(type): + """class properties""" + @property + def name(cls): + return cls.__name__.rstrip('Hasher').lower() + + @property + def label(cls): + return cls.name if not cls._weak else "{} (weak)".format(cls.name) + def _encrypt(self, clearvalue, salt=None): raise NotImplementedError @@ -75,12 +85,10 @@ class PasswordHasher(object): class PLAINHasher(PasswordHasher): - """ Plain (ie. clear) password hasher. """ - name = "plain" - label = "plain (weak)" + _weak = True @property def scheme(self): @@ -91,14 +99,12 @@ class PLAINHasher(PasswordHasher): class CRYPTHasher(PasswordHasher): - """ crypt password hasher. Uses python `crypt` standard module. """ - name = "crypt" - label = "crypt (weak)" + _weak = True @property def scheme(self): @@ -117,14 +123,12 @@ class CRYPTHasher(PasswordHasher): class MD5Hasher(PasswordHasher): - """ MD5 password hasher. Uses python `hashlib` standard module. """ - name = "md5" - label = "md5 (weak)" + _weak = True @property def scheme(self): @@ -136,14 +140,12 @@ class MD5Hasher(PasswordHasher): class SHA256Hasher(PasswordHasher): - """ SHA256 password hasher. Uses python `hashlib` and `base64` standard modules. """ - name = "sha256" - label = "sha256 (weak)" + _weak = True @property def scheme(self): diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py index 3d21445bf..a363643fc 100644 --- a/modoboa/lib/sysutils.py +++ b/modoboa/lib/sysutils.py @@ -8,9 +8,13 @@ This module extra functions/shortcuts to communicate with the system from __future__ import unicode_literals import inspect +import os import re import subprocess +from django.conf import settings +from django.utils.encoding import force_text + def exec_cmd(cmd, sudo_user=None, pinput=None, capture_output=True, **kwargs): """Execute a shell command. @@ -42,6 +46,42 @@ def exec_cmd(cmd, sudo_user=None, pinput=None, capture_output=True, **kwargs): return process.returncode, output +def doveadm_cmd(params, sudo_user=None, pinput=None, capture_output=True, **kwargs): + """Execute doveadm command. + + Run doveadm command using the current user. Set :keyword:`sudo_user` if + you need different privileges. + + :param str params: the parameters to give to doveadm + :param str sudo_user: a valid system username + :param str pinput: data to send to process's stdin + :param bool capture_output: capture process output or not + :rtype: tuple + :return: return code, command output + """ + dpath = None + code, output = exec_cmd("which doveadm") + if not code: + dpath = force_text(output).strip() + else: + known_paths = getattr( + settings, "DOVEADM_LOOKUP_PATH", + ("/usr/bin/doveadm", "/usr/local/bin/doveadm") + ) + for fpath in known_paths: + if os.path.isfile(fpath) and os.access(fpath, os.X_OK): + dpath = fpath + break + if dpath: + return exec_cmd("{} {}".format(dpath, params), + sudo_user=sudo_user, + pinput=pinput, + capture_output=capture_output, + **kwargs) + else: + raise OSError("doveadm command not found") + + def guess_extension_name(): """Tries to guess the application's name by inspecting the stack.