--- a/VirtualMailManager/Alias.py Tue Feb 09 22:14:08 2010 +0000
+++ b/VirtualMailManager/Alias.py Wed Feb 10 02:13:35 2010 +0000
@@ -2,121 +2,153 @@
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.
-"""Virtual Mail Manager's Alias class to manage e-mail aliases."""
+"""
+ VirtualMailManager.Alias
-import VirtualMailManager.constants.ERROR as ERR
+ Virtual Mail Manager's Alias class to manage e-mail aliases.
+"""
+
from VirtualMailManager.Domain import Domain
from VirtualMailManager.EmailAddress import EmailAddress
from VirtualMailManager.Exceptions import VMMAliasException as VMMAE
-import VirtualMailManager as VMM
+from VirtualMailManager.constants.ERROR import ALIAS_ADDR_DEST_IDENTICAL, \
+ ALIAS_EXCEEDS_EXPANSION_LIMIT, ALIAS_EXISTS, NO_SUCH_ALIAS, NO_SUCH_DOMAIN
+
+
+_ = lambda msg: msg
+
class Alias(object):
"""Class to manage e-mail aliases."""
- __slots__ = ('_addr', '_dest', '_gid', '_isNew', '_dbh')
- def __init__(self, dbh, address, destination=None):
+ __slots__ = ('_addr', '_dests', '_gid', '_dbh')
+
+ def __init__(self, dbh, address):
if isinstance(address, EmailAddress):
self._addr = address
else:
raise TypeError("Argument 'address' is not an EmailAddress")
- if destination is None:
- self._dest = None
- elif isinstance(destination, EmailAddress):
- self._dest = destination
- else:
- raise TypeError("Argument 'destination' is not an EmailAddress")
- if address == destination:
- raise VMMAE(_(u"Address and destination are identical."),
- ERR.ALIAS_ADDR_DEST_IDENTICAL)
self._dbh = dbh
self._gid = 0
- self._isNew = False
- self._setAddr()
- if not self._dest is None:
- self._exists()
- if VMM.VirtualMailManager.accountExists(self._dbh, self._addr):
- raise VMMAE(_(u"There is already an account with address “%s”.") %\
- self._addr, ERR.ACCOUNT_EXISTS)
- if VMM.VirtualMailManager.relocatedExists(self._dbh, self._addr):
- raise VMMAE(
- _(u"There is already a relocated user with the address “%s”.") %\
- self._addr, ERR.RELOCATED_EXISTS)
+ self._dests = []
- def _exists(self):
- dbc = self._dbh.cursor()
- dbc.execute("SELECT gid FROM alias WHERE gid=%s AND address=%s\
- AND destination=%s", self._gid, self._addr._localpart, str(self._dest))
- gid = dbc.fetchone()
- dbc.close()
- if gid is None:
- self._isNew = True
+ self.__set_gid()
+ self.__load_dests()
- def _setAddr(self):
- dom = Domain(self._dbh, self._addr._domainname)
+ def __set_gid(self):
+ """Sets the alias' _gid based on its _addr.domainname."""
+ dom = Domain(self._dbh, self._addr.domainname)
self._gid = dom.getID()
if self._gid == 0:
- raise VMMAE(_(u"The domain “%s” doesn't exist.") %\
- self._addr._domainname, ERR.NO_SUCH_DOMAIN)
+ raise VMMAE(_(u"The domain “%s” doesn't exist.") %
+ self._addr.domainname, NO_SUCH_DOMAIN)
- def _checkExpansion(self, limit):
+ def __load_dests(self):
+ """Loads all known destination addresses into the _dests list."""
dbc = self._dbh.cursor()
- dbc.execute('SELECT count(gid) FROM alias where gid=%s AND address=%s',
- self._gid, self._addr._localpart)
- curEx = dbc.fetchone()[0]
+ dbc.execute(
+ 'SELECT destination FROM alias WHERE gid=%s AND address=%s',
+ self._gid, self._addr.localpart)
+ dests = iter(dbc.fetchall())
+ if dbc.rowcount > 0:
+ dest_add = self._dests.append
+ for dest in dests:
+ dest_add(EmailAddress(dest[0]))
dbc.close()
- if curEx == limit:
- errmsg = _(u"""Can't add new destination to alias “%(address)s”.
-Currently this alias expands into %(count)i recipients.
+
+ def __check_expansion(self, limit):
+ """Checks the current expansion limit of the alias."""
+ dcount = len(self._dests)
+ failed = False
+ if dcount == limit:
+ failed = True
+ errmsg = _(
+u"""Can't add new destination to alias “%(address)s”.
+Currently this alias expands into %(count)i/%(limit)i recipients.
One more destination will render this alias unusable.
-Hint: Increase Postfix' virtual_alias_expansion_limit
-""") % {'address': self._addr, 'count': curEx}
- raise VMMAE(errmsg, ERR.ALIAS_EXCEEDS_EXPANSION_LIMIT)
+Hint: Increase Postfix' virtual_alias_expansion_limit""")
+ elif dcount > limit:
+ failed = True
+ errmsg = _(
+u"""Can't add new destination to alias “%(address)s”.
+This alias already exceeds it's 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 VMMAE(errmsg % {'address': self._addr, 'count': dcount,
+ 'limit': limit},
+ ALIAS_EXCEEDS_EXPANSION_LIMIT)
- def save(self, expansion_limit):
- if self._dest is None:
- raise VMMAE(_(u"No destination address specified for alias."),
- ERR.ALIAS_MISSING_DEST)
- if self._isNew:
- self._checkExpansion(expansion_limit)
+ def __delete(self, destination=None):
+ """Deletes a destination from the alias, if ``destination`` is not
+ ``None``. If ``destination`` is None, the alias with all it's
+ destination addresses will be deleted."""
+ dbc = self._dbh.cursor()
+ if destination is None:
+ dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s",
+ self._gid, self._addr.localpart)
+ else:
+ dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s AND \
+ destination=%s",
+ self._gid, self._addr.localpart, str(destination))
+ if dbc.rowcount > 0:
+ self._dbh.commit()
+ dbc.close()
+
+ def __len__(self):
+ """Returns the number of destinations of the alias."""
+ return len(self._dests)
+
+ def addDestination(self, destination, expansion_limit):
+ """Adds the ``destination`` `EmailAddress` to the alias."""
+ if not isinstance(destination, EmailAddress):
+ raise TypeError("Argument 'destination' is not an EmailAddress")
+ if self._addr == destination:
+ raise VMMAE(_(u"Address and destination are identical."),
+ ALIAS_ADDR_DEST_IDENTICAL)
+ if not destination in self._dests:
+ self.__check_expansion(expansion_limit)
dbc = self._dbh.cursor()
- dbc.execute("INSERT INTO alias (gid, address, destination) VALUES\
- (%s, %s, %s)", self._gid, self._addr._localpart, str(self._dest))
+ dbc.execute('INSERT INTO alias (gid, address, destination) \
+VALUES (%s, %s, %s)',
+ self._gid, self._addr.localpart, str(destination))
self._dbh.commit()
dbc.close()
+ self._dests.append(destination)
else:
- raise VMMAE(
- _(u"The alias “%(a)s” with destination “%(d)s” already exists.")\
- % {'a': self._addr, 'd': self._dest}, ERR.ALIAS_EXISTS)
+ raise VMMAE(_(
+ u'The alias “%(a)s” has already the destination “%(d)s”.') %
+ {'a': self._addr, 'd': destination}, ALIAS_EXISTS)
- def getInfo(self):
- dbc = self._dbh.cursor()
- dbc.execute('SELECT destination FROM alias WHERE gid=%s AND address=%s',
- self._gid, self._addr._localpart)
- destinations = dbc.fetchall()
- dbc.close()
- if len(destinations) > 0:
- targets = [destination[0] for destination in destinations]
- return targets
+ def delDestination(self, destination):
+ """Deletes the specified ``destination`` address from the alias."""
+ if not isinstance(destination, EmailAddress):
+ raise TypeError("Argument 'destination' is not an EmailAddress")
+ if not self._dests:
+ raise VMMAE(_(u"The alias “%s” doesn't exist.") % self._addr,
+ NO_SUCH_ALIAS)
+ if not destination in self._dests:
+ raise VMMAE(_(u"The address “%(d)s” isn't a destination of \
+the alias “%(a)s”.") %
+ {'a': self._addr, 'd': destination}, NO_SUCH_ALIAS)
+ self.__delete(destination)
+ self._dests.remove(destination)
+
+ def getDestinations(self):
+ """Returns an iterator for all destinations of the alias."""
+ if self._dests:
+ return iter(self._dests)
else:
raise VMMAE(_(u"The alias “%s” doesn't exist.") % self._addr,
- ERR.NO_SUCH_ALIAS)
+ NO_SUCH_ALIAS)
def delete(self):
- dbc = self._dbh.cursor()
- if self._dest is None:
- dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s",
- self._gid, self._addr._localpart)
+ """Deletes the alias with all it's destinations."""
+ if self._dests:
+ self.__delete()
+ del self._dests[:]
else:
- dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s AND \
- destination=%s", self._gid, self._addr._localpart, str(self._dest))
- rowcount = dbc.rowcount
- dbc.close()
- if rowcount > 0:
- self._dbh.commit()
- else:
- if self._dest is None:
- msg = _(u"The alias “%s” doesn't exist.") % self._addr
- else:
- msg = _(u"The alias “%(a)s” with destination “%(d)s” doesn't\
- exist.") % {'a': self._addr, 'd': self._dest}
- raise VMMAE(msg, ERR.NO_SUCH_ALIAS)
+ raise VMMAE(_(u"The alias “%s” doesn't exist.") % self._addr,
+ NO_SUCH_ALIAS)
+
+del _
--- a/VirtualMailManager/Handler.py Tue Feb 09 22:14:08 2010 +0000
+++ b/VirtualMailManager/Handler.py Wed Feb 10 02:13:35 2010 +0000
@@ -42,7 +42,7 @@
class Handler(object):
"""Wrapper class to simplify the access on all the stuff from
VirtualMailManager"""
- __slots__ = ('_Cfg', '_cfgFileName', '__dbh', '_scheme', '__warnings',
+ __slots__ = ('_Cfg', '_cfgFileName', '_dbh', '_scheme', '__warnings',
'_postconf')
def __init__(self, skip_some_checks=False):
@@ -58,7 +58,7 @@
self._cfgFileName = ''
self.__warnings = []
self._Cfg = None
- self.__dbh = None
+ self._dbh = None
if os.geteuid():
raise VMMNotRootException(_(u"You are not root.\n\tGood bye!\n"),
@@ -129,16 +129,16 @@
def __dbConnect(self):
"""Creates a pyPgSQL.PgSQL.connection instance."""
- if self.__dbh is None or (isinstance(self.__dbh, PgSQL.Connection) and
- not self.__dbh._isOpen):
+ if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and
+ not self._dbh._isOpen):
try:
- self.__dbh = PgSQL.connect(
+ self._dbh = PgSQL.connect(
database=self._Cfg.dget('database.name'),
user=self._Cfg.pget('database.user'),
host=self._Cfg.dget('database.host'),
password=self._Cfg.pget('database.pass'),
client_encoding='utf8', unicode_results=True)
- dbc = self.__dbh.cursor()
+ dbc = self._dbh.cursor()
dbc.execute("SET NAMES 'UTF8'")
dbc.close()
except PgSQL.libpq.DatabaseError, e:
@@ -157,22 +157,22 @@
def accountExists(dbh, address):
sql = "SELECT gid FROM users WHERE gid = (SELECT gid FROM domain_name\
- WHERE domainname = '%s') AND local_part = '%s'" % (address._domainname,
- address._localpart)
+ WHERE domainname = '%s') AND local_part = '%s'" % (address.domainname,
+ address.localpart)
return Handler._exists(dbh, sql)
accountExists = staticmethod(accountExists)
def aliasExists(dbh, address):
sql = "SELECT DISTINCT gid FROM alias WHERE gid = (SELECT gid FROM\
domain_name WHERE domainname = '%s') AND address = '%s'" % (
- address._domainname, address._localpart)
+ address.domainname, address.localpart)
return Handler._exists(dbh, sql)
aliasExists = staticmethod(aliasExists)
def relocatedExists(dbh, address):
sql = "SELECT gid FROM relocated WHERE gid = (SELECT gid FROM\
domain_name WHERE domainname = '%s') AND address = '%s'" % (
- address._domainname, address._localpart)
+ address.domainname, address.localpart)
return Handler._exists(dbh, sql)
relocatedExists = staticmethod(relocatedExists)
@@ -181,27 +181,25 @@
address = EmailAddress(address)
if not password is None:
password = self.__pwhash(password)
- return Account(self.__dbh, address, password)
+ return Account(self._dbh, address, password)
- def __getAlias(self, address, destination=None):
+ def __getAlias(self, address):
self.__dbConnect()
address = EmailAddress(address)
- if destination is not None:
- destination = EmailAddress(destination)
- return Alias(self.__dbh, address, destination)
+ return Alias(self._dbh, address)
def __getRelocated(self, address, destination=None):
self.__dbConnect()
address = EmailAddress(address)
if destination is not None:
destination = EmailAddress(destination)
- return Relocated(self.__dbh, address, destination)
+ return Relocated(self._dbh, address, destination)
def __getDomain(self, domainname, transport=None):
if transport is None:
transport = self._Cfg.dget('misc.transport')
self.__dbConnect()
- return Domain(self.__dbh, domainname,
+ return Domain(self._dbh, domainname,
self._Cfg.dget('misc.base_directory'), transport)
def __getDiskUsage(self, directory):
@@ -474,12 +472,12 @@
domainname -- name of the target domain (str)
"""
dom = self.__getDomain(domainname)
- aliasDom = AliasDomain(self.__dbh, aliasname, dom)
+ aliasDom = AliasDomain(self._dbh, aliasname, dom)
aliasDom.save()
def aliasDomainInfo(self, aliasname):
self.__dbConnect()
- aliasDom = AliasDomain(self.__dbh, aliasname, None)
+ aliasDom = AliasDomain(self._dbh, aliasname, None)
return aliasDom.info()
def aliasDomainSwitch(self, aliasname, domainname):
@@ -490,7 +488,7 @@
domainname -- name of the new target domain (str)
"""
dom = self.__getDomain(domainname)
- aliasDom = AliasDomain(self.__dbh, aliasname, dom)
+ aliasDom = AliasDomain(self._dbh, aliasname, dom)
aliasDom.switch()
def aliasDomainDelete(self, aliasname):
@@ -500,7 +498,7 @@
aliasname -- the name of the alias domain (str)
"""
self.__dbConnect()
- aliasDom = AliasDomain(self.__dbh, aliasname, None)
+ aliasDom = AliasDomain(self._dbh, aliasname, None)
aliasDom.delete()
def domainList(self, pattern=None):
@@ -520,7 +518,7 @@
_(u"The pattern “%s” contains invalid characters.") %
pattern, ERR.DOMAIN_INVALID)
self.__dbConnect()
- return search(self.__dbh, pattern=pattern, like=like)
+ return search(self._dbh, pattern=pattern, like=like)
def userAdd(self, emailaddress, password):
if password is None or (isinstance(password, basestring) and
@@ -536,14 +534,18 @@
self.__mailDirMake(acc.getDir('domain'), acc.getUID(), acc.getGID())
def aliasAdd(self, aliasaddress, targetaddress):
- alias = self.__getAlias(aliasaddress, targetaddress)
- alias.save(long(self._postconf.read('virtual_alias_expansion_limit')))
- gid = self.__getDomain(alias._dest._domainname).getID()
- if gid > 0 and (not Handler.accountExists(self.__dbh, alias._dest) and
- not Handler.aliasExists(self.__dbh, alias._dest)):
+ """Creates a new `Alias` entry for the given *aliasaddress* with
+ the given *targetaddress*."""
+ alias = self.__getAlias(aliasaddress)
+ destination = EmailAddress(targetaddress)
+ alias.addDestination(destination,
+ long(self._postconf.read('virtual_alias_expansion_limit')))
+ gid = self.__getDomain(destination.domainname).getID()
+ if gid > 0 and (not Handler.accountExists(self._dbh, destination) and
+ not Handler.aliasExists(self._dbh, destination)):
self.__warnings.append(
_(u"The destination account/alias “%s” doesn't exist.") %
- alias._dest)
+ destination)
def userDelete(self, emailaddress, force=None):
if force not in [None, 'delalias']:
@@ -570,12 +572,34 @@
raise
def aliasInfo(self, aliasaddress):
+ """Returns an iterator object for all destinations (`EmailAddress`
+ instances) for the `Alias` with the given *aliasaddress*."""
alias = self.__getAlias(aliasaddress)
- return alias.getInfo()
+ try:
+ return alias.getDestinations()
+ except VMMAliasException, e:
+ if e.code() == ERR.NO_SUCH_ALIAS:
+ if Handler.accountExists(self._dbh, alias._addr):
+ raise VMMException(
+ _(u'There is already an account with address “%s”.') %
+ aliasaddress, ERR.ACCOUNT_EXISTS)
+ if Handler.relocatedExists(self._dbh, alias._addr):
+ raise VMMException(_(u'There is already a relocated user \
+with the address “%s”.') %
+ aliasaddress, ERR.RELOCATED_EXISTS)
+ raise
+ else:
+ raise
def aliasDelete(self, aliasaddress, targetaddress=None):
- alias = self.__getAlias(aliasaddress, targetaddress)
- alias.delete()
+ """Deletes the `Alias` *aliasaddress* with all its destinations from
+ the database. If *targetaddress* is not ``None``, only this
+ destination will be removed from the alias."""
+ alias = self.__getAlias(aliasaddress)
+ if targetaddress is None:
+ alias.delete()
+ else:
+ alias.delDestination(EmailAddress(targetaddress))
def userInfo(self, emailaddress, details=None):
if details not in (None, 'du', 'aliases', 'full'):
@@ -594,7 +618,7 @@
def userByID(self, uid):
from Handler.Account import getAccountByID
self.__dbConnect()
- return getAccountByID(uid, self.__dbh)
+ return getAccountByID(uid, self._dbh)
def userPassword(self, emailaddress, password):
if password is None or (isinstance(password, basestring) and
@@ -647,5 +671,5 @@
relocated.delete()
def __del__(self):
- if isinstance(self.__dbh, PgSQL.Connection) and self.__dbh._isOpen:
- self.__dbh.close()
+ if isinstance(self._dbh, PgSQL.Connection) and self._dbh._isOpen:
+ self._dbh.close()