Beispiel #1
0
 def del_destinations(self, destinations, warnings=None):
     """Deletes the specified ``destinations`` from the catchall alias."""
     destinations = set(destinations)
     assert destinations and all(
         isinstance(dest, EmailAddress) for dest in destinations)
     if warnings is not None:
         assert isinstance(warnings, list)
     if not self._dests:
         raise AErr(
             _("There are no catch-all aliases defined for "
               "domain '%s'.") % self._domain,
             NO_SUCH_ALIAS,
         )
     unknown = destinations.difference(set(self._dests))
     if unknown:
         destinations.intersection_update(set(self._dests))
         if warnings is not None:
             warnings.extend(unknown)
     if not destinations:
         raise AErr(
             _("No suitable destinations left to remove from the "
               "catch-all alias of domain '%s'.") % self._domain,
             NO_SUCH_ALIAS,
         )
     self._delete(destinations)
     for destination in destinations:
         self._dests.remove(destination)
Beispiel #2
0
    def del_destinations(self, destinations, warnings=None):
        """Delete the specified `EmailAddress`es of *destinations* from
        the alias's destinations.

        """
        destinations = set(destinations)
        assert destinations and all(
            isinstance(dest, EmailAddress) for dest in destinations
        )
        if warnings is not None:
            assert isinstance(warnings, list)
        if self._addr in destinations:
            destinations.remove(self._addr)
            if warnings is not None:
                warnings.append(self._addr)
        if not self._dests:
            raise AErr(_("The alias '%s' does not exist.") % self._addr, NO_SUCH_ALIAS)
        unknown = destinations.difference(set(self._dests))
        if unknown:
            destinations.intersection_update(set(self._dests))
            if warnings is not None:
                warnings.extend(unknown)
        if not destinations:
            raise AErr(
                _("No suitable destinations left to remove from alias " "'%s'.")
                % self._addr,
                NO_SUCH_ALIAS,
            )
        self._delete(destinations)
        for destination in destinations:
            self._dests.remove(destination)
Beispiel #3
0
 def save(self):
     """Save the new Account in the database."""
     if not self._new:
         raise AErr(
             _("The account '%s' already exists.") % self._addr,
             ACCOUNT_EXISTS)
     if not self._passwd:
         raise AErr(
             _("No password set for account: '%s'") % self._addr,
             ACCOUNT_MISSING_PASSWORD,
         )
     self._prepare(
         MailLocation(
             self._dbh,
             mbfmt=cfg_dget("mailbox.format"),
             directory=cfg_dget("mailbox.root"),
         ))
     dbc = self._dbh.cursor()
     # fmt: off
     dbc.execute(
         "INSERT INTO users ("
         "   local_part, "
         "   passwd, "
         "   uid, "
         "   gid, "
         "   mid, "
         "   qid, "
         "   ssid, "
         "   tid, "
         "   note"
         ") "
         "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
         (
             self._addr.localpart,
             pwhash(self._passwd, user=self._addr),
             self._uid,
             self._domain.gid,
             self._mail.mid,
             self._qlimit.qid if self._qlimit else None,
             self._services.ssid if self._services else None,
             self._transport.tid if self._transport else None,
             self._note,
         ),
     )
     # fmt: on
     self._dbh.commit()
     dbc.close()
     self._new = False
Beispiel #4
0
 def _chk_state(self):
     """Raise an AccountError if the Account is new - not yet saved in the
     database."""
     if self._new:
         raise AErr(
             _("The account '%s' does not exist.") % self._addr,
             NO_SUCH_ACCOUNT)
Beispiel #5
0
    def modify(self, field, value):
        """Update the Account's *field* to the new *value*.

        Possible values for *field* are: 'name', 'note' and 'pwhash'.

        Arguments:

        `field` : str
          The attribute name: 'name', 'note' or 'pwhash'
        `value` : str
          The new value of the attribute.
        """
        if field not in ("name", "note", "pwhash"):
            raise AErr(_("Unknown field: '%s'") % field, INVALID_ARGUMENT)
        if field == "pwhash":
            field = "passwd"
        self._chk_state()
        dbc = self._dbh.cursor()
        # fmt: off
        dbc.execute(f"UPDATE users "
                    f"SET {field} = %s "
                    f"WHERE uid = %s", (value, self._uid))
        # fmt: on
        if dbc.rowcount > 0:
            self._dbh.commit()
        dbc.close()
Beispiel #6
0
    def _check_expansion(self, count_new):
        """Checks the current expansion limit of the alias."""
        postconf = Postconf(cfg_dget("bin.postconf"))
        limit = int(postconf.read("virtual_alias_expansion_limit"))
        dcount = len(self._dests)
        failed = False
        if dcount == limit or dcount + count_new > limit:
            failed = True
            errmsg = _(
                """Cannot add %(count_new)i new destination(s) to catch-all alias for
domain '%(domain)s'. Currently this alias expands into %(count)i/%(limit)i
recipients. %(count_new)i additional destination(s) will render this alias
unusable.
Hint: Increase Postfix' virtual_alias_expansion_limit""")
        elif dcount > limit:
            failed = True
            errmsg = _(
                """Cannot add %(count_new)i new destination(s) to catch-all alias for domain
'%(domain)s'. This alias already exceeds its expansion limit \
(%(count)i/%(limit)i).
So its unusable, all messages addressed to this alias will be bounced.
Hint: Delete some destination addresses.""")
        if failed:
            raise AErr(
                errmsg % {
                    "domain": self._domain,
                    "count": dcount,
                    "limit": limit,
                    "count_new": count_new,
                },
                ALIAS_EXCEEDS_EXPANSION_LIMIT,
            )
Beispiel #7
0
    def __init__(self, dbh, address):
        """Creates a new Account instance.

        When an account with the given *address* could be found in the
        database all relevant data will be loaded.

        Arguments:

        `dbh` : psycopg2._psycopg.connection
          A database connection for the database access.
        `address` : vmm.EmailAddress.EmailAddress
          The e-mail address of the (new) Account.
        """
        if not isinstance(address, EmailAddress):
            raise TypeError("Argument 'address' is not an EmailAddress")
        self._addr = address
        self._dbh = dbh
        self._domain = Domain(self._dbh, self._addr.domainname)
        if not self._domain.gid:
            # TP: Hm, what “quotation marks” should be used?
            # If you are unsure have a look at:
            # http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
            raise AErr(
                _("The domain '%s' does not exist.") % self._addr.domainname,
                NO_SUCH_DOMAIN,
            )
        self._uid = 0
        self._mail = None
        self._qlimit = None
        self._services = None
        self._transport = None
        self._note = None
        self._passwd = None
        self._new = True
        self._load()
Beispiel #8
0
 def get_destinations(self):
     """Returns an iterator for all destinations of the catchall alias."""
     if not self._dests:
         raise AErr(
             _("There are no catch-all aliases defined for "
               "domain '%s'.") % self._domain,
             NO_SUCH_ALIAS,
         )
     return iter(self._dests)
Beispiel #9
0
 def delete(self):
     """Deletes all catchall destinations for the domain."""
     if not self._dests:
         raise AErr(
             _("There are no catch-all aliases defined for "
               "domain '%s'.") % self._domain,
             NO_SUCH_ALIAS,
         )
     self._delete()
     del self._dests[:]
Beispiel #10
0
    def __init__(self, dbh, domain):
        self._domain = domain
        self._dbh = dbh
        self._gid = get_gid(self._dbh, self.domain)
        if not self._gid:
            raise AErr(
                _("The domain '%s' does not exist.") % self.domain,
                NO_SUCH_DOMAIN)
        self._dests = []

        self._load_dests()
Beispiel #11
0
    def set_password(self, password):
        """Set a password for the new Account.

        If you want to update the password of an existing Account use
        Account.modify().

        Argument:

        `password` : basestring
          The password for the new Account.
        """
        if not self._new:
            raise AErr(
                _("The account '%s' already exists.") % self._addr,
                ACCOUNT_EXISTS)
        if not isinstance(password, str) or not password:
            raise AErr(
                _("Could not accept password: '******'") % password,
                ACCOUNT_MISSING_PASSWORD,
            )
        self._passwd = password
Beispiel #12
0
    def __init__(self, dbh, address):
        assert isinstance(address, EmailAddress)
        self._addr = address
        self._dbh = dbh
        self._gid = get_gid(self._dbh, self._addr.domainname)
        if not self._gid:
            raise AErr(
                _("The domain '%s' does not exist.") % self._addr.domainname,
                NO_SUCH_DOMAIN,
            )
        self._dests = []

        self._load_dests()
Beispiel #13
0
def get_account_by_uid(uid, dbh):
    """Search an Account by its UID.

    This function returns a dict (keys: 'address', 'gid' and 'uid'), if an
    Account with the given *uid* exists.

    Argument:

    `uid` : int
      The Account unique ID.
    `dbh` : psycopg2._psycopg.connection
      a database connection for the database access.
    """
    try:
        uid = int(uid)
    except ValueError:
        raise AErr(_("UID must be an integer."), INVALID_ARGUMENT)
    if uid < 1:
        raise AErr(_("UID must be greater than 0."), INVALID_ARGUMENT)
    dbc = dbh.cursor()
    # fmt: off
    dbc.execute(
        "SELECT local_part||'@'|| domain_name.domainname AS address, "
        "   uid, users.gid, note "
        "FROM users "
        "LEFT JOIN domain_name ON (domain_name.gid = users.gid AND is_primary) "
        "WHERE uid = %s",
        (uid, ),
    )
    # fmt: on
    info = dbc.fetchone()
    dbc.close()
    if not info:
        raise AErr(
            _("There is no account with the UID: '%d'") % uid, NO_SUCH_ACCOUNT)
    info = dict(list(zip(("address", "uid", "gid", "note"), info)))
    return info
Beispiel #14
0
    def get_info(self):
        """Returns a dict with some information about the Account.

        The keys of the dict are: 'address', 'gid', 'home', 'imap'
        'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport', 'uid',
        'uq_bytes', 'uq_messages', 'ql_bytes', 'ql_messages', and
        'ql_domaindefault'.
        """
        self._chk_state()
        dbc = self._dbh.cursor()
        # fmt: off
        dbc.execute(
            "SELECT ("
            "   name, "
            "   CASE WHEN bytes IS NULL THEN 0 ELSE bytes END, "
            "   CASE WHEN messages IS NULL THEN 0 ELSE messages END "
            ") "
            "FROM users "
            "LEFT JOIN userquota USING (uid) "
            "WHERE users.uid = %s",
            (self._uid, ),
        )
        # fmt: on
        info = dbc.fetchone()
        dbc.close()
        if info:
            info = dict(list(zip(("name", "uq_bytes", "uq_messages"), info)))
            info.update(self._get_info_serviceset())
            info["address"] = self._addr
            info["gid"] = self._domain.gid
            info["home"] = "%s/%s" % (self._domain.directory, self._uid)
            info["mail_location"] = self._mail.mail_location
            if self._qlimit:
                info["ql_bytes"] = self._qlimit.bytes
                info["ql_messages"] = self._qlimit.messages
                info["ql_domaindefault"] = False
            else:
                info["ql_bytes"] = self._domain.quotalimit.bytes
                info["ql_messages"] = self._domain.quotalimit.messages
                info["ql_domaindefault"] = True
            info["transport"] = self._get_info_transport()
            info["note"] = self._note
            info["uid"] = self._uid
            return info
        # nearly impossible‽
        raise AErr(
            _("Could not fetch information for account: '%s'") % self._addr,
            NO_SUCH_ACCOUNT,
        )
Beispiel #15
0
 def _prepare(self, maillocation):
     """Check and set different attributes - before we store the
     information in the database.
     """
     if maillocation.dovecot_version > cfg_dget("misc.dovecot_version"):
         raise AErr(
             _("The mailbox format '%(mbfmt)s' requires Dovecot "
               ">= v%(version)s.") % {
                   "mbfmt": maillocation.mbformat,
                   "version": version_str(maillocation.dovecot_version),
               },
             INVALID_MAIL_LOCATION,
         )
     transport = self._transport or self._domain.transport
     validate_transport(transport, maillocation)
     self._mail = maillocation
     self._set_uid()
Beispiel #16
0
    def delete(self, force=False):
        """Delete the Account from the database.

        Argument:

        `force` : bool
          if *force* is `True`, all aliases, which points to the Account,
          will be also deleted.  If there are aliases and *force* is
          `False`, an AccountError will be raised.
        """
        if not isinstance(force, bool):
            raise TypeError("force must be a bool")
        self._chk_state()
        dbc = self._dbh.cursor()
        if force:
            # fmt: off
            dbc.execute("DELETE FROM users " "WHERE uid = %s", (self._uid, ))
            # fmt: on
            # delete also all aliases where the destination address is the same
            # as for this account.
            # fmt: off
            dbc.execute("DELETE FROM alias "
                        "WHERE destination = %s", (str(self._addr), ))
            # fmt: on
            self._dbh.commit()
        else:  # check first for aliases
            a_count = self._count_aliases()
            if a_count > 0:
                dbc.close()
                raise AErr(
                    _("There are %(count)d aliases with the "
                      "destination address '%(address)s'.") % {
                          "count": a_count,
                          "address": self._addr
                      },
                    ALIAS_PRESENT,
                )
            # fmt: off
            dbc.execute("DELETE FROM users " "WHERE uid = %s", (self._uid, ))
            # fmt: on
            self._dbh.commit()
        dbc.close()
        self._new = True
        self._uid = 0
        self._addr = self._dbh = self._domain = self._passwd = None
        self._mail = self._qlimit = self._services = self._transport = None
Beispiel #17
0
 def delete(self):
     """Deletes the alias with all its destinations."""
     if not self._dests:
         raise AErr(_("The alias '%s' does not exist.") % self._addr, NO_SUCH_ALIAS)
     self._delete()
     del self._dests[:]
Beispiel #18
0
 def get_destinations(self):
     """Returns an iterator for all destinations of the alias."""
     if not self._dests:
         raise AErr(_("The alias '%s' does not exist.") % self._addr, NO_SUCH_ALIAS)
     return iter(self._dests)