Esempio n. 1
0
    def _delete_domain_dir(self, domdir, gid):
        """Delete a domain's directory.

        Arguments:

        `domdir` : basestring
          The domain's directory (commonly DomainObj.directory)
        `gid` : int
          The domain's GID (commonly DomainObj.gid)
        """
        assert isinstance(domdir, str) and isinstance(gid, int)
        if gid < MIN_GID:
            raise VMMError(
                _("GID '%(gid)u' is less than '%(min_gid)u'.") % {
                    "gid": gid,
                    "min_gid": MIN_GID
                },
                DOMAINDIR_GROUP_MISMATCH,
            )
        if domdir.count(".."):
            raise VMMError(
                _('Found ".." in domain directory path: %s') % domdir,
                FOUND_DOTS_IN_PATH,
            )
        if not lisdir(domdir):
            self._warnings.append(_("No such directory: %s") % domdir)
            return
        dirst = os.lstat(domdir)
        if dirst.st_gid != gid:
            raise VMMError(
                _("Detected group mismatch in domain directory: "
                  "%s") % domdir,
                DOMAINDIR_GROUP_MISMATCH,
            )
        rmtree(domdir, ignore_errors=True)
Esempio n. 2
0
 def _make_domain_dir(self, domain):
     """Create a directory for the `domain` and its accounts."""
     cwd = os.getcwd()
     hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
     dir_created = False
     os.chdir(self._cfg.dget("misc.base_directory"))
     old_umask = os.umask(0o022)
     if not os.path.exists(hashdir):
         os.mkdir(hashdir, 0o711)
         os.chown(hashdir, 0, 0)
         dir_created = True
     if not dir_created and not lisdir(hashdir):
         raise VMMError(
             _("'%s' is not a directory.") % hashdir, NO_SUCH_DIRECTORY)
     if os.path.exists(domain.directory):
         raise VMMError(
             _("The file/directory '%s' already exists.") %
             domain.directory,
             VMM_ERROR,
         )
     os.mkdir(os.path.join(hashdir, domdir),
              self._cfg.dget("domain.directory_mode"))
     os.chown(domain.directory, 0, domain.gid)
     os.umask(old_umask)
     os.chdir(cwd)
Esempio n. 3
0
def _doveadmpw(password, scheme, encoding):
    """Communicates with Dovecot's doveadm and returns
    the hashed password: {scheme[.encoding]}hash
    """
    if encoding:
        scheme = ".".join((scheme, encoding))
    cmd_args = [
        cfg_dget("bin.doveadm"),
        "pw",
        "-s",
        scheme,
        "-p",
        get_unicode(password),
    ]
    process = Popen(cmd_args, stdout=PIPE, stderr=PIPE)
    stdout, stderr = process.communicate()
    if process.returncode:
        raise VMMError(stderr.strip().decode(ENCODING), VMM_ERROR)
    hashed = stdout.strip().decode(ENCODING)
    if not hashed.startswith("{%s}" % scheme):
        raise VMMError(
            "Unexpected result from %s: %s" %
            (cfg_dget("bin.doveadm"), hashed),
            VMM_ERROR,
        )
    return hashed
Esempio n. 4
0
 def user_password(self, emailaddress, password, scheme=None):
     """Wrapper for Account.update_password(...)."""
     if not isinstance(password, str) or not password:
         raise VMMError(
             _("Could not accept password: '******'") % password,
             INVALID_ARGUMENT)
     acc = self._get_account(emailaddress)
     if not acc:
         raise VMMError(
             _("The account '%s' does not exist.") % acc.address,
             NO_SUCH_ACCOUNT)
     acc.update_password(password, scheme)
Esempio n. 5
0
 def user_pwhash(self, emailaddress, pwhash):
     """Wrapper for Account.modify('pwhash', ...)"""
     scheme = extract_scheme(pwhash)
     if not scheme:
         raise VMMError(_("Missing {SCHEME} prefix from password hash."),
                        INVALID_ARGUMENT)
     else:
         scheme, encoding = verify_scheme(scheme)  # or die …
     acc = self._get_account(emailaddress)
     if not acc:
         raise VMMError(
             _("The account '%s' does not exist.") % acc.address,
             NO_SUCH_ACCOUNT)
     acc.modify("pwhash", pwhash)
Esempio n. 6
0
 def user_transport(self, emailaddress, transport):
     """Wrapper for Account.update_transport(Transport)."""
     if not isinstance(transport, str) or not transport:
         raise VMMError(
             _("Could not accept transport: '%s'") % transport,
             INVALID_ARGUMENT)
     acc = self._get_account(emailaddress)
     if not acc:
         raise VMMError(
             _("The account '%s' does not exist.") % acc.address,
             NO_SUCH_ACCOUNT)
     transport = (None if transport == "domain" else Transport(
         self._dbh, transport=transport))
     acc.update_transport(transport)
Esempio n. 7
0
    def _delete_home(self, domdir, uid, gid):
        """Delete a user's home directory.

        Arguments:

        `domdir` : basestring
          The directory of the domain the user belongs to
          (commonly AccountObj.domain.directory)
        `uid` : int
          The user's UID (commonly AccountObj.uid)
        `gid` : int
          The user's GID (commonly AccountObj.gid)
        """
        assert all(isinstance(xid, int)
                   for xid in (uid, gid)) and isinstance(domdir, str)
        if uid < MIN_UID or gid < MIN_GID:
            raise VMMError(
                _("UID '%(uid)u' and/or GID '%(gid)u' are less "
                  "than %(min_uid)u/%(min_gid)u.") % {
                      "uid": uid,
                      "gid": gid,
                      "min_gid": MIN_GID,
                      "min_uid": MIN_UID
                  },
                MAILDIR_PERM_MISMATCH,
            )
        if domdir.count(".."):
            raise VMMError(
                _('Found ".." in domain directory path: %s') % domdir,
                FOUND_DOTS_IN_PATH,
            )
        if not lisdir(domdir):
            raise VMMError(
                _("No such directory: %s") % domdir, NO_SUCH_DIRECTORY)
        os.chdir(domdir)
        userdir = "%s" % uid
        if not lisdir(userdir):
            self._warnings.append(
                _("No such directory: %s") % os.path.join(domdir, userdir))
            return
        mdstat = os.lstat(userdir)
        if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
            raise VMMError(
                _("Detected owner/group mismatch in home "
                  "directory."),
                MAILDIR_PERM_MISMATCH,
            )
        rmtree(userdir, ignore_errors=True)
Esempio n. 8
0
    def user_add(self, emailaddress, password=None, note=None):
        """Override the parent user_add() - add the interactive password
        dialog.

        Returns the generated password, if account.random_password == True.
        """
        acc = self._get_account(emailaddress)
        if acc:
            raise VMMError(
                _("The account '%s' already exists.") % acc.address,
                ACCOUNT_EXISTS)
        self._is_other_address(acc.address, TYPE_ACCOUNT)
        should_create_random_password = self._cfg.dget(
            "account.random_password")
        if password is None:
            if should_create_random_password:
                password = randompw(self._cfg.dget("account.password_length"))
            else:
                password = read_pass()
        acc.set_password(password)
        if note:
            acc.set_note(note)
        acc.save()
        self._make_account_dirs(acc)
        return password if should_create_random_password else None
Esempio n. 9
0
def read_pass():
    """Interactive 'password chat', returns the password in plain format.

    Throws a VMMError after the third failure.
    """
    # TP: Please preserve the trailing space.
    readp_msg0 = _("Enter new password: "******"Retype new password: "******"Too many failures - try again later."),
                           VMM_TOO_MANY_FAILURES)
        clear0 = getpass(prompt=readp_msg0)
        clear1 = getpass(prompt=readp_msg1)
        if clear0 != clear1:
            failures += 1
            w_err(0, _("Sorry, passwords do not match."))
            continue
        if not clear0:
            failures += 1
            w_err(0, _("Sorry, empty passwords are not permitted."))
            continue
        mismatched = False
    return clear0
Esempio n. 10
0
 def _read(self, parameter):
     """Ask postconf for the value of a single configuration parameter."""
     stdout, stderr = Popen([self._bin, "-h", parameter],
                            stdout=PIPE,
                            stderr=PIPE).communicate()
     if stderr:
         raise VMMError(stderr.strip().decode(), VMM_ERROR)
     return stdout.strip().decode()
Esempio n. 11
0
 def user_note(self, emailaddress, note):
     """Wrapper for Account.modify('note', ...)."""
     acc = self._get_account(emailaddress)
     if not acc:
         raise VMMError(
             _("The account '%s' does not exist.") % acc.address,
             NO_SUCH_ACCOUNT)
     acc.modify("note", note)
Esempio n. 12
0
    def address_list(self, typelimit, pattern=None):
        """TODO"""
        llike = dlike = False
        lpattern = dpattern = None
        if pattern:
            parts = pattern.split("@", 2)
            if len(parts) == 2:
                # The pattern includes '@', so let's treat the
                # parts separately to allow for pattern search like %@domain.%
                lpattern = parts[0]
                llike = lpattern.startswith("%") or lpattern.endswith("%")
                dpattern = parts[1]
                dlike = dpattern.startswith("%") or dpattern.endswith("%")

                checkp = lpattern.strip("%") if llike else lpattern
                if len(checkp) > 0 and re.search(RE_LOCALPART, checkp):
                    raise VMMError(
                        _("The pattern '%s' contains invalid "
                          "characters.") % pattern,
                        LOCALPART_INVALID,
                    )
            else:
                # else just match on domains
                # (or should that be local part, I don't know…)
                dpattern = parts[0]
                dlike = dpattern.startswith("%") or dpattern.endswith("%")

            checkp = dpattern.strip("%") if dlike else dpattern
            if len(checkp) > 0 and not RE_DOMAIN_SEARCH.match(checkp):
                raise VMMError(
                    _("The pattern '%s' contains invalid "
                      "characters.") % pattern,
                    DOMAIN_INVALID,
                )
        self._db_connect()
        from vmm.common import search_addresses

        return search_addresses(
            self._dbh,
            typelimit=typelimit,
            lpattern=lpattern,
            llike=llike,
            dpattern=dpattern,
            dlike=dlike,
        )
Esempio n. 13
0
 def _check_parameter(self, parameter):
     """Check that the `parameter` looks like a configuration parameter.
     If not, a VMMError will be raised."""
     if not self.__class__._parameter_re.match(parameter):
         raise VMMError(
             _("The value '%s' does not look like a valid "
               "Postfix configuration parameter name.") % parameter,
             VMM_ERROR,
         )
Esempio n. 14
0
 def user_services(self, emailaddress, *services):
     """Wrapper around Account.update_serviceset()."""
     acc = self._get_account(emailaddress)
     if not acc:
         raise VMMError(
             _("The account '%s' does not exist.") % acc.address,
             NO_SUCH_ACCOUNT)
     if len(services) == 1 and services[0] == "domain":
         serviceset = None
     else:
         kwargs = dict.fromkeys(SERVICES, False)
         for service in set(services):
             if service not in SERVICES:
                 raise VMMError(
                     _("Unknown service: '%s'") % service, UNKNOWN_SERVICE)
             kwargs[service] = True
         serviceset = ServiceSet(self._dbh, **kwargs)
     acc.update_serviceset(serviceset)
Esempio n. 15
0
 def alias_info(self, aliasaddress):
     """Returns an iterator object for all destinations (`EmailAddress`
     instances) for the `Alias` with the given *aliasaddress*."""
     alias = self._get_alias(aliasaddress)
     if alias:
         return alias.get_destinations()
     if not self._is_other_address(alias.address, TYPE_ALIAS):
         raise VMMError(
             _("The alias '%s' does not exist.") % alias.address,
             NO_SUCH_ALIAS)
Esempio n. 16
0
 def user_info(self, emailaddress, details=None):
     """Wrapper around Account.get_info(...)"""
     if details not in (None, "du", "aliases", "full"):
         raise VMMError(
             _("Invalid argument: '%s'") % details, INVALID_ARGUMENT)
     acc = self._get_account(emailaddress)
     if not acc:
         if not self._is_other_address(acc.address, TYPE_ACCOUNT):
             raise VMMError(
                 _("The account '%s' does not exist.") % acc.address,
                 NO_SUCH_ACCOUNT)
     info = acc.get_info()
     if self._cfg.dget("account.disk_usage") or details in ("du", "full"):
         path = os.path.join(acc.home, acc.mail_location.directory)
         info["disk usage"] = self._get_disk_usage(path)
         if details in (None, "du"):
             return info
     if details in ("aliases", "full"):
         return (info, acc.get_aliases())
     return info
Esempio n. 17
0
 def relocated_info(self, emailaddress):
     """Returns the target address of the relocated user with the given
     *emailaddress*."""
     relocated = self._get_relocated(emailaddress)
     if relocated:
         return relocated.get_info()
     if not self._is_other_address(relocated.address, TYPE_RELOCATED):
         raise VMMError(
             _("The relocated user '%s' does not exist.") %
             relocated.address,
             NO_SUCH_RELOCATED,
         )
Esempio n. 18
0
 def _read_multi(self, parameters):
     """Ask postconf for multiple configuration parameters. Returns a dict
     parameter: value items."""
     cmd = [self._bin]
     cmd.extend(parameter[1:] for parameter in parameters)
     stdout, stderr = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
     if stderr:
         raise VMMError(stderr.strip().decode(), VMM_ERROR)
     par_val = {}
     for line in stdout.decode().splitlines():
         par, val = line.split(" = ")
         par_val[par] = val
     return par_val
Esempio n. 19
0
def verify_scheme(scheme):
    """Checks if the password scheme *scheme* is known and supported by the
    configured `misc.dovecot_version`.

    The *scheme* maybe a password scheme's name (e.g.: 'PLAIN') or a scheme
    name with a encoding suffix (e.g. 'PLAIN.BASE64').  If the scheme is
    known and supported by the used Dovecot version,
    a tuple ``(scheme, encoding)`` will be returned.
    The `encoding` in the tuple may be `None`.

    Raises a `VMMError` if the password scheme:
      * is unknown
      * depends on a newer Dovecot version
      * has a unknown encoding suffix
    """
    assert isinstance(scheme, str), "Not a str: {!r}".format(scheme)
    scheme_encoding = scheme.upper().split(".")
    scheme = scheme_encoding[0]
    if scheme not in _scheme_info:
        raise VMMError(
            _("Unsupported password scheme: '%s'") % scheme, VMM_ERROR)
    if cfg_dget("misc.dovecot_version") < _scheme_info[scheme][1]:
        raise VMMError(
            _("The password scheme '%(scheme)s' requires Dovecot "
              ">= v%(version)s.") % {
                  "scheme": scheme,
                  "version": version_str(_scheme_info[scheme][1])
              },
            VMM_ERROR,
        )
    if len(scheme_encoding) > 1:
        if scheme_encoding[1] not in ("B64", "BASE64", "HEX"):
            raise VMMError(
                _("Unsupported password encoding: '%s'") % scheme_encoding[1],
                VMM_ERROR)
        encoding = scheme_encoding[1]
    else:
        encoding = None
    return scheme, encoding
Esempio n. 20
0
 def user_add(self, emailaddress, password, note=None):
     """Wrapper around Account.set_password() and Account.save()."""
     acc = self._get_account(emailaddress)
     if acc:
         raise VMMError(
             _("The account '%s' already exists.") % acc.address,
             ACCOUNT_EXISTS)
     self._is_other_address(acc.address, TYPE_ACCOUNT)
     acc.set_password(password)
     if note:
         acc.set_note(note)
     acc.save()
     self._make_account_dirs(acc)
Esempio n. 21
0
    def configure(self, section=None):
        """Starts the interactive configuration.

        Configures in interactive mode options in the given ``section``.
        If no section is given (default) all options from all sections
        will be prompted.
        """
        if section is None:
            self._cfg.configure(self._cfg.sections())
        elif self._cfg.has_section(section):
            self._cfg.configure([section])
        else:
            raise VMMError(
                _("Invalid section: '%s'") % section, INVALID_SECTION)
Esempio n. 22
0
 def _chkenv(self):
     """Make sure our base_directory is a directory and that all
     required executables exists and are executable.
     If not, a VMMError will be raised"""
     dir_created = False
     basedir = self._cfg.dget("misc.base_directory")
     if not os.path.exists(basedir):
         old_umask = os.umask(0o006)
         os.makedirs(basedir, 0o771)
         os.chown(basedir, 0, 0)
         os.umask(old_umask)
         dir_created = True
     if not dir_created and not lisdir(basedir):
         raise VMMError(
             _("'%(path)s' is not a directory.\n(%(cfg_file)s: "
               "section 'misc', option 'base_directory')") % {
                   "path": basedir,
                   "cfg_file": self._cfg_fname
               },
             NO_SUCH_DIRECTORY,
         )
     for opt, val in self._cfg.items("bin"):
         try:
             exec_ok(val)
         except VMMError as err:
             if err.code in (NO_SUCH_BINARY, NOT_EXECUTABLE):
                 raise VMMError(
                     err.msg + _("\n(%(cfg_file)s: section "
                                 "'bin', option '%(option)s')") % {
                                     "cfg_file": self._cfg_fname,
                                     "option": opt
                                 },
                     err.code,
                 )
             else:
                 raise
Esempio n. 23
0
    def edit(self, parameter, value):
        """Set the `parameter`'s value to `value`.

        Arguments:

        `parameter` : str
          the name of a Postfix configuration parameter
        `value` : str
          the parameter's new value.
        """
        self._check_parameter(parameter)
        stderr = Popen((self._bin, "-e", parameter + "=" + str(value)),
                       stderr=PIPE).communicate()[1]
        if stderr:
            raise VMMError(stderr.strip().decode(), VMM_ERROR)
Esempio n. 24
0
 def user_quotalimit(self, emailaddress, bytes_, messages=0):
     """Wrapper for Account.update_quotalimit(QuotaLimit)."""
     acc = self._get_account(emailaddress)
     if not acc:
         raise VMMError(
             _("The account '%s' does not exist.") % acc.address,
             NO_SUCH_ACCOUNT)
     if bytes_ == "domain":
         quotalimit = None
     else:
         if not all(isinstance(i, int) for i in (bytes_, messages)):
             raise TypeError("'bytes_' and 'messages' have to be "
                             "integers or longs.")
         quotalimit = QuotaLimit(self._dbh, bytes=bytes_, messages=messages)
     acc.update_quotalimit(quotalimit)
Esempio n. 25
0
 def user_password(self, emailaddress, password=None, scheme=None):
     """Override the parent user_password() - add the interactive
     password dialog."""
     acc = self._get_account(emailaddress)
     if not acc:
         raise VMMError(
             _("The account '%s' does not exist.") % acc.address,
             NO_SUCH_ACCOUNT)
     if scheme:
         scheme, encoding = verify_scheme(scheme)
         if encoding:
             scheme = "%s.%s" % (scheme, encoding)
     if not isinstance(password, str) or not password:
         password = read_pass()
     acc.update_password(password, scheme)
Esempio n. 26
0
    def domain_list(self, pattern=None):
        """Wrapper around function search() from module Domain."""
        from vmm.domain import search

        like = False
        if pattern and (pattern.startswith("%") or pattern.endswith("%")):
            like = True
            if not RE_DOMAIN_SEARCH.match(pattern.strip("%")):
                raise VMMError(
                    _("The pattern '%s' contains invalid "
                      "characters.") % pattern,
                    DOMAIN_INVALID,
                )
        self._db_connect()
        return search(self._dbh, pattern=pattern, like=like)
Esempio n. 27
0
 def _doveadm_create(self, mailboxes, subscribe):
     """Wrap around Dovecot's doveadm"""
     cmd_args = [
         cfg_dget("bin.doveadm"),
         "mailbox",
         "create",
         "-u",
         str(self._user.address),
     ]
     if subscribe:
         cmd_args.append("-s")
     cmd_args.extend(mailboxes)
     process = Popen(cmd_args, stderr=PIPE)
     stderr = process.communicate()[1]
     if process.returncode:
         e_msg = _("Failed to create mailboxes: %r\n") % mailboxes
         raise VMMError(e_msg + stderr.strip().decode(ENCODING), VMM_ERROR)
Esempio n. 28
0
 def _find_cfg_file(self):
     """Search the CFG_FILE in CFG_PATH.
     Raise a VMMError when no vmm.cfg could be found.
     """
     for path in CFG_PATH.split(":"):
         tmp = os.path.join(path, CFG_FILE)
         if os.path.isfile(tmp):
             self._cfg_fname = tmp
             break
     if not self._cfg_fname:
         raise VMMError(
             _("Could not find '%(cfg_file)s' in: "
               "'%(cfg_path)s'") % {
                   "cfg_file": CFG_FILE,
                   "cfg_path": CFG_PATH
               },
             CONF_NOFILE,
         )
Esempio n. 29
0
 def domain_info(self, domainname, details=None):
     """Wrapper around Domain.get_info(), Domain.get_accounts(),
     Domain.get_aliase_names(), Domain.get_aliases() and
     Domain.get_relocated."""
     if details not in [
             None,
             "accounts",
             "aliasdomains",
             "aliases",
             "full",
             "relocated",
             "catchall",
     ]:
         raise VMMError(
             _("Invalid argument: '%s'") % details, INVALID_ARGUMENT)
     dom = self._get_domain(domainname)
     dominfo = dom.get_info()
     if dominfo["domain name"].startswith(
             "xn--") or dominfo["domain name"].count(".xn--"):
         dominfo["domain name"] += " (%s)" % dominfo["domain name"].encode(
             "utf-8").decode("idna")
     if details is None:
         return dominfo
     elif details == "accounts":
         return (dominfo, dom.get_accounts())
     elif details == "aliasdomains":
         return (dominfo, dom.get_aliase_names())
     elif details == "aliases":
         return (dominfo, dom.get_aliases())
     elif details == "relocated":
         return (dominfo, dom.get_relocated())
     elif details == "catchall":
         return (dominfo, dom.get_catchall())
     else:
         return (
             dominfo,
             dom.get_aliase_names(),
             dom.get_accounts(),
             dom.get_aliases(),
             dom.get_relocated(),
             dom.get_catchall(),
         )
Esempio n. 30
0
 def _db_connect(self):
     """Return a new psycopg2 connection object."""
     if self._dbh is None or (isinstance(self._dbh,
                                         psycopg2.extensions.connection)
                              and self._dbh.closed):
         try:
             self._dbh = psycopg2.connect(
                 host=self._cfg.dget("database.host"),
                 sslmode=self._cfg.dget("database.sslmode"),
                 port=self._cfg.dget("database.port"),
                 database=self._cfg.dget("database.name"),
                 user=self._cfg.pget("database.user"),
                 password=self._cfg.pget("database.pass"),
             )
             self._dbh.set_client_encoding("utf8")
             dbc = self._dbh.cursor()
             dbc.execute("SET NAMES 'UTF8'")
             dbc.close()
         except psycopg2.DatabaseError as err:
             raise VMMError(str(err), DATABASE_ERROR)